14 KiB
平台完整升级包
CI 从同次构建的不可变镜像摘要导出完整 tar.gz,包含源码中的运行脚本和模板、前后端编译产物、锁文件和已经安装的生产 node_modules。不包含 Node 可执行文件、用户数据、.env、Git 历史或 npm 缓存。保留现有 Node 和用户脚本依赖。
在线升级
package.json 中 qinglongUpgradeFormat: 2 表示使用完整包。旧版升级器仍可通过原归档路径完成首次迁移;完整包版本不会在平台包缺失、损坏或不兼容时退回设备端 npm 安装。仅当完整包已通过校验、依赖加载或后端预启动明确报告依赖错误时,在线升级会按同一冻结锁文件修复一次生产依赖,然后再次预启动;第二次失败即停止,保留正在运行的旧服务。
面板下载任务与本地离线入口共用 platformBundle.cjs:平台匹配 → 归档安全检查 → 全文件/链接/权限和锁文件校验 → SQLite/Sequelize/cron-parser/gRPC/undici 加载及 SQLite 内存库读写 → 隔离后端预启动 → 发布就绪指针。预启动使用现有 Node、临时数据目录和 loopback 随机端口,运行真实数据库建表/迁移和事务读写、gRPC 服务,以及与正常启动共用的 HTTP 中间件、路由和 WebSocket 初始化,再通过 /api/health 确认双服务与目标版本、提交和后端摘要;不运行任务、订阅、通知、用户启动钩子和系统依赖安装。临时库不是用户数据库副本,因此实际应用后的健康检查和回退仍不可省略。点击重启时,接口只检查就绪记录并提交后台任务;完整文件与依赖复验由独立工作进程执行,复验通过后才停止旧服务,避免小设备的预检耗时超过 HTTP 请求期限。离线升级同步面板任务状态,并与在线任务互斥;中断准备时保留之前的可用就绪包。原有服务停止、文件备份、启动健康检查及失败恢复仍由共享升级引擎处理。
正式升级包跟随 master 同一次版本镜像构建,直接使用该次 Alpine/Debian 镜像的不可变 digest;develop 的普通提交和 Git tag 工作流不生成或发布完整包。开发包需要手动运行现有镜像工作流:选择 develop,并勾选 build_development_bundles(默认为 false,不能与 static_sync_only 同时启用)。也可执行:
gh workflow run build-docker-image.yml --ref develop -f build_development_bundles=true
手动运行会构建该次 develop 提交的两种镜像,并从输出的固定 digest 导出完整包,更新共用的 upgrade-develop/latest.json。develop 和 debian-dev 镜像都使用该索引,按实际 Node 架构和 musl/glibc 选择包,无需设置测试专用下载源。开发批次位于 upgrade-build-develop-v<version>-<run-id>-<run-attempt>,不会改写正式版本或 upgrade-master。同一开发渠道的手动发布串行执行,普通 develop CI 不受该队列影响。
开发和正式包都必须在 13 个平台完成归档、解包后复验及依赖试运行,并且全部上传资产的大小和 SHA256 复验通过后,才更新各自索引。ARMv6 glibc 继续使用同次 Debian 应用源码及专用 ARM1176 构建和验证环境。
版本入口为 GitHub prerelease upgrade-v<version> 下的 latest.json;在线正式渠道保留 upgrade-master/latest.json,兼容现有客户端。二者指向同一套完整包,指定版本与 latest 不重复生成产物。索引记录渠道、版本、源码提交、构建批次、准确字节数、SHA256,以及 upgrade-build-v<version>-<run-id>-<run-attempt> 下的固定下载 URL。相同版本的镜像 tag 重新发布时,完整包也重新生成,版本和正式渠道索引同步覆盖到新批次;同一提交重跑也允许产生新内容。已选中旧索引的下载继续使用旧批次资产,避免覆盖文件导致哈希不匹配。master 的镜像构建和完整包发布整条流水线串行执行,避免镜像 tag 与完整包被并发重发交错覆盖;旧工作流明确重跑时也允许同版本、同提交重新发布。过期源码提交不能推进对应渠道或版本索引;平台包上传失败时不切换索引。
手动离线安装和升级
从 upgrade-v<version> 的说明或 latest.json 中取得当前批次的下载链接,下载匹配设备的 .tar.gz 和独立 install-bundle.cjs;两者都来自同一个 upgrade-build-v<version>-<run-id>-<run-attempt> release。独立安装器已内置解析依赖,在目标设备正常情况无需 npm、pnpm 或重新安装 SQLite。离线准备和预启动默认不联网;依赖不符合要求时直接报错。可对照同批次 release 的 latest.json 核对归档 SHA256。
已有安装(默认应用并重启):
node install-bundle.cjs --root /ql --file /absolute/path/qinglong-<commit>-linux-arm64-musl.tar.gz
只准备,稍后手动重启:
node install-bundle.cjs --root /ql --file /absolute/path/package.tar.gz --download-only
ql reload system
仅在明确允许联网修复时添加 --repair-deps(安装和升级均支持),例如:
node install-bundle.cjs --root /ql --file /absolute/path/package.tar.gz --download-only --repair-deps
修复需要已有 pnpm 8.3.1 及必要编译工具。依赖在私有目录按 --prod --frozen-lockfile 重装,仅重建 SQLite;ARMv6 强制源码编译和 ARMv6 硬浮点指令,禁止下载 generic ARM 的 ARMv7 二进制。修复仅执行一次;不安装或替换 Node、不改锁文件、不改已校验源码和静态产物。应用前以原包校验清单和独立记录的修复依赖摘要复验。
修复继承调用环境中的标准 HTTP/HTTPS/ALL/NO_PROXY(含小写形式),其余 Node 预加载和编译参数仍隔离。修复安装或 SQLite 重建失败时,清理私有目录,保留当前服务、当前依赖及上一次就绪包;不会标记本次更新就绪。预启动或修复主进程退出时立即终止其进程组,防止持有输出管道的子进程继续写入已清理目录。
通过 Bash 或已选择的本地 CLI 执行上述重启时,也会把已准备的面板任务更新为完成或失败;启动成功必须同时匹配目标版本、提交和后端哈希,匹配失败则恢复旧文件和服务。
手动应用会以当前就绪指针为准建立新的应用任务,并清除之前的失败信息。因此,准备失败后应用保留的旧就绪包、此前任务已完成后再次应用,以及完整包安装中任务记录丢失或尚未建立时,仍会校验实际启动版本并同步面板状态。未支持完整包且没有任务记录的旧版保留原重载兼容路径。
重启中断后的停止新服务、恢复文件和启动旧服务不再使用已经取消的信号。若新服务无法停止,则保留备份,避免在仍运行的进程下覆盖文件;若文件已恢复但旧服务也无法启动,任务仍报告失败。Bash 和本地 CLI 启动后端时都移除离线包选择、修复许可和升级启动器 PID,避免后续在线更新误用旧离线包;用户配置及代理保持不变。回滚恢复应用文件和依赖,不把用户数据库恢复到历史快照;数据库迁移仍需保持向后兼容。
安装到空目录:
node install-bundle.cjs --root /opt/qinglong --file /absolute/path/package.tar.gz --install
QL_DIR=/opt/qinglong QL_DATA_DIR=/opt/qinglong/data bash /opt/qinglong/shell/start-simplify.sh
--install 拒绝非空目录,不覆盖既有配置。该包安装青龙及其 Node 生产依赖;首次启动仍需已有 Python、Bash、OpenSSL、PM2、nginx 等相应运行环境,不会在线安装或替换它们。手动启动脚本会在启动工作进程前完成首次 gRPC 证书准备,避免慢设备生成密钥耗尽工作进程的就绪期限;已有证书继续复用。服务启动和系统自启按原手动安装流程配置。
ARMv6 glibc 小设备(例如 Raspberry Pi 1/Zero 的 Raspbian/Raspberry Pi OS 32 位系统)选择 linux-armv6-glibc.tar.gz,以同一安装器执行上述离线命令。该包要求 glibc >= 2.31、ARMv6 硬浮点 Node >= 20.18.1,不适用于 ARMv5、软浮点 Node 或旧版 glibc。安装和升级均不需要 Docker,也不会下载或替换 Node。
npm 面板包
@whyour/qinglong 仍通过 npm 安装生产依赖;它包含运行脚本、gRPC proto、升级引擎和完整前后端构建,不包含用户数据、Node、开发 CLI 或生产 node_modules。@whyour/qinglong-cli 是单独的远程管理包。
npm 会排除根目录的 pnpm-lock.yaml。主包的原生 npm pack/publish 在 prepack 中核对当前 Git 提交、干净工作区、锁文件及完整构建文件的哈希,通过后才将锁文件复制为 shell/upgrade-lock.yaml,安装时由仅使用 Node 内置模块的 postinstall 校验 SHA256 后恢复标准路径;源代码安装和 Docker 的最小依赖构建阶段继续使用原有锁文件。归档使用精确运行文件白名单,避免打入本地生成的预加载配置、缓存、测试和验收输出。静态构建中的 source map 与 gzip 文件也必须完整保留,否则清单复验会失败。
干净源码完成 pnpm build:front、pnpm build:back、pnpm build:info 后,运行 node scripts/verify-panel-package.cjs pack /tmp/qinglong-npm 生成并检查真实 npm 归档。CI 在一次性 Linux 容器中,以 Node 20.18.1 和 Node 24 安装同一个归档,使用真实生产依赖和已安装的 qinglong、ql 入口,验证启动、损坏下载保留旧服务、在线准备与重启、离线准备与 Bash 重启及账号、配置、环境变量、任务和脚本保留。测试还安装完整性摘要固定的已发布 @whyour/qinglong@2.23.0,在旧服务中建立真实数据,再用新 .tgz 执行 npm 覆盖安装,验证账号、配置、环境变量、任务和脚本保留。之后升级到同次 Debian 镜像不可变 digest 通过正式 Dockerfile.upgrade-bundle 导出的原始平台包,确认运行目录变为 pnpm 布局。该单平台验收包仅上传短期 CI artifact,不发布 Release,也不推进开发或正式更新索引。
服务集成测试只能在专用容器中运行:设置 QL_PANEL_PACKAGE_TEST_ISOLATED=1、指向隔离 PM2 的 QL_PANEL_TEST_PM2 和指向正式构建脚本输出目录的 QL_PANEL_TEST_BUNDLE_DIR,然后执行 node scripts/verify-panel-package.cjs verify /tmp/qinglong-npm /tmp/qinglong-npm-results。容器需已有 Bash、curl、OpenSSL、Python、jq、nginx 和 procps。旧正式包会重置 PATH,因此验收容器将 PM2 6.0.14 安装到 /usr/local/bin/pm2,并以此设置 QL_PANEL_TEST_PM2;隔离通过专属容器和独立 PM2_HOME 保证。测试创建独立的 npm prefix、HOME、PM2_HOME 和数据目录,结束后清理。
通过 npm 覆盖升级前,应先停止服务,执行新包安装,再启动服务。正常 npm 安装的 postinstall 会从当前路径对应的 npm 旧包临时目录中自动保留 .env,逐字节恢复为权限 0600 的普通文件,不需要手工备份和恢复;新目录中已有的 .env 不会被覆盖。只读取包名匹配的旧包,不扫描其他安装或备份目录;异常配置(如符号链接)会使安装失败,由 npm 回退旧包,避免静默丢失配置。此机制需要启用 npm 安装脚本,不能使用 --ignore-scripts。持久数据仍必须放在包目录以外的 QL_DATA_DIR。验收直接检查真实旧 npm 包覆盖安装后配置自动保留,并校验外置用户数据。在线和离线平台包升级仍由事务保留现有 .env。
master 发布任务等待上述测试通过,直接发布已验证的 .tgz,不重新构建或打包。develop 只生成测试归档与验收记录,不发布 npm。
平台兼容
- 最低 Node 为 20.18.1,Node-API 至少 6;安装器只检查,不替换 Node。
- 以 Node 实际运行架构、ARM 指令集和 libc 类型/版本选择包;不依据 Docker 架构别名猜测。
- glibc、musl 分包,并记录构建系统 libc 下限。系统库仍以目标设备加载测试作为最后检查。
- ARMv6 glibc 使用签名校验的 Raspbian bullseye(glibc 2.31)构建环境,以固定 SHA256 的 ARMv6 Node 20.18.1 头文件编译 SQLite,强制
-march=armv6 -mfpu=vfp -mfloat-abi=hard。CI 检查 ELF 的 ARMv6 和硬浮点标记,并在 QEMU ARM1176 下完成归档复验、SQLite 读写和空目录离线安装;验证辅助镜像按不可变摘要记录,不进入用户安装包。Debian ARMv7 镜像仅提供同次构建的应用文件,不复用其 Node、libc 或 SQLite 二进制。 - ARMv6 与 ARMv7 使用不同包名。ARMv6 在目标镜像中从源码编译 SQLite,禁止复用 generic
arm的 ARMv7 预编译件;构建需通过加载和内存库读写,失败则不发布渠道。 - ARMv6 的归档构建、在线更新工作进程及独立离线安装器使用
--jitless,避开 ARM1176 上 V8 的优化编译崩溃;保留现有 Node 二进制,正常面板进程的启动参数不变。ARMv6 的 gRPC 工作进程首次启动及重建最多等待 2 分钟;升级后的整体健康检查最多等待 5 分钟,覆盖慢设备按顺序启动数据库、调度与 HTTP 工作进程的耗时;其他平台仍为 60 秒,超时或版本校验失败仍执行回退。依赖加载与后端预启动使用现有 Node 的正常模式,使 Node 20.18.1 的内置 HTTP/WebAssembly 组件保持可用。预启动只有一个后端进程,按顺序初始化数据库、gRPC、HTTP,不启动 PM2/cluster;ARMv6 V8 堆上限 128 MiB、期限 5 分钟,其他平台为 192 MiB/60 秒。堆上限不是 RSS 总内存上限,系统仍需为现有服务和 OpenSSL 留出内存。依赖修复的包管理器及编译使用单并发、低网络并发和有界时长;ARMv6 包管理器采用--jitless、256 MiB 堆上限,并优先复用版本匹配的现有 Node 头文件与实际运行时编译配置;超时、中断会终止整个进程组并清理临时目录。 - tar 中允许 pnpm 的包内符号链接;拒绝绝对路径、越界路径、包外链接、硬链接、设备条目和经符号链接写入的条目。重启前再次校验依赖,暂存失败保留上一次可用的就绪包。
自托管镜像可设置 QL_UPGRADE_INDEX_URL 指向兼容索引;要求 HTTPS,测试时仅放行 loopback HTTP。索引和所有平台归档应部署到同一可访问源,避免依赖额外的 raw 域名。