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

+12 -64
View File
@@ -1,29 +1,25 @@
#!/bin/sh
# 部署或更新容器服务(Backend、Agent、Probe)。可以重复运行:每次都会拉取镜像、把配置迁移到当前版本需要的样子、
# 部署或更新容器服务(Agent、Probe;Backend 用 Quadlet,见 packaging/quadlet/moshiro-backend.container)。可以重复运行:每次都会拉取镜像、把配置迁移到当前版本需要的样子、
# 重新安装单元和更新器,然后重启节点;已有的身份和配置不会被覆盖。
#
# 首次部署 Agent:sudo sh deploy-node.sh agent --image-repo REPO --server https://panel --join jt_xxx --name hk-1
# 首次部署 Probe:sudo sh deploy-node.sh probe --image-repo REPO --server https://panel --join jt_xxx --name home-1
# 首次部署 Backend:sudo sh deploy-node.sh backend --channel dev [--public-url https://panel] [--port 29181] [--env KEY=VALUE]...
# 没有默认管理员:部署完打开面板,按页面引导自己创建管理员、注册 passkey、建立信任根;页面要的认领码在后端日志里(journalctl -u moshiro-backend)。
# 更新(配置迁移、单元和更新器、镜像):sudo sh deploy-node.sh agent (其余参数沿用上次的更新器配置)
# 旧版 Probe(probe.json 里没有 fleet 段):加上 --server --join --name 重新运行即可,旧文件备份为 probe.json.old
#
# Backend 只支持 Podman,更新策略固定为 manual(管理端确认),监听宿主网络的 --port。
# 需要 root、systemd,以及 Podman 或 Docker;脚本要和 packaging/ 里的 systemd/、update/ 放在一起。
# rootless Podman 见 docs/ops/deployment.md。
set -eu
usage() {
echo "usage: deploy-node.sh <agent|probe|backend> [--engine podman|docker] [--image-repo R] [--channel dev|release] [--tag T] [--image REF]" >&2
echo "usage: deploy-node.sh <agent|probe> [--engine podman|docker] [--image-repo R] [--channel dev|release] [--tag T] [--image REF]" >&2
echo " [--server URL --join TOKEN --name NAME] [--policy auto|manual] [--authfile F]" >&2
echo " backend: [--channel dev|release] [--port N] [--admin-user U --admin-password P:只给自动化用,正常不要] [--public-url URL] [--env KEY=VALUE]..." >&2
exit 2
}
ROLE="${1:-}"; shift || true
case "$ROLE" in agent|probe|backend) ;; *) usage ;; esac
ENGINE=""; REPO=""; TAG=""; IMAGE=""; SERVER=""; JOIN=""; NAME=""; POLICY=""; AUTHFILE=""; CHANNEL=""; PORT=""; ADMIN_USER=""; ADMIN_PASSWORD=""; PUBLIC_URL=""; EXTRA_ENV=""
case "$ROLE" in agent|probe) ;; *) usage ;; esac
ENGINE=""; REPO=""; TAG=""; IMAGE=""; SERVER=""; JOIN=""; NAME=""; POLICY=""; AUTHFILE=""; CHANNEL=""
while [ $# -gt 0 ]; do
case "$1" in
--engine) ENGINE="$2"; shift 2 ;;
@@ -36,12 +32,6 @@ while [ $# -gt 0 ]; do
--policy) POLICY="$2"; shift 2 ;;
--authfile) AUTHFILE="$2"; shift 2 ;;
--channel) CHANNEL="$2"; shift 2 ;;
--port) PORT="$2"; shift 2 ;;
--admin-user) ADMIN_USER="$2"; shift 2 ;;
--admin-password) ADMIN_PASSWORD="$2"; shift 2 ;;
--public-url) PUBLIC_URL="$2"; shift 2 ;;
--env) EXTRA_ENV="$EXTRA_ENV$2
"; shift 2 ;;
*) usage ;;
esac
done
@@ -59,13 +49,6 @@ conf_get() { [ -r "$CONF" ] && sed -n "s/^$1='\\(.*\\)'\$/\\1/p" "$CONF" | head
[ -n "$AUTHFILE" ] || AUTHFILE="$(conf_get AUTHFILE)"
[ -n "$ENGINE" ] || ENGINE="$(conf_get ENGINE)"
[ -n "$CHANNEL" ] || CHANNEL="$(conf_get CHANNEL)"
if [ "$ROLE" = backend ]; then
[ -n "$CHANNEL" ] || { echo "backend needs --channel dev or release" >&2; exit 2; }
case "$CHANNEL" in dev) NAMESPACE=moshiro-dev ;; release) NAMESPACE=moshiro ;; *) echo "--channel must be dev or release" >&2; exit 2 ;; esac
[ -n "$REPO" ] || REPO="registry.oeo.one/$NAMESPACE/moshiro-backend"
[ -n "$PORT" ] || PORT=29181
ENGINE=podman; POLICY=manual
fi
[ -n "$REPO" ] || REPO="registry.oeo.one/moshiro/moshiro-$ROLE"
[ -n "$POLICY" ] || POLICY=manual
if [ -z "$ENGINE" ]; then
@@ -78,14 +61,13 @@ case "$ENGINE" in
esac
V_CFG="moshiro-$ROLE-config"
V_STATUS="moshiro-$ROLE-status"
if [ "$ROLE" = backend ]; then UNIT=moshiro-backend.service; V_CFG=moshiro-backend-data; fi
for f in "$PACKAGING/systemd/$UNIT_SRC" "$PACKAGING/update/install-updater.sh" "$PACKAGING/update/moshiro-update.sh" "$PACKAGING/update/release-key.pub.pem"; do
[ -r "$f" ] || { echo "missing $f (keep deploy-node.sh next to the packaging/ directory)" >&2; exit 1; }
done
# 1. 镜像:固定到 digest,后面的单元、辅助容器都用同一个引用。
# 默认从仓库里的签名清单取(<仓库>-manifest:<通道>,CI 发布的),用随脚本带的发布公钥验签,按清单里的 digest 固定——
# 仓库和网络都不需要可信,也不需要面板在线(Backend 第一次部署时还没有面板)。
# 仓库和网络都不需要可信,也不需要面板在线。
# --tag 指定标签就不验签(拉那个标签再固定 digest);--image 直接用你给的引用。
if [ "$ENGINE" = docker ] && [ -n "$AUTHFILE" ]; then export DOCKER_CONFIG="$(dirname "$AUTHFILE")"; fi
pull_quiet() {
@@ -137,38 +119,14 @@ in_volume() {
"$ENGINE" run --rm -i --pull=never --network none --cap-drop ALL --security-opt no-new-privileges \
-v "$V_CFG:/etc/moshiro" --entrypoint /bin/sh "$IMAGE" -c "$1"
}
[ "$ROLE" = backend ] || in_volume 'mkdir -p /etc/moshiro/update' </dev/null
in_volume 'mkdir -p /etc/moshiro/update' </dev/null
# 3. 配置:身份由节点自己生成(Agent 和 Probe 一样),注册之后面板只认这把公钥
if [ "$ROLE" = backend ]; then
# Backend 的环境文件放在宿主机上(单元用 --env-file 读取);已有的不会被覆盖
install -d -m 755 /etc/moshiro
if [ ! -f /etc/moshiro/backend.env ]; then
(
umask 077
{
echo "MoShiro__DataDirectory=/data"
echo "MoShiro__UpdateChannel=$CHANNEL"
echo "MoShiro__ImageNamespace=$NAMESPACE"
# 没有默认管理员:第一个管理员由部署的人在首次打开的页面上自己填,认领码在后端日志里(--admin-password 只给自动化用)
if [ -n "$ADMIN_PASSWORD" ]; then
echo "MoShiro__InitialAdminUser=${ADMIN_USER:-admin}"
echo "MoShiro__InitialAdminPassword=$ADMIN_PASSWORD"
fi
[ -z "$PUBLIC_URL" ] || echo "MoShiro__PublicUrl=$PUBLIC_URL"
# 其他设置原样写入,例如 --env MoShiro__CorsOrigins=http://localhost:5173 --env MoShiro__SecureCookies=false
[ -z "$EXTRA_ENV" ] || printf '%s' "$EXTRA_ENV"
} > /etc/moshiro/backend.env
)
echo "config: created /etc/moshiro/backend.env"
fi
else
ID_DIR="/etc/moshiro/$ROLE-id"
if ! in_volume "test -f $ID_DIR/node.json" </dev/null; then
[ -n "$SERVER" ] && [ -n "$JOIN" ] && [ -n "$NAME" ] || { echo "first deployment needs --server, --join and --name" >&2; exit 1; }
"$ENGINE" run --rm --pull=never --network host -v "$V_CFG:/etc/moshiro" --entrypoint /usr/local/bin/fleet-enroll "$IMAGE" \
--server "$SERVER" --join "$JOIN" --name "$NAME" --identity-dir "$ID_DIR"
fi
ID_DIR="/etc/moshiro/$ROLE-id"
if ! in_volume "test -f $ID_DIR/node.json" </dev/null; then
[ -n "$SERVER" ] && [ -n "$JOIN" ] && [ -n "$NAME" ] || { echo "first deployment needs --server, --join and --name" >&2; exit 1; }
"$ENGINE" run --rm --pull=never --network host -v "$V_CFG:/etc/moshiro" --entrypoint /usr/local/bin/fleet-enroll "$IMAGE" \
--server "$SERVER" --join "$JOIN" --name "$NAME" --identity-dir "$ID_DIR"
fi
if [ "$ROLE" = agent ]; then
if ! in_volume 'test -f /etc/moshiro/agent.json' </dev/null; then
@@ -199,10 +157,8 @@ fi
install -d -m 755 "$CONF_DIR"
printf 'IMAGE=%s\n' "$IMAGE" > "$CONF_DIR/$ROLE.image"
install -m 644 "$PACKAGING/systemd/$UNIT_SRC" "/etc/systemd/system/$UNIT"
[ "$ROLE" != backend ] || sed -i "s/:29181/:$PORT/" "/etc/systemd/system/$UNIT"
set -- "$ROLE" --engine "$ENGINE" --volume "$V_CFG" --status-volume "$V_STATUS" --helper-image "$IMAGE" \
--image-repo "$REPO" --unit "$UNIT" --policy "$POLICY" --no-enable
[ "$ROLE" != backend ] || set -- "$@" --channel "$CHANNEL" --health-url "http://127.0.0.1:$PORT/healthz"
[ -z "$AUTHFILE" ] || set -- "$@" --authfile "$AUTHFILE"
sh "$PACKAGING/update/install-updater.sh" "$@"
@@ -211,13 +167,5 @@ systemctl daemon-reload
systemctl enable "$UNIT" "moshiro-update-$ROLE.timer" >/dev/null 2>&1
systemctl restart "$UNIT"
systemctl start "moshiro-update-$ROLE.timer"
if [ "$ROLE" = backend ]; then
n=0
until curl -fsS -o /dev/null "http://127.0.0.1:$PORT/healthz" 2>/dev/null; do
n=$((n + 1)); [ "$n" -lt 30 ] || { echo "backend did not become healthy" >&2; systemctl status "$UNIT" --no-pager >&2 || true; exit 1; }
sleep 2
done
else
sleep 5
fi
sleep 5
systemctl is-active "$UNIT"