- packaging/linux: netx-<ver>-linux-x86_64.tar.gz with bundled Python 3.12, PostgreSQL 16 and web/dist (glibc >= 2.17). install.sh (install/update/ reinstall), netxctl (start/stop/status/logs, set-port, set-bind, set-pg-port, reconfigure-db, check-update/update/auto-update, backup, uninstall), systemd units, nohup fallback without systemd. Privilege drop, detaching and tar go through the bundled Python, so bare images without su/runuser/setsid/tar work. - Updates: same GitHub/Forgejo/manifest sources as Windows, sha256 verified, pre-update pg_dump, automatic rollback when the new version does not come up. - Non-loopback bind: generated bootstrap admin password and NETX_UME_VERIFY_TLS=true, as security_bootstrap requires. - Build in manylinux2014 + check_bundle.sh (GLIBC <= 2.17, no missing libs); release-linux.yml builds on v* tags and smoke-tests offline on CentOS 7, Rocky 9, Ubuntu 18.04/24.04, Debian 12, openSUSE Leap 15 and openEuler 22.03 before attaching to the release. - Windows: Setup DB page gets a bundled PG port field and /BundledPgPort=; set_pg_port.ps1 + tray "Built-in database port"; reconfigure keeps the configured port; postgresql.conf is written without a BOM (PS 5.1 Set-Content -Encoding utf8 broke postgres). - Forgejo/GitHub publish scripts include the Linux package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
22 KiB
NetX Linux 发行版实施计划(2026-10-14)
修订(同日):用户确认只出 x86_64;API/Web 端口也必须可自选(服务器上服务多,避免冲突)。下文已按此调整;由 Claude 直接实现。
0. 目标与已确认的需求
| # | 需求(用户确认) | 落地方式 |
|---|---|---|
| 1 | 覆盖尽量多的 Linux 发行版 | 以 glibc ≥ 2.17 为基线(CentOS 7 级别),只出 x86_64 包 |
| 2 | tar.gz + install.sh;离线安装,目标机上什么都不用装 | 包内自带 Python 3.12、全部依赖、PostgreSQL 16 和 web/dist;目标机只需要 bash、tar、gzip、coreutils 和 useradd,这些是任何 glibc 发行版的最小系统都有的 |
| 3 | 在线更新 | netxctl update,更新源与 Windows 版一致(GitHub / Forgejo / manifest),可选每日自动更新(systemd timer);失败自动回滚程序 |
| 4a | API/Web 端口可配置 | 安装时 --port 或交互输入(检查占用,被占用时重新输入);装好后用 netxctl set-port,失败自动回滚 |
| 4b | 内置 PG 端口可配置 | Linux:安装时 --pg-port 或交互输入,装好后用 netxctl set-pg-port;Windows 同步补齐:Setup 数据库页加端口框,静默参数 /BundledPgPort=,另提供改端口脚本 |
现状:后端是纯 Python,依赖都有 manylinux2014 wheel,host_metrics.py 已有 Linux 分支。所以 netx_api 业务代码不需要改,工作量都在 packaging/linux/、CI 和 Windows 的端口配置上。scripts/start_netx.sh / stop_netx.sh 保留,继续作为源码/开发模式的启动方式,不动。
支持范围
- 支持(glibc ≥ 2.17,x86_64):CentOS 7/8/Stream 9、RHEL 7–9、Rocky/Alma 8–9、Oracle Linux、Fedora、Ubuntu 18.04+、Debian 9+、openSUSE Leap/SLES 12+、openEuler 20.03+、麒麟 V10、统信 UOS 20、Anolis 等。
- 不支持(第一期):ARM(鲲鹏/飞腾)、Alpine 等 musl 系统、32 位系统、CentOS 6(glibc 2.12)、龙芯 LoongArch / MIPS(没有现成的 standalone Python,后续单独评估)。
- 有没有 systemd 都能用:有 systemd 就注册服务;没有(容器、精简系统)就用
netxctl start/stop后台运行。
1. 产物与目录布局
1.1 发布产物(与 Windows Setup 挂在同一个 GitHub/Forgejo Release 下)
netx-<ver>-linux-x86_64.tar.gz + .sha256
解压后:
netx-<ver>-linux-x86_64/
install.sh # 入口:安装 / 更新 / 重装
version.json # {"version": "...", "arch": "x86_64", "pg_major": 16, "python": "3.12.x"}
netx_api/ alembic/ alembic.ini pyproject.toml requirements.txt README.md LICENSE
web/dist/ # 由 API 托管,不需要 Node
python/ # python-build-standalone 3.12,依赖直接装在它的 site-packages 里(不用 venv)
pgsql/{bin,lib,share} # PostgreSQL 16 只保留运行需要的部分;rpath=$ORIGIN/../lib
packaging/linux/ # netxctl、netx_admin.py、systemd 单元模板、logrotate、README
1.2 安装后的目录
| 路径 | 内容 | 属主 / 权限 |
|---|---|---|
/opt/netx/releases/<ver>/ |
某个版本的程序(只读) | root:root 0755 |
/opt/netx/current → releases/<ver> |
当前版本的软链,切换是原子的 | |
/usr/bin/netxctl → /opt/netx/current/packaging/linux/netxctl |
管理命令 | |
/var/lib/netx/ |
.env、pgdata/、data/…(与 Windows 的 Get-NetxDataEnvMap 子目录一致)、backups/、logs/ |
netx:netx 0750,.env 0600 |
/etc/systemd/system/netx.service、netx-update.{service,timer} |
服务单元 |
--prefix 和 --data 都可以改。始终只保留最近 2 个 release 目录,用于回滚。
2. 任务清单
L1 构建:PostgreSQL 16 可移植二进制(glibc 2.17)
- 新文件
packaging/linux/build_pg.sh <arch>:在quay.io/pypa/manylinux2014_<arch>容器里从源码编译 PostgreSQL 16.x,和 Windows 用同一个小版本(现在是 16.4,见packaging/cache/pgsql-16.4-1)。./configure --prefix=/pgsql --without-readline --without-icu --with-zlib --without-openssl LDFLAGS='-Wl,-rpath,$ORIGIN/../lib'- 把
libz.so.1复制进pgsql/lib/,避免最小系统里没有 zlib。 - 只保留运行需要的二进制:
postgres pg_ctl initdb psql pg_dump pg_dumpall pg_restore pg_isready(与 build_release.ps1:163 一致),外加lib/、share/。 - 打包成
pg-16.x-linux-<arch>.tar.gz;CI 按版本+架构缓存,本地放packaging/cache/。
- 版本号和下载校验值统一写在
packaging/linux/versions.env(PG 源码 URL+sha256,python-build-standalone URL+sha256)。
验收:在 centos:7 容器里,pgsql/bin/initdb / pg_ctl start / psql -c 'select version()' 都能跑;L4 的检查脚本通过。
L2 构建:Python 运行时 + 依赖
packaging/linux/build_python.sh <arch>:下载 python-build-standalonecpython-3.12.x+<date>-<arch>-unknown-linux-gnu-install_only_stripped.tar.gz(pin 住版本并校验 sha256)。- 在 manylinux2014 容器里执行
python/bin/python3 -m pip install --only-binary=:all: -r requirements.txt。容器的 glibc 是 2.17,pip 只会选兼容的 wheel;任何依赖要编译或要求更高的 glibc,构建就会直接失败。 - 删掉
test/、idlelib/、tkinter/、ensurepip/,再执行compileall -q,这样/opt只读也不会在运行时写 pyc。 - 不用 venv(standalone Python 本身可以搬动,venv 的软链和绝对路径反而碍事)。所有启动都用
python/bin/python3 -m …,不依赖bin/下脚本的 shebang。
验收:在 centos:7 和 ubuntu:24.04 里,python/bin/python3 -c "import netx_api.main, psycopg, cryptography, netmiko, bcrypt, psutil" 都成功。
L3 构建:打包脚本 packaging/linux/build_linux_release.sh
- 参数:
--version(默认读 pyproject)、--web-dist <path>、--skip-web-build。 - 流程:先检查 web/dist(没有就执行
npm ci && npm run build:release),调用 L1/L2,拷贝程序文件(清单与 build_release.ps1:88-93 一致),写version.json,执行tar czf并生成.sha256,最后调用 L4 检查。 - 依赖 docker(manylinux 容器)。本机是 Windows,所以本地构建要在 WSL2 里的 Ubuntu+docker 上做,或者用一台 Linux 构建机。CI 是主要的构建途径。
- 文件:
packaging/linux/README.md,写清楚构建、安装、运维、支持范围。同时把 packaging/README.md 第 3 行"Linux is unchanged"改成指向 Linux 发行版的说明。
L4 兼容性闸门 packaging/linux/check_bundle.sh
- 用
objdump -T扫描包里所有 ELF(python/、site-packages/**/*.so、pgsql/),引用的GLIBC_x.y最高不能超过 2.17。 - 用
readelf -d检查 NEEDED:除了libc/libm/libdl/libpthread/librt/libutil/libcrypt/ld-linux*/libgcc_s/libstdc++以外,都必须能在包内找到。 - 不通过就让构建失败。
L5 install.sh(安装 / 更新 / 重装)
只依赖 bash、coreutils、tar、gzip、useradd(或 adduser)。凡是复杂逻辑(改 .env、检查端口、URL 编码、生成密钥)都交给包里自带的 Python(packaging/linux/netx_admin.py,只用标准库),不依赖 curl、ss、openssl 这些。
参数(交互模式下会逐项提问;中文还是英文按 $LANG 判断;--yes 为非交互):
--mode install|update|reinstall # 已有安装时默认 update
--prefix /opt/netx --data /var/lib/netx
--db bundled|external
--pg-port 15432 # 内置 PG 端口(需求 4)
--pg-password <pw> # 不填则自动生成
--db-url postgresql+psycopg://… # external 用;也可以拆成 --db-host/--db-port/--db-user/--db-password/--db-name
--bind 0.0.0.0 --port 8890 # Linux 一般是无桌面的服务器,默认绑 0.0.0.0(Windows 是 127.0.0.1)
--credential-key-file <path> # 复用别的安装的 NE 密码加密密钥(对应 Windows 的 /CredentialSecretKey)
--no-service # 不注册 systemd 服务
--auto-update # 打开每日自动更新(默认关闭,与 Windows 一致)
--open-firewall # 有 firewall-cmd / ufw 时放行 --port;默认只打印提示
--no-backup # 更新前不做 pg_dump
预检(失败时给出中英文提示并退出,不留下半装的状态):
- 必须是 root;
- 架构和包一致;
getconf GNU_LIBC_VERSION≥ 2.17;检测到 musl 就拒绝; - 磁盘空间够(≥ 1 GB);
- 用自带 Python 检查
--port、--pg-port是否空闲(被占用时交互模式重新问,--yes模式直接报port_in_use); - external 模式用
psql SELECT 1测试连接(对应 Windows 的行为)。
首次安装的步骤:
- 创建系统用户
netx(useradd -r -s /sbin/nologin -d /var/lib/netx;没有 useradd 就退到adduser -S); - 解压到
releases/<ver>,切换current软链,建立netxctl链接; - 创建数据目录、写
.env(键与 Windows setup_first_run.ps1 一致:NETX_DB_MODE、NETX_HOST、NETX_PORT、NETX_UI_DIST_DIR、NETX_BUNDLED_PG_PORT、NETX_DATABASE_URL、Get-NetxDataEnvMap里的路径、凭据密钥); - 内置 PG:用
netx用户执行initdb -U postgres -A scram-sha-256 -E UTF8 --locale=C,在postgresql.conf写入port = <pg-port>和listen_addresses = '127.0.0.1',pg_hba只允许本机,然后创建netx角色和库(照搬 Windows 的 bootstrap SQL); - 如果系统有 SELinux 并且有
restorecon,执行restorecon -R <prefix>; - 有 systemd(
[ -d /run/systemd/system ])就安装netx.service并enable --now;没有就执行netxctl start; - 等待
/health返回正常(最多 90 秒),最后打印访问地址、首次登录说明和防火墙提示。
更新(--mode update,也是在线更新最后调用的入口):
- 解压到
releases/<newver>(版本已存在就覆盖); - 内置 PG 时做
pg_dump -Fc到backups/pre-<newver>-<ts>.dump,只保留最近 3 份; - 停服务,切换
current软链,启动,等待/health; - 如果没有起来:软链切回旧版本再启动,返回非 0,并提示备份文件的位置(数据库可能已经被 alembic 迁移过,所以只回滚程序,数据库靠这个 dump 恢复);
- 成功后清理旧 release,只保留 2 个;
- 不会改动
.env和 PG 端口;systemd 单元按新版本的模板重新生成。
--mode reinstall:重新走数据库配置,但不删除 /var/lib/netx(和 Windows 一样)。
L6 netxctl 管理命令
bash 写的薄入口,复杂的子命令转交 netx_admin.py。以 root 调用时,需要降权的操作用 runuser -u netx --(没有 runuser 就用 su -s /bin/sh netx -c)。
netxctl run # 前台监督进程(systemd 的 ExecStart 就是它)
netxctl start|stop|restart|status
netxctl logs [-f] [api|worker|pg]
netxctl set-pg-port <port> # 需求 4
netxctl set-port <port> / set-bind <host>
netxctl reconfigure-db # 等于 install.sh --mode reinstall 里的数据库部分
netxctl check-update / update [--yes] [--file <tar.gz>]
netxctl auto-update on|off
netxctl backup [<file>] # pg_dump + data/auth 下的密钥,打成一个 tar.gz
netxctl uninstall [--purge] # --purge 删除数据,需要输入 "DELETE" 确认
netxctl version
netxctl run(前台监督进程,对应 Windows 的 service_run.ps1):
- 加载
/var/lib/netx/.env,并强制覆盖为数据目录下的路径;设置PYTHONPATH=current、NETX_UI_DIST_DIR; - 内置模式先执行
pg_ctl -D pgdata -w start。启动前检查pgdata/PG_VERSION的主版本与version.json里的pg_major是否一致,不一致就报pg_major_mismatch,不擅自升级数据; - 启动 API(
-m netx_api.main)、worker(-m netx_api.worker)和 biz_state workers,副本数和参数照搬 start_netx.sh:122-160,NETX_RUN_INLINE_SCHEDULERS=false; trap TERM INT:先停所有子进程,再pg_ctl stop -m fast;- 用
wait -n等待:任何一个子进程退出就整体退出并返回非 0,由 systemdRestart=on-failure重启; - 日志写到
/var/lib/netx/logs/*.log,并安装/etc/logrotate.d/netx(copytruncate,7 份)。
没有 systemd 时:netxctl start 执行 nohup netxctl run &,pid 写到 data/runtime/netx.pid;stop 发 TERM,30 秒后还没退出就 KILL。
netx.service:
[Unit]
Description=NetX
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=netx
Group=netx
ExecStart=/opt/netx/current/packaging/linux/netxctl run
KillMode=mixed
TimeoutStopSec=60
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
L7a API/Web 端口可配置
install.sh:支持--port和--bind;交互模式下默认 8890 / 0.0.0.0,会检查端口是否被占用(自带 Python 的 socket 检测),被占用时重新问,--yes模式直接报port_in_use。netxctl set-port <port>/set-bind <host>:校验 → 停服务 → 改.env里的NETX_PORT/NETX_HOST→ 启动 → 等/health;失败就恢复.env并重新启动。改完后打印 firewalld/ufw 的放行命令。- 重装(
--mode reinstall)时,默认值取当前配置,并允许沿用自己正在占用的端口。
L7 内置 PG 端口可配置(Linux 部分)
install.sh:支持--pg-port;交互模式默认值 15432,会检查是否被占用,被占用时重新问。netxctl set-pg-port <port>:校验范围在 1024–65535 且端口空闲 → 停服务 → 改postgresql.conf里的port→ 改.env里的NETX_BUNDLED_PG_PORT,并把NETX_DATABASE_URL中的端口一并改掉(只换 host:port 部分,密码保持原样)→ 启动 → 等/health;失败就把三处改回去。- external 模式下执行这个命令会报错,提示改用
reconfigure-db。
L8 在线更新 packaging/linux/netx_update.py(只用标准库)
- 更新源和配置项与 Windows check_update.ps1 完全一致:
NETX_UPDATE_SOURCES(默认github,forgejo)、NETX_UPDATE_GITHUB_REPO、NETX_UPDATE_FORGEJO_URL、NETX_UPDATE_URL(manifest)、NETX_UPDATE_TOKEN;会探测所有源,选最新的那个;版本相同时优先 GitHub;token 只发给非 GitHub 的主机。 - 资产匹配规则:
^netx-(?P<ver>[\d.]+)-linux-<arch>\.tar\.gz$;sha256 优先取<asset>.sha256资产,其次取 manifest 里的值,两者都没有就拒绝更新。 - 代理:遵循
HTTPS_PROXY/NO_PROXY(urllib 默认就支持)。 - 流程:下载到
/var/lib/netx/updates/→ 校验 → 解压到临时目录 → 执行新包里的install.sh --mode update --yes→ 清理临时文件。 netxctl update --file <tar.gz>:离线升级,跳过下载这一步。- 自动更新:
netx-update.timer(OnCalendar=*-*-* 03:30,RandomizedDelaySec=1h,Persistent=true),执行netxctl update --yes --quiet。没有 systemd 但有 crontab 时写到/etc/cron.d/netx-update;两者都没有就提示不支持。 - manifest.example.json 增加:
"linux": { "x86_64": {"url": "...netx-0.4.19-linux-x86_64.tar.gz", "sha256": "", "size": 0}, } - Windows 这边 check_update.ps1:115 只匹配
Setup-.*\.exe,同一个 Release 里出现 Linux 资产不受影响。需要加一条回归验证。
L9 CI:.github/workflows/release-linux.yml
- 触发:
v*tag(和 Windows 一致);同样校验 tag 必须等于 pyproject 里的版本。 - job
web:Node 20,执行npm ci && npm run build:release,上传web/dist作为 artifact。 - job
build:ubuntu-latest+ manylinux2014_x86_64 容器- 步骤:缓存/构建 PG(L1),执行 L2、L3、L4,上传 tar.gz 和 sha256 artifact。
- job
smoke(必须通过才发布),x86_64 在下面这些镜像里各跑一遍(docker run --network none,同时验证离线安装):centos:7、rockylinux:9、ubuntu:18.04、ubuntu:24.04、debian:12、opensuse/leap:15、openeuler/openeuler:22.03。 每个镜像执行packaging/linux/smoke_test.sh:install.sh --yes --no-service --db bundled --pg-port 25432/health返回 200;用默认管理员登录拿到 tokennetxctl set-pg-port 25433,之后/health仍然是 200install.sh --mode update --yes(用同一个包模拟升级):backups/里多出一份 dump,数据还在(登录前先建一条测试数据)netxctl stop,检查没有残留进程netxctl uninstall --purge(用--yes跳过确认)
- job
release:用softprops/action-gh-release@v2把 4 个文件上传到同一个 tag 的 Release(不要设generate_release_notes,release notes 由 Windows 的 workflow 生成,避免互相覆盖)。
L10 发布脚本
- publish_forgejo_release.ps1:
packaging/release/下如果有netx-<ver>-linux-*.tar.gz(.sha256),就一起上传;Release 说明里加上 Linux 安装方式。 - publish_release.ps1:GitHub Release 说明模板(第 107 行附近)加一段 Linux 的说明。
- 本地发布流程(写进
packaging/linux/README.md):CI 发布 tag 后,用gh release download vX -p 'netx-*-linux-*'把 Linux 包取回packaging/release/,再执行publish_forgejo_release.ps1同步到 Forgejo。
W1 Windows:Setup 数据库页增加内置 PG 端口
- netx.iss:在
RbBundled(约第 762 行)下面加一个"端口"输入框EdBundledPort,默认 15432,只有选内置时才可用(DbModeClick,第 469 行)。翻页时校验它是 1024–65535 的数字,并且端口没有被占用(可以复用Test-NetxTcpPortFree,或者在 Pascal 里用netstat -ano检查)。 - 静默安装参数
/BundledPgPort=25432(加在第 644 行附近解析DbMode的地方)。 - 执行
setup_first_run.ps1时传入-BundledPort(这个参数已经存在,见 setup_first_run.ps1:11)。 - Update 模式不显示、也不修改端口。
- 中英文文案(
english./chinesesimplified.)。
W2 Windows:安装后修改端口
- 新文件
packaging/set_pg_port.ps1 -Port <n>(会自动提权):做的事和 L7 一样:校验 → 通过runtime_ctl.ps1停服务 → 改postgresql.conf、.env里的NETX_BUNDLED_PG_PORT和NETX_DATABASE_URL的端口 → 启动 → 等/health,失败就回滚。 - 托盘的"设置"里加一项"内置数据库端口…",调用这个脚本。
- build_release.ps1:121 的拷贝清单里加上
set_pg_port.ps1。
3. 验收清单
| ID | 场景 | 通过标准 |
|---|---|---|
| A1 | CI 构建 | release-linux.yml 在测试 tag 上完整跑通;两个架构的 tar.gz 都 ≤ 150 MB;L4 检查通过 |
| A2 | 离线安装矩阵 | L9 的 smoke 在全部镜像里通过,并且都是 --network none |
| A3 | 真机 + systemd | 在一台真实的 Linux 服务器(例如 10.0.0.131 或一台 CentOS 7 / 麒麟虚拟机)上交互式安装:开机自启;systemctl restart netx 正常;kill -9 API 进程后 5 秒内被拉起;浏览器能打开 http://<ip>:8890/ 并登录 |
| A4 | PG 端口 | 安装时 --pg-port 25432 生效(ss -lnt 只看到 127.0.0.1:25432);netxctl set-pg-port 25433 后服务正常;改成一个已被占用的端口会被拒绝,原配置不变 |
| A4b | API 端口 | 安装时 --port 18890 生效;netxctl set-port 18891 后新端口能访问、旧端口不再监听;设成一个已被占用的端口会被拒绝 |
| A5 | 在线更新 | 先装 vN,在 Release 上挂 vN+1,执行 netxctl check-update 能看到新版本;netxctl update --yes 成功,current 指向新版本,数据还在,backups/ 里有 dump |
| A6 | 更新回滚 | 构造一个启动就失败的包(比如删掉 netx_api/main.py),netxctl update --file 之后 current 自动切回旧版本,服务正常,命令返回非 0 |
| A7 | 校验失败 | 篡改 .sha256 后执行 update,结果是拒绝更新,并且不会解压 |
| A8 | 自动更新 | netxctl auto-update on 后 systemctl list-timers 里能看到 netx-update.timer;off 后消失 |
| A9 | external 模式 | --db external --db-url … 连接到外部 PG 能正常工作;set-pg-port 会给出明确的报错 |
| A10 | 卸载 | netxctl uninstall 保留 /var/lib/netx;--purge 加确认后全部清掉;systemd 单元和 netxctl 链接都被删除 |
| A11 | Windows 端口 | Setup 选内置并把端口填成 25432:装好后 .env 和 postgresql.conf 一致,服务正常;静默 /BundledPgPort=25432 一样生效;Update 模式下端口不变;托盘改端口成功 |
| A12 | Windows 回归 | 现有的 Windows 沙箱离线验证(静默安装、启动、登录、停止、服务模式)全部通过;同一个 Release 里有 Linux 资产时,Windows 的 check_update.ps1 仍然只选 Setup.exe |
4. 建议的实现顺序
- L1 → L2 → L4(先把"能在 CentOS 7 上运行的运行时"做出来,这是风险最大的部分)
- L6
netxctl run/start/stop → L5 install.sh → L7 - L3 + L9(CI + smoke 矩阵)
- L8 在线更新 + L10 发布脚本
- W1、W2(Windows 端口,相对独立,可以并行做)
5. 风险与说明
- PG 大版本:固定为 16。以后要升到 17 需要单独做
pg_upgrade流程;第一期只检测、不处理(pg_major_mismatch)。 - API 默认绑 0.0.0.0:Linux 服务器一般通过远程访问,所以默认对外。安装完成后会醒目提示修改默认管理员密码,并提醒放通防火墙端口。
- 本地构建:本机目前没有 docker,WSL 里也没有装发行版。要在本地构建 Linux 包,需要
wsl --install -d Ubuntu再装 docker,或者在一台 Linux 构建机上执行。日常发布以 CI 为主。