ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

统信UOS下Avalonia应用制作deb包全流程实操指南

统信UOS下Avalonia应用制作deb包全流程实操指南 统信系统上分发 Avalonia 应用最绕不开的一步就是把它打成 deb 安装包。Avalonia 是 .NET 生态里少数能在 Linux 桌面把界面做得原生、又能跨平台复用的 UI 框架但每次把编译产物直接丢给同事对方第一句几乎都是“这怎么安装”于是我把整套 deb 打包流程自己跑了一遍从目录结构、control 文件、desktop 启动器到依赖分析、安装脚本全部踩完坑之后整理成下面这份可以直接照抄的实操记录。如果你手上正好有一个 Avalonia 项目要发给统信 UOS 用户这篇文章应该能帮你少走好几天的弯路。1. 这件事为什么值得做1.1 统信系统下分发应用的现实痛点统信 UOS 不是一个小众玩具系统很多政企客户、行业软件都在上面跑。但它的应用分发方式和 Windows 完全不一样Windows 上双击 exe或者用安装精灵下一步下一步用户都能理解到了统信上如果你给一个非技术用户发一个 zip 包让他解压后点二进制文件他大概率会直接懵掉或者右键执行时发现没有权限又或者终端里能跑起来但桌面图标永远找不到。deb 是 Debian 系 Linux 的标准软件包格式统信 UOS 底层基于 Debian 体系所以 deb 包就是最通用、最不容易出问题的分发形态。安装时只需要双击安装包或在终端执行sudo dpkg -i xxx.deb就能把程序放到系统正确的位置生成开始菜单项、图标、文件关联和自动卸载信息。对比手动部署deb 包让用户从“会解压”降级到“会双击”这一步对非技术用户的友好度提升是巨大的。另外很多企业都要求软件能够统一安装、统一卸载、统一审计deb 包天然支持这些能力它会在系统数据库里注册软件信息卸载时不会留下一堆碎文件。这个特性在统信环境里尤其重要因为不少单位的使用者并不习惯手动清理残留文件装完又卸载之后系统里乱七八糟最后怪到软件头上。1.2 Avalonia 为什么能融入统信生态Avalonia 是一个基于 .NET 的跨平台 UI 框架最大的特点是用 XAML 写界面一起编译成 IL然后在不同的操作系统上通过各自的渲染后端把界面画出来。它不像 WPF 只认 Windows也不像 Electron 得自带一整个 ChromiumAvalonia 的程序体积适中、启动速度相对轻快在国产 Linux 系统里跑起来很自然。统信 UOS 上有不少桌面应用其实是 HTML 套壳或者 Electron 打包的内存占用动不动就 500MB 起步。Avalonia 应用的内存占用通常低很多拿我做过的一个数据分析工具来说同样的界面Avalonia 版本总内存占用不到 Electron 版本的一半对配置不高的国产整机来说这个差异体感非常明显。而且 Avalonia 支持 Linux 下常见的 HiDPI、窗口阴影、透明背景、自定义标题栏能做出现代感比较强的界面不至于一放到统信上就显得像老古董。从开发者的角度看Avalonia 用的 C#、XAML、MVVM 模式和 WPF/UWP 那套很接近团队成员如果本来就会 WPF可以直接上手。代码在 Windows、Linux、macOS 之间不换语言核心逻辑和界面结构可以共用这对需要同时维护多平台产品的团队来说省了不少人力。1.3 打包方案的选型dpkg-deb 足够用Linux 下有很多种打包方式可以用debhelper、dh_make生成完整 Debian 源码包也可以用electron-builder或flutter自带的打包工具但针对 Avalonia 这样的自定义构建产物我最后选了最朴素也最可控的dpkg-deb直接打二进制包。为什么不用 dh_make因为 dh_make 更适用于从源码编译的 Debian 包会引入一整套构建规则、Makefile、安装目录映射对 Avalonia 来说有点大炮打蚊子而且配置复杂稍微写错一个规则生成的包就会在安装时出现莫名其妙的行为。我们直接用dotnet publish产出我们想要的文件再把这些文件按 deb 格式放进指定目录交给dpkg-deb打包整个流程不过十几行命令全程看得见摸得着排查问题也简单。选中 dpkg-deb 还有一个原因统信 UOS 自带该工具不需要额外安装繁琐的构建依赖。在纯内网环境、无外网权限的客户那里你总不可能让他们临时去 apt 安装一个devscripts。项目现场经常是什么环境都很干净、什么都装不了所以依赖越少越好dpkg-deb属于基础工具绝大多数系统镜像里都预装了。2. 前期准备把环境理顺再动手2.1 在统信上安装 .NET SDK打包以前首先要保证你能在统信系统上完成 Avalonia 项目的编译和发布。统信 UOS 默认软件源里可能没有最新版 .NET SDK不过微软官方给 Debian 系系统提供了包源统信兼容 Debian所以可以参考 Debian 的方式安装只是注意版本对应关系。我当时的操作是在终端里依次执行wget https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb sudo dpkg -i packages-microsoft-prod.deb sudo apt update sudo apt install -y dotnet-sdk-6.0如果你的系统是统信 UOS 家庭版或者专业版不同版本的底层库版本略有差异但这条路通常都能走通。装完之后用dotnet --info验证版本确认 SDK 能正常运行再往下走。如果是在离线内网环境就得提前准备好 .NET SDK 的离线安装包或者在另一台同版本系统上把依赖打包成 deb 带进去。这里一定不要图省事直接把开发机里的 dotnet 目录拷贝过去统信系统上文件权限和依赖库非常敏感拷贝过去往往会报一堆libhostfxr.so找不到的错误。2.2 创建并编译 Avalonia 项目在统信系统上dotnet new命令默认不会自带 Avalonia 模板需要先安装模板扩展dotnet new install Avalonia.Templates dotnet new avalonia.app -o MyApp cd MyApp dotnet build -c Release模板生成的项目已经包含一个最基本的窗口能直接编译运行。建议先把程序跑一遍确认在统信桌面环境里界面能正常显示再去做打包。这一步很多人会跳过直接上发布命令结果打完包之后在干净的测试机上才暴露问题排查起来要从头开始非常浪费时间。编译时建议用 Release 配置并且去掉不必要的调试信息。可以在.csproj里按下述内容进行配置保证剪裁掉不必要的 PDB 和开发期组件PropertyGroup DebugTypenone/DebugType DebugSymbolsfalse/DebugSymbols PublishSingleFilefalse/PublishSingleFile /PropertyGroup我一般不建议使用单文件发布Avalonia 项目带上原生依赖库之后单文件模式在 Linux 上偶尔会出现原生库解压不出来的问题而且启动时多一层自解压速度反而更慢。2.3 确认 dpkg-deb 可用统信 UOS 上 dpkg-deb 是默认安装的但为了保险还是先检查一下。dpkg-deb --version如果没有可以用sudo apt install dpkg装上但这种情况比较少见。另外建议准备一个干净的打包目录不要直接在编译输出目录里乱建文件。我自己的习惯是在项目根目录下建一个packaging目录所有 deb 包相关的临时文件都放在里面打包完就清理避免把中间产物混进源码仓库。3. 手把手构建一个能用的 deb 包3.1 理解 deb 包的标准目录结构deb 包本质上是一个经过ar归档和gzip压缩的容器里面除了控制信息control、md5sums、postinst 等之外剩下就是程序要安装的文件并且文件在包里存放的路径就是它安装到系统后的绝对路径。也就是说如果你希望程序最终安装在/usr/bin/myapp那么打包目录里就要有usr/bin/myapp这个文件。最容易理解的方式是建一个以软件包命名的根目录比如myapp_1.0.0里面放两个目录DEBIAN和usr。DEBIAN目录是给包管理工具用的不会安装到系统里去usr目录下的所有内容会被原样复制到系统的/usr目录。所以你要保证根目录下的usr和系统根目录/是一一对应的。常见目录规划如下DEBIAN/control包的元信息包括名称、版本、架构、依赖、描述。usr/bin/放启动脚本或可执行文件的软链接。一般不建议直接把 DLL 或可执行文件大块头扔在这里因为/usr/bin讲究的是干净。usr/lib/myapp/放 Avalonia 发布产物包括所有 DLL、运行时、原生库。usr/share/applications/放.desktop文件注册到开始菜单。usr/share/icons/hicolor/xxx/apps/放应用图标。之所以把主程序放在/usr/lib/myapp/而不是/usr/bin/myapp是为了避免用户直接双击/usr/bin下的二进制文件导致 DLL 找不到或者环境变量错乱。我们可以用/usr/bin/myapp下的一个启动脚本去读取/usr/lib/myapp中的程序集这样逻辑清晰也方便以后做权限控制。3.2 编写 control 文件包的身份信息DEBIAN/control是每个 deb 包的核心没有它 dpkg 根本不认这个包。我一份常用的 control 文件贴出来Package: myapp Version: 1.0.0 Section: utils Priority: optional Architecture: amd64 Depends: libc6 ( 2.31), libice6, libsm6, libfontconfig1, libgl1 Maintainer: Your Name youexample.com Description: Cross-platform desktop application Built with Avalonia for UOS.关键字段逐个说Package包名只能包含小写字母、数字和横线不要用大写。Version版本号格式不要乱搞建议用1.0.0这种三段式。Architectureamd64还是arm64取决于目标机器。统信 UOS 常见的是 x86_64但飞腾、鲲鹏等 ARM 平台也不少后面专门讲多架构。Depends依赖包的 Debian 名称。如果某项依赖缺失安装时系统会警告但你依然可以强制安装只是运行时会出问题。写 Depends 的时候不要拍脑袋可以用dpkg-deb -I查看已有 deb 包的依赖或者用ldd看程序依赖哪些.so文件再反查这些.so属于哪些系统包。这个过程比较繁琐但能让 deb 包的健壮性提高一大截。比如 Avalonia 在 Linux 上依赖libSkiaSharp的某个版本而它通常又依赖libfontconfig1、libgl1这些底层库最好在 Depends 里显式声明。3.3 发布 Avalonia 程序并放入 lib 目录在项目根目录执行dotnet publish -c Release -r linux-x64 --self-contained false -o ./publish/linux-x64如果选择--self-contained false目标机器上必须安装 .NET 运行时如果选择--self-contained true会把整个 .NET 运行时打进发布目录体积更大但不依赖系统环境。我推荐在统信系统上用--self-contained true理由后面再说。发布完成之后把publish/linux-x64下的所有文件复制到打包根目录的usr/lib/myapp/下。注意保留可执行权限mkdir -p myapp_1.0.0/usr/lib/myapp cp -r ./publish/linux-x64/* myapp_1.0.0/usr/lib/myapp/ chmod x myapp_1.0.0/usr/lib/myapp/MyApp如果 MyApp 这个主程序文件依赖同目录下的其他文件启动时需要用cd切到吗不需要但脚本里最好绕一下保证它在任何当前目录下都能正确找到自己的程序集。3.4 创建启动脚本和 .desktop 文件在usr/bin下放一个同名的小脚本内容如下#!/bin/bash exec /usr/lib/myapp/MyApp $给脚本可执行权限chmod x myapp_1.0.0/usr/bin/myapp这里其实也可以直接把发布出来的可执行文件软链到/usr/bin下但 Avalonia 程序启动时经常需要定位自己的运行时库如果直接用软链程序会尝试从/usr/bin找依赖容易失败。脚本方式更稳它能确保程序入口总在/usr/lib/myapp里依赖全部就位。.desktop文件放在usr/share/applications/myapp.desktop我照抄一下[Desktop Entry] Version1.0 TypeApplication NameMyApp GenericNameMyApp CommentAvalonia application Exec/usr/bin/myapp Icon/usr/share/icons/hicolor/256x256/apps/myapp.png Terminalfalse CategoriesUtility; StartupNotifytrue这里面比较容易踩坑的字段是Exec。它必须是绝对路径而且如果脚本里带参数比如Exec/usr/bin/myapp %F要确保脚本正确处理参数。Icon路径如果不写绝对路径系统会去图标主题里找但那需要你先安装图标到正确位置我还是建议直接写绝对路径少一个让系统猜的环节。Categories字段决定了应用出现在开始菜单的哪个分类下想让它出现在“系统工具”里就写System;Utility;想出现在“开发”里就写Development;但注意分类要符合 freedesktop 规范乱写可能导致菜单里找不到。3.5 图标处理与多尺寸支持统信系统的开始菜单会按图标主题去加载图标推荐安装到/usr/share/icons/hicolor/尺寸/apps/目录。最容易让用户满意的做法是提供 16、32、48、64、128、256 多个尺寸不过最小可用方案是一个 256x256 的 PNG。用 ImageMagick 可以批量生成mkdir -p myapp_1.0.0/usr/share/icons/hicolor/256x256/apps convert app_icon.png -resize 256x256 myapp_1.0.0/usr/share/icons/hicolor/256x256/apps/myapp.png如果不想装 ImageMagick也可以用 .NET 或者 Avalonia 本身自己画一个图标导出但效率太低。实在不行用 GIMP 导出一个单尺寸也行。图标格式优先 PNG不要用 JPEG因为 JPEG 没有 alpha 通道圆角图标边缘会很难看。统信系统对 SVG 图标支持也好但如果你的图标用 PNG 做出来已经是高清的那就没必要非用 SVG。3.6 可选写 postinst 脚本处理首次安装动作有的程序在安装完以后需要更新图标缓存、注册 MIME 类型或者设置某些权限这些可以在DEBIAN/postinst脚本里完成。这是一个普通 shell 脚本安装后由 dpkg 以 root 身份执行。比如更新图标缓存#!/bin/bash set -e if command -v gtk-update-icon-cache /dev/null 21; then gtk-update-icon-cache -f -t /usr/share/icons/hicolor fi exit 0对应的卸载前清理脚本prerm可以写也可以不写。如果程序本身没有写系统目录的需求完全可以让整个包只有DEBIAN/control和文件目录不再带脚本这样包反而更干净。我一般不写 postinst除非需要做桌面图标缓存刷新或者要把某个文件设为 setuid。注意postinst 脚本里任何一行出错都会导致安装被回滚所以脚本越简单越好不要在里面做太多不必要的事情。3.7 使用 dpkg-deb 正式打包所有文件放好之后执行dpkg-deb --build --root-owner-group myapp_1.0.0默认会在当前目录生成myapp_1.0.0.deb。--root-owner-group这个参数很重要它会强制让包内所有文件的属主变成 root防止你在普通用户目录下打包导致文件属主变成你自己的 UID安装完出现权限失控问题。如果你不写这个参数有些统信系统安装包时会做所有权检查并给出提示甚至安装失败。装包测试sudo dpkg -i myapp_1.0.0.deb如果之前报依赖缺失可以用sudo apt -f install自动修复但真正该做的是把 Depends 写完整而不是让用户去手动解决依赖。4. 发布模式与依赖管理的几个决策点4.1 自包含还是框架依赖我为什么推荐自包含Avalonia 程序有两种发布方式框架依赖发布和自包含发布。框架依赖意味着目标机器上得有对应版本的 .NET 运行时统信 UOS 预装 .NET 运行时的概率非常低如果你强制依赖系统运行时用户安装完点开程序大概率直接报错“You must install .NET runtime”或者“hostfxr.dll not found”。自包含发布则是把 .NET 运行时、Avalonia 运行时、所有依赖的 native 库一起打进去。代价是体积变大通常一个小工具从 20MB 变成 60MB 到 80MB但现在存储和带宽都不太在乎这点体积换来的是在干净系统上开箱即用这对企业环境太重要了。命令改成dotnet publish -c Release -r linux-x64 --self-contained true -o ./publish/linux-x64配合-p:PublishTrimmedfalse我建议先不裁剪。裁剪会减少体积但 Avalonia 依赖大量反射和动态类型裁剪不当会出现界面空白、控件不渲染或者运行时抛异常排查难度很高。除非你非常明确地测试过裁剪之后的完整功能否则别动。4.2 分析 Deb 包的实际依赖即便是自包含发布应用还是有少量系统级依赖比如 glibc、libstdc、libfontconfig、libGL、libX11 等。查询依赖可以用ldd命令ldd /usr/lib/myapp/MyApp输出里会列出它需要的.so名称。注意统信系统本身自带这些库但版本有差异比如有的低版本系统 glibc 是 2.28而高版本 .NET 运行时要求 glibc 2.31 以上装上去以后运行就报 GLIBC 找不到。这时候要么降低 .NET 运行时版本要么在文档里明确标注系统版本要求。我一般建议在 Depends 里写上libc6 ( 2.31)并在发布说明里写好最低系统版本。想要更精确地反查依赖包用apt-file比较麻烦实际项目里我通常是装到干净虚拟机上测一遍用什么报什么再逐个补到 control 的 Depends 里。这个“先打包、再测试、再改依赖”的循环虽然笨但最准。4.3 如果用到 Avalonia 原生库或 VLCAvalonia 在 Linux 上依赖libSkiaSharp它自带 glibc 兼容等常见底层依赖。如果你在程序里使用了LibVLCSharp或者Avalonia.Vlc这类的播放组件还要把libvlc.so、libvlccore.so以及 VLC 的插件目录一并打包或者通过 Depends 依赖系统的libvlc-dev。VLC 这种库用 dpkg 依赖系统包的方案更靠谱因为插件很多手动打包容易漏而且 VLC 版本升级后二进制兼容性容易出问题。你得在 control 文件里写Depends: vlc ( 3.0)安装时确保用户有 VLC 或者自动装上。如果客户端不允许装额外软件那就只能用--self-contained true方案把整个 VLC 运行时也塞进去但体积会接近 200MB体验不太好。如果只是普通界面程序没有调用视频播放建议不要引入 VLC很多坑都是引入第三方原生库后出现的比如某些系统缺少libX11或libXext界面启动时直接段错误这种问题排查起来头大。5. 常见问题与排查实录5.1 安装后开始菜单找不到应用这是一个高频问题。安装后如果开始菜单没出现图标先检查.desktop文件权限必须是可读的而且内容不能有Exec指向不存在的路径。手动验证可以执行desktop-file-validate /usr/share/applications/myapp.desktop如果提示没有desktop-file-utils先装上sudo apt install desktop-file-utilsdesktop-file-validate会告诉你Exec字段格式是否正确、Icon路径是否合法。另外一个容易被忽略的点是统信系统有自己的一套“应用图标缓存”有时候装完不会立刻刷新可以执行update-desktop-database /usr/share/applications或者注销重登很多情况下比重启还管用。还有一个原因.desktop文件里Name字段如果包含中文而系统 locale 是英文也可能显示异常但通常这不是“找不到”的最主要原因。5.2 双击启动没反应双击桌面上的应用图标鼠标转了两下就没然后了这种情况十次有八次是启动脚本出错或依赖没找到。在终端里手动运行/usr/bin/myapp看终端输出的错误信息。常见报错包括No such file or directory二进制或动态库路径错了。GLIBCXX_3.4.29 not found系统 libstdc 版本太低。Failed to create drawing context显卡驱动或 X11 问题统信系统某些老显卡会出现这种问题但和打包本身无关。还有一种情况是脚本的 shebang 写成了#!/bin/bash但目标系统把/bin/bash换成/usr/bin/bash了这多见于极简环境。提高兼容性的办法是把 shebang 写成#!/usr/bin/env bash但更稳的是干脆不写脚本直接把主程序放到/usr/lib/myapp下再用一个软链或包装脚本。5.3 报错 libhostfxr.so 找不到这种情况几乎可以断定是 .NET 运行时没有正确部署。如果你用的是框架依赖发布目标机器没有安装对应版本的 .NET Runtime那么无论你怎么打包都不可能让用户直接跑起来除非在 postinst 里偷偷去下运行时但这是最蠢的做法。解决办法就是改回自包含发布把 runtime 打进去。另外注意如果自包含发布后发布目录里的libhostfxr.so因为文件权限问题没有一起进包也会出现这种错误。检查一下usr/lib/myapp下是否有libhostfxr.so文件以及整个目录的权限是不是 root。若打包时忘了加--root-owner-group目录权限可能是你的用户名虽说不影响 root 读但 dpkg 对包内所有权检查很苛刻。5.4 统信系统登录后黑屏、root 被锁定等周边环境问题在客户现场测试时会遇到一些统信系统本身的问题例如有些机器登录后黑屏、只显示鼠标指针或者提示root is locked无法进入系统维护模式。这些多数是系统的显示驱动或账号策略问题不是 Avalonia 应用导致的。我的处理经验是先区分问题是否在安装我们的软件前就存在。如果装之前登录就正常装之后才黑屏重点查程序是否把显卡驱动目录覆盖了或者是否通过 postinst 改了系统配置。如果装之前已经黑屏那大概率和我们无关。为避免误导我通常会在发布说明里写清楚测试环境统信 UOS 专业版 1050、X11 模式、NVIDIA 显卡驱动版本。这样客户复现问题时可以对照环境差异。5.5 卸载不干净或升级失败卸载时执行sudo dpkg -r myapp系统会删掉安装时写入的文件。如果有日志或配置写在/etc/myapp或~/.config/MyAppdpkg 默认不删这符合 Linux 惯例。但有些用户期望卸载后完全干净你可以在postrm脚本里删掉这些用户数据但千万慎重因为用户可能只是升级软件想保留配置如果把数据删了升级完后找不到原先的设置更容易挨骂。升级失败的常见情形是新版本包里某个文件曾经属于另一个包dpkg 会报文件冲突。解决方法是升级前先核对旧版本卸载是否成功以及两个不同包之间是否都写了同一个路径。比如你把启动脚本放到了/usr/bin/myapp另一个包othertool也占用了这个路径dpkg 就不让你装。此时把脚本改成/usr/bin/myapp-launcher之类的唯一名字就能绕开。6. 验证、分发与一点扩展6.1 在干净系统上做安装验证打包完之前所有步骤都不算完成唯一能让你放心交付的验证方式是在一台干净的统信系统上用普通用户身份安装并运行。我每次都会开一台虚拟机装上和客户一致的统信版本执行sudo dpkg -i myapp_1.0.0.deb然后从开始菜单启动应用测试关键的几个界面切换、文件打开、数据保存操作。这一步能暴露大量问题比如图标尺寸不对导致出现在菜单里很模糊或者某个依赖库缺失在客户机器上才报出来。做完基础验证后再用dpkg -L myapp看系统上的文件中哪些被安装确认没有多余文件漏进去。6.2 多架构打包amd64 和 arm64 一起出统信 UOS 不只跑在 Intel 上飞腾、鲲鹏这些 ARM 平台也很常见。Avalonia 项目可以分别发布dotnet publish -c Release -r linux-x64 --self-contained true -o ./publish/linux-x64 dotnet publish -c Release -r linux-arm64 --self-contained true -o ./publish/linux-arm64为不同架构分别制作打包根目录control 里的Architecture分别填amd64和arm64。要注意的是Avalonia 原生库libSkiaSharp.so在不同架构上是不同文件不能混用。我只能告诉你在 ARM 平台上编译时开发机如果是 x86直接发布会报错最好在 ARM 机器上编译或者用交叉编译工具链。没有 ARM 开发机的话用 GitHub Actions 等云服务跑一个ubuntu-22.04-arm环境的 runner 也能解决。6.3 分发仓库、内部源和自动更新debian 包最舒服的地方在于它可以直接投喂给内网的 apt 仓库。你可以用reprepro或aptly搭建一个简单的私有源然后把 deb 包传上去客户只要配置源地址执行apt install myapp就能完成安装和升级。这个能力在政企项目里很加分尤其是你需要面向几十台机器统一部署时。我自己的一个扩展经验是在 avalonia 程序里内置一个版本检测启动时请求一个静态接口返回最新版本号和 deb 包的下载地址用户点“检查更新”程序自动下载 deb 并用pkexec dpkg -i调用系统包管理器完成升级。这样可以绕开应用商店审核也避开了给用户发安装包的繁琐流程。但注意权限问题需要向普通用户明确说明软件将请求 root 权限。这个方案如果对安全有顾虑建议只在受控的内网环境用。最后讲一个我踩得最深的坑不要把编译目录直接当作最终交付物。有段时间我觉得每次重新编译完手动复制到远程机器很麻烦于是直接把publish目录打个压缩包发给客户结果客户拿回去解压以后不仅图标没有就连程序文件权限也全乱了因为 zip 不保留 Unix 可执行位。从那以后我再也没有绕过 deb 打包直接发裸目录因为 deb 包的目录结构和权限是明文定义的安装行为可预期出了问题也好查。你按这套流程走统信系统的用户拿到包双击安装桌面图标自然出现比折腾解压强太多了。
返回列表