feat(update): update the backend like nodes via quadlet restart [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-image Pipeline was successful

This commit is contained in:
justme committed 2026-10-18 00:36:38 +08:00
1 parent 34190f5a04
commit 68c7113adf
23 files changed
+309 -357

No files matched your search

+13 -29
View File
@@ -56,7 +56,7 @@ EOF
podman compose up -d # Docker 用 docker compose
```
**更新**:先备份数据卷,再 `podman compose pull && podman compose up -d`。新版启动时自动迁移数据库,但**旧版不认比自己新的数据库**:想回滚要先恢复备份,只改 `MOSHIRO_VERSION` 不够。Backend 能指挥所有节点,更新它尽量用宿主机更新器(见下文「Backend 签名更新」,清单有发布签名,更新前自动备份,不健康自动回滚),或者在 `.env` 里把 `MOSHIRO_VERSION` 固定到经过核对的版本或 digest。
**更新**:先备份数据卷,再 `podman compose pull && podman compose up -d`。新版启动时自动迁移数据库,但**旧版不认比自己新的数据库**:想回滚要先恢复备份,只改 `MOSHIRO_VERSION` 不够。Backend 能指挥所有节点,更新它尽量用 Quadlet(`packaging/quadlet/moshiro-backend.container`,见下文「Backend 的更新」:管理端确认,更新前自动备份数据库),或者在 `.env` 里把 `MOSHIRO_VERSION` 固定到经过核对的版本或 digest。
**首次认领(没有默认管理员)**:面板里还没有任何用户时,打开 `/admin/` 会进入认领向导:
1. 填认领码(首次启动时随机生成并写进后端日志,`journalctl -u moshiro-backend` 或 `docker logs`;也可以用 `MoShiro__ClaimCode` / `MOSHIRO_CLAIM_CODE` 指定)、管理员的用户名和密码;
@@ -258,19 +258,12 @@ f=$(mktemp) && curl -fsSL 'https://panel.example.com/install' -o "$f" && echo "<
脚本是静态的(`packaging/install/install.sh` 和 `install.ps1`,嵌入 Backend,不替换任何值),所以它的 SHA-256 在一个版本内固定;角色、面板地址、代码和属主走环境变量。环境变量 `MOSHIRO_NAME`、`MOSHIRO_ENGINE=docker|podman`、`MOSHIRO_DRY_RUN=1`(只打印 run 命令);Podman 会顺便启用 `podman-restart`。
**用脚本部署和更新(Backend;Agent 和 Probe 请用 Quadlet)**:`packaging/container/deploy-node.sh` 可以重复运行,每次都会拉取镜像并固定到 digest、把配置迁移到当前版本需要的样子(例如旧的 `listen.bind` 为 `0.0.0.0` 的 Agent 改成 `::`,这样 IPv6 线路才能建立)、重装单元和更新器,然后重启节点;已有的身份不会动,更新策略沿用上次的。
**用脚本部署和更新(Agent、Probe;推荐用 Quadlet,Backend 只有 Quadlet 和 compose)**:`packaging/container/deploy-node.sh` 可以重复运行,每次都会拉取镜像并固定到 digest、把配置迁移到当前版本需要的样子(例如旧的 `listen.bind` 为 `0.0.0.0` 的 Agent 改成 `::`,这样 IPv6 线路才能建立)、重装单元和更新器,然后重启节点;已有的身份不会动,更新策略沿用上次的。
```sh
# 首次部署(需要 root;脚本和 packaging/ 里的 systemd/、update/ 放在一起)
sudo sh deploy-node.sh agent --image-repo registry.oeo.one/moshiro/moshiro-agent --server https://<backend> --join <token> --name <name>
sudo sh deploy-node.sh probe --image-repo registry.oeo.one/moshiro/moshiro-probe --server https://<backend> --join <probe 令牌> --name <name>
# Backend(只支持 Podman,更新策略固定为管理端确认;--port 默认 29181,环境文件写在 /etc/moshiro/backend.env,已有的不覆盖)
sudo sh deploy-node.sh backend --channel dev --public-url https://<面板域名>
# 没有默认管理员:首次打开面板按页面引导认领(认领码写在后端日志里,或用 --env MoShiro__ClaimCode=… 自己指定)
# 其他环境变量用 --env 原样写入(只在首次创建 backend.env 时生效):本地开发前端要连这个面板时加
# --env MoShiro__CorsOrigins=http://localhost:5173,http://localhost:5174 --env MoShiro__SecureCookies=false
# 面板前面有反向代理(Caddy、frp、Cloudflare)时,登录限流和审计看到的是代理的地址:把代理的地址写进可信代理,才会采信转发头里的客户端地址
# --env MoShiro__TrustedProxies=127.0.0.1,172.16.0.0/12 (头默认 X-Forwarded-For,可用 MoShiro__TrustedProxyHeader 改成 CF-Connecting-IP)
# 之后更新:不带参数(旧版 Probe 的 probe.json 没有 fleet 段,加上 --server --join --name 重新运行即可,旧文件备份为 probe.json.old)
sudo sh deploy-node.sh agent
```
@@ -309,7 +302,7 @@ Agent 的 Quadlet 单元已经包含以下只读挂载和环境变量:
Probe 也上报同样的汇总数字(`probe.json` 的 `reporting.host_stats` 默认开,设为 `false` 就一个字也不上报,同时可以去掉 `/proc`、`/sys`、`/` 这三个挂载),所以 Probe 的 Quadlet 单元和 Agent 一样挂载它们并设置 `HOST_*` 环境变量。Docker Desktop(macOS、Windows)上读到的是 Docker 的虚拟机,不是宿主系统本身;Windows 上直接运行的 Probe 和 Agent 用系统调用读 CPU、内存和物理网卡的流量(不读网卡名和地址);macOS 目前只有磁盘和运行时长。
## Podman(Quadlet)节点的更新(Agent、Probe,推荐)
## Podman(Quadlet)的更新(Agent、Probe、Backend,推荐)
节点跑成一个 Podman Quadlet 单元(`packaging/quadlet/moshiro-agent.container`、`moshiro-probe.container`,Podman 4.6 及以上),更新是**销毁容器、重新拉最新镜像再启动**:节点自己不下载、不替换任何东西,只是有序退出;Quadlet 生成的 systemd 服务用 `podman run --pull always` 启动,所以被拉起时先从镜像仓库拉最新的镜像。**默认自动更新**:面板发现新版本后,节点运行满 10 分钟(防止重启循环)就自己退出重启;想改成在管理端点「更新」才更新,在 env 文件里写 `MOSHIRO_UPDATE_POLICY=manual`。
@@ -328,9 +321,9 @@ CI 推镜像到 registry ─▶ 面板收到 CI 签名的清单 ─▶ 转发给
- **回滚**:新镜像起不来时 systemd 一直重试;把模板里的标签改回旧版本再 `systemctl --user restart` 即可。
- 测试:`packaging/quadlet` 里的模板用 `/usr/libexec/podman/quadlet -dryrun -user` 检查过(Podman 5.4);节点一侧的逻辑见 `RestartUpdaterTests`。
## 宿主机更新器(Backend、一行脚本装的节点)
## 宿主机更新器(一行脚本装的节点)
节点的更新走上面的 Quadlet。下面的宿主机更新器现在只服务 Backend 和一行安装脚本装的节点,Backend 改成 Quadlet 之后会一并移除。
节点和 Backend 的更新都走上面的 Quadlet。下面的宿主机更新器现在只服务一行安装脚本装的节点(它们没有 systemd 的 Pull=always 可用)。
更新器支持 rootless Podman 用户服务、rootful Podman 和 Docker 系统服务。配置、身份和更新请求通过命名 volume 传递,不需要指定宿主数据目录。短时辅助容器读取请求、写入独立状态 volume;辅助镜像必须已在本机拉取,更新器不会自动拉取辅助镜像。配置、公钥和已安装版本记录留在容器外。
@@ -378,24 +371,15 @@ CI 给每个镜像的 digest 签名之后,除了(面板在线时)登记给
- **发版**:打 `vMAJOR.MINOR.PATCH` tag 时,节点镜像(Agent、Probe 的多架构镜像)也和 Backend 一样签清单,放到 `moshiro/…-manifest:release`。
- **压缩包的校验和也签名**:Release 里除了每个压缩包的 `.sha256`,还有汇总的 `SHA256SUMS` 和 `SHA256SUMS.sig`(用发布私钥签,base64 的 DER ECDSA P-256,和清单同一把密钥)。验证:`base64 -d SHA256SUMS.sig > s.bin && openssl dgst -sha256 -verify packaging/update/release-key.pub.pem -signature s.bin SHA256SUMS`,通过之后再 `sha256sum -c SHA256SUMS`。macOS 的 Developer ID 签名和公证、Windows 的 Authenticode 需要苹果和微软的证书,目前没有做。
- **首次部署**:`deploy-node.sh` 默认就走这条路,不需要面板,也不用手动指定镜像(Backend 第一次部署时本来就没有面板)。`--tag` 指定标签时不验签并有警告;`--image` 直接用你给的引用。
- **别人自己部署后端,什么都不用改**:仓库对匿名读开放(`moshiro/*` 和各 `*-manifest` 仓库),然后 `sudo sh deploy-node.sh backend --channel release --public-url https://<自己的域名>`,只是配置,不改代码、不需要自己的 CI;passkey 绑定在自己的域名上。自己 fork 并自建镜像的人要换自己的发布公钥和仓库地址(`--image-repo`)并重新构建,这是 fork 的正常代价。
- **别人自己部署后端,什么都不用改**:仓库对匿名读开放(`moshiro/*` 和各 `*-manifest` 仓库),然后按 `packaging/quadlet/moshiro-backend.container` 的说明装 Backend(`MoShiro__PublicUrl` 写自己的域名),只是配置,不改代码、不需要自己的 CI;passkey 绑定在自己的域名上。自己 fork 并自建镜像的人要换自己的发布公钥和仓库地址(`--image-repo`)并重新构建,这是 fork 的正常代价。
- 测试:`bash packaging/update/tests/feed-test.sh`(假的 podman + 真的 openssl,覆盖篡改、别的密钥、旧 seq、换钥、仓库不可用回退、首次部署的验签)。
## Backend 签名更新
## Backend 的更新
Backend 也会收到 CI 发布的签名清单,但只提示管理端,不会自动重启。管理员确认精确的清单序号后,宿主更新器才会备份 Backend 数据 volume、按清单 digest 拉取镜像、重启服务并检查 `/healthz`;失败时恢复旧镜像和数据库备份。确认和发布通过同一个锁串行处理,避免确认过期版本。
Backend 和节点是同一种更新:`packaging/quadlet/moshiro-backend.container`(Quadlet,`Pull=always`、`Restart=always`、`Environment=MOSHIRO_UPDATE=restart`)。CI 推镜像并把 CI 签名的清单登记到面板,管理端显示「Backend 有新版本」,管理员点「更新并重启」(`POST /api/admin/backend/update`,带当时登记的清单 `seq`,清单变了就要重新确认);Backend 先把数据库备份到数据卷的 `backups/pre-update-<时间>-<seq>.db`(`VACUUM INTO`,保留最近 3 份,备份失败就不更新),再有序退出,systemd 重启单元并拉最新镜像。
`MOSHIRO_UPDATE_CHANNEL=auto` 时,`MOSHIRO_IMAGE_NAMESPACE=moshiro-dev` 选择 `dev`,`moshiro` 选择 `release`。CI 发布流水线使用对应 channel 签名并登记到 Backend webhook。旧版 Backend 不识别 Backend 清单,因此首次升级需按常规部署流程引导一次;该期间 CI 对这类旧版面板的明确“不认识 backend 组件”响应会提示引导信息,其他 webhook 错误仍会令发布失败。
更新器以手动策略运行,并通过独立状态 volume 将状态只读挂载回 Backend:
```sh
sudo packaging/update/install-updater.sh backend --engine podman \
--volume moshiro-backend-data --status-volume moshiro-backend-status \
--helper-image registry.oeo.one/moshiro-dev/moshiro-backend:<version> \
--image-repo registry.oeo.one/moshiro-dev/moshiro-backend \
--unit moshiro-backend.service --policy manual --channel dev \
--health-url http://127.0.0.1:29181/healthz
```
Backend 服务需要将 `moshiro-backend-status` 挂载到 `/data/update-status:ro`,并将镜像引用文件 `/etc/moshiro-update/backend.image` 作为 `IMAGE_FILE` 提供给 systemd 单元。release 实例使用 `--channel release` 及 `registry.oeo.one/moshiro` 仓库。更新请求由管理端确认后写入状态接口,不要启用全局 Podman 自动更新 timer。
- **不自动重启**:Backend 指挥所有节点,重启会让它们全部断一两分钟,所以只提示,不像节点默认那样自动退出。
- **没有 `MOSHIRO_UPDATE=restart` 就不能从管理端更新**(compose、一行脚本装的 Backend 没有重新拉取的机制),管理端会提示;这种部署继续用 `compose pull && compose up -d`。
- **通道**:`MoShiro__UpdateChannel` 为 `auto` 时,镜像命名空间 `moshiro-dev` 选 `dev`,`moshiro` 选 `release`;没写命名空间就按当前运行的构建 tag 判断。
- **回滚**:新版启动时会迁移数据库,旧版不认比自己新的库。要回旧版:停服务,把 `backups/pre-update-*.db` 复制成数据卷里的 `moshiro.db`(删掉旧的 `-wal`、`-shm`),把单元里的标签固定成旧版本号再启动。新版起不来时 systemd 会一直重试同一个镜像,没有自动回滚,也没有 `/healthz` 的探针(镜像里没有 curl)。
- **首次升级**:还没有这个机制的旧版 Backend 不认 `MOSHIRO_UPDATE`,第一次要手动 `systemctl --user restart moshiro-backend`(Pull=always 拉到新镜像),之后就能从管理端更新。