feat(update): update the backend like nodes via quadlet restart [CI]
This commit is contained in:
1 parent
34190f5a04
commit
68c7113adf
23 files changed
+309
-357
No files matched your search
@@ -27,7 +27,6 @@ services:
|
||||
MoShiro__DataDirectory: /data
|
||||
volumes:
|
||||
- moshiro-backend-data:/data
|
||||
- moshiro-backend-status:/data/update-status:ro
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,noexec,nosuid,size=64m,mode=1777
|
||||
@@ -40,5 +39,3 @@ services:
|
||||
volumes:
|
||||
moshiro-backend-data:
|
||||
name: moshiro-backend-data
|
||||
moshiro-backend-status:
|
||||
name: moshiro-backend-status
|
||||
@@ -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"
|
||||
@@ -0,0 +1,50 @@
|
||||
# MoShiro Backend(面板)的 Podman Quadlet 单元(Podman 4.6 及以上),更新方式和 Agent、Probe 完全一样:
|
||||
# systemd 每次启动这个单元时 Pull=always 先从镜像仓库拉最新的镜像,Backend 在管理端确认更新之后有序退出(先把数据库备份到数据卷的 backups/),
|
||||
# Restart=always 把它拉起来,退出再被拉起就是更新(Backend 自己不下载、不替换任何东西)。
|
||||
# 不会自动重启:Backend 指挥所有节点,更新要管理员在管理端点「更新并重启」。
|
||||
# 回滚:新版启动时会迁移数据库,旧版不认比自己新的数据库。想回到旧版,先停服务,把 backups/pre-update-*.db 复制成数据卷里的 moshiro.db,再把 Image 固定成旧版本号。
|
||||
#
|
||||
# 安装(rootless,普通用户,不用 sudo):
|
||||
# mkdir -p ~/.config/containers/systemd ~/.config/moshiro
|
||||
# 写 ~/.config/moshiro/backend.env,chmod 600,每行一个变量,至少:
|
||||
# MoShiro__DataDirectory=/data
|
||||
# MoShiro__PublicUrl=https://panel.example.com # 面板对外的地址;passkey 绑定在这个域名上,改了会让已注册的 passkey 失效
|
||||
# ASPNETCORE_HTTP_PORTS=8080
|
||||
# 前面有反向代理(Caddy、cloudflared、frp)时再加 MoShiro__TrustedProxies(代理连到 Backend 的地址,rootless pasta 网络下是宿主的局域网地址和 127.0.0.1)
|
||||
# 和 MoShiro__TrustedProxyHeader(Cloudflare 用 CF-Connecting-IP,其他用 X-Forwarded-For),否则登录限流、审计和节点的国家识别看到的都是代理的地址。
|
||||
# 把本文件放进 ~/.config/containers/systemd/
|
||||
# systemctl --user daemon-reload && systemctl --user start moshiro-backend # Quadlet 会自动生成服务,不用 enable;[Install] 让它开机自启
|
||||
# loginctl enable-linger $USER # 退出登录后继续运行(需要管理员执行一次)
|
||||
# 第一次打开面板按页面引导认领:没有默认管理员,认领码在日志里(podman logs moshiro-backend)。
|
||||
# rootful:本文件放进 /etc/containers/systemd/,env 文件放 /etc/moshiro/backend.env,把下面的 EnvironmentFile 改成这个路径、
|
||||
# WantedBy 改成 multi-user.target,用 systemctl(不加 --user)。
|
||||
# 开发通道:把 Image 的 moshiro 换成 moshiro-dev,并在 env 文件里加 MoShiro__UpdateChannel=dev。固定版本:把 :latest 换成 :<版本号>(固定之后更新不会换版本)。
|
||||
# 说明见 docs/ops/deployment.md。
|
||||
[Unit]
|
||||
Description=MoShiro Backend
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
Image=registry.oeo.one/moshiro/moshiro-backend:latest
|
||||
ContainerName=moshiro-backend
|
||||
Pull=always
|
||||
PublishPort=127.0.0.1:8080:8080
|
||||
EnvironmentFile=%h/.config/moshiro/backend.env
|
||||
Volume=moshiro-backend-data:/data
|
||||
ReadOnly=true
|
||||
NoNewPrivileges=true
|
||||
DropCapability=ALL
|
||||
Tmpfs=/tmp:rw,noexec,nosuid,size=64m,mode=1777
|
||||
StopTimeout=30
|
||||
# 告诉 Backend:退出之后外面会重新拉取,所以「更新」就是退出(没有这一行,管理端不能更新 Backend)
|
||||
Environment=MOSHIRO_UPDATE=restart
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
# 第一次拉取镜像可能要一会儿
|
||||
TimeoutStartSec=600
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,16 +0,0 @@
|
||||
[Unit]
|
||||
Description=MoShiro Backend (signed container updates)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/etc/moshiro-update/backend.image
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/podman rm -f moshiro-backend
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-backend --network host -e ASPNETCORE_URLS=http://0.0.0.0:29181 --read-only --tmpfs /tmp --security-opt no-new-privileges --cap-drop all --env-file /etc/moshiro/backend.env -v moshiro-backend-data:/data -v moshiro-backend-status:/data/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/podman stop -t 20 moshiro-backend
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -5,7 +5,7 @@
|
||||
set -eu
|
||||
|
||||
COMPONENT="${1:-}"; shift || true
|
||||
case "$COMPONENT" in agent|probe|backend) ;; *) echo "usage: install-updater.sh <agent|probe|backend> [--user] [--engine podman|docker] --data-dir D --image-repo R --unit U [--policy auto|manual] [--channel dev|release] [--health-url URL] [--authfile F] [--no-enable]" >&2; exit 2 ;; esac
|
||||
case "$COMPONENT" in agent|probe) ;; *) echo "usage: install-updater.sh <agent|probe> [--user] [--engine podman|docker] --data-dir D --image-repo R --unit U [--policy auto|manual] [--channel dev|release] [--health-url URL] [--authfile F] [--no-enable]" >&2; exit 2 ;; esac
|
||||
DATA_DIR=""; IMAGE_REPO=""; UNIT=""; POLICY=manual; AUTHFILE=""; ENGINE=podman; SERVICE_SCOPE=system; ENABLE=true; DATA_VOLUME=""; STATUS_VOLUME=""; HELPER_IMAGE=""
|
||||
CHANNEL=""; HEALTH_URL=""
|
||||
while [ $# -gt 0 ]; do
|
||||
@@ -31,10 +31,6 @@ if [ -z "$IMAGE_REPO" ] || [ -z "$UNIT" ] || { [ -z "$DATA_DIR" ] && [ -z "$DATA
|
||||
fi
|
||||
case "$POLICY" in auto|manual) ;; *) echo "--policy must be auto or manual" >&2; exit 2 ;; esac
|
||||
case "$ENGINE" in podman|docker) ;; *) echo "--engine must be podman or docker" >&2; exit 2 ;; esac
|
||||
if [ "$COMPONENT" = backend ]; then
|
||||
[ "$POLICY" = manual ] && [ -n "$DATA_VOLUME" ] && [ -n "$HEALTH_URL" ] || { echo "Backend requires manual policy, a data volume and --health-url" >&2; exit 2; }
|
||||
case "$CHANNEL" in dev|release) ;; *) echo "Backend requires --channel dev or release" >&2; exit 2 ;; esac
|
||||
fi
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
[ -z "$DATA_DIR" ] || { echo "use --volume or --data-dir, not both" >&2; exit 2; }
|
||||
[ -n "$HELPER_IMAGE" ] || { echo "--volume needs --helper-image (an already pulled MoShiro image)" >&2; exit 2; }
|
||||
|
||||
@@ -14,8 +14,8 @@ umask 077
|
||||
|
||||
CMD="${1:-}"
|
||||
COMPONENT="${2:-}"
|
||||
case "$CMD" in check|apply) ;; *) echo "usage: moshiro-update <check|apply> <agent|probe|backend>" >&2; exit 2 ;; esac
|
||||
case "$COMPONENT" in agent|probe|backend) ;; *) echo "component must be agent, probe or backend" >&2; exit 2 ;; esac
|
||||
case "$CMD" in check|apply) ;; *) echo "usage: moshiro-update <check|apply> <agent|probe>" >&2; exit 2 ;; esac
|
||||
case "$COMPONENT" in agent|probe) ;; *) echo "component must be agent or probe" >&2; exit 2 ;; esac
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
DEFAULT_CONF_DIR=/etc/moshiro-update
|
||||
@@ -37,12 +37,6 @@ DATA_VOLUME="${DATA_VOLUME:-}"
|
||||
STATUS_VOLUME="${STATUS_VOLUME:-moshiro-$COMPONENT-status}"
|
||||
CHANNEL="${CHANNEL:-}"
|
||||
HEALTH_URL="${HEALTH_URL:-}"
|
||||
if [ "$COMPONENT" = backend ]; then
|
||||
[ "$POLICY" = manual ] || { echo "Backend updates require manual confirmation" >&2; exit 2; }
|
||||
case "$CHANNEL" in dev|release) ;; *) echo "Backend needs CHANNEL=dev or release" >&2; exit 2 ;; esac
|
||||
[ -n "$HEALTH_URL" ] || { echo "Backend needs a local HEALTH_URL" >&2; exit 2; }
|
||||
[ -n "$DATA_VOLUME" ] || { echo "Backend backup needs DATA_VOLUME" >&2; exit 2; }
|
||||
fi
|
||||
case "$ENGINE" in podman|docker) ;; *) echo "unsupported engine: $ENGINE" >&2; exit 2 ;; esac
|
||||
case "$SERVICE_SCOPE" in system|user) ;; *) echo "unsupported service scope: $SERVICE_SCOPE" >&2; exit 2 ;; esac
|
||||
service_ctl() {
|
||||
@@ -174,38 +168,17 @@ seed_installed() {
|
||||
current_digest() { [ -r "$INSTALLED" ] && sed -n 2p "$INSTALLED" || echo ""; }
|
||||
installed_seq() { [ -r "$INSTALLED" ] && sed -n 1p "$INSTALLED" || echo 0; }
|
||||
|
||||
# Backend 的请求带用户确认的 seq;复制以后再验清单,避免后来到达的 tag 被自动套用。
|
||||
# 面板(管理员)请求更新:apply.request 在数据卷里放着就是请求了
|
||||
fetch_request() {
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
"$ENGINE" run --pull=never --rm --network none --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
-v "$DATA_VOLUME:/etc/moshiro:ro" --entrypoint /bin/sh "$HELPER_IMAGE" \
|
||||
-c 'f=/etc/moshiro/update/apply.request; if [ -f "$f" ] && [ ! -L "$f" ]; then if [ "$1" = backend ]; then head -c 64 "$f"; else printf yes; fi; fi' sh "$COMPONENT"
|
||||
-c 'f=/etc/moshiro/update/apply.request; if [ -f "$f" ] && [ ! -L "$f" ]; then printf yes; fi'
|
||||
elif [ -f "$UPDATE_DIR/apply.request" ] && [ ! -L "$UPDATE_DIR/apply.request" ]; then
|
||||
if [ "$COMPONENT" = backend ]; then head -c 64 "$UPDATE_DIR/apply.request"; else printf yes; fi
|
||||
printf yes
|
||||
fi
|
||||
}
|
||||
|
||||
backend_backup() {
|
||||
BACKUP="$STATE_DIR/backend-before-$M_SEQ.tar.gz"
|
||||
service_ctl stop "$UNIT"
|
||||
# 容器的标准输出就是备份本身:不能再让引擎把它当日志写进 journal(每次更新会多灌一份备份大小的日志)
|
||||
if ! "$ENGINE" run --pull=never --rm --log-driver=none --network none --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
-v "$DATA_VOLUME:/data:ro" --entrypoint /bin/sh "$HELPER_IMAGE" -c 'tar -czf - -C /data .' > "$BACKUP.tmp"; then
|
||||
rm -f "$BACKUP.tmp"
|
||||
service_ctl start "$UNIT" || true
|
||||
write_status failed "$avail" "database backup failed"
|
||||
return 1
|
||||
fi
|
||||
chmod 600 "$BACKUP.tmp"
|
||||
mv -f "$BACKUP.tmp" "$BACKUP"
|
||||
}
|
||||
|
||||
backend_restore() {
|
||||
service_ctl stop "$UNIT" || true
|
||||
"$ENGINE" run --pull=never --rm -i --network none --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
-v "$DATA_VOLUME:/data" --entrypoint /bin/sh "$HELPER_IMAGE" -c 'tar -xzf - --no-same-owner -C /data' < "$BACKUP"
|
||||
}
|
||||
|
||||
# 从扁平的清单里取字段(清单由 CI 生成,只有字符串和整数,没有嵌套)
|
||||
field() { sed -n 's/.*"'"$1"'":"\([^"]*\)".*/\1/p' "$OFFER_MANIFEST" | head -n 1; }
|
||||
num_field() { sed -n 's/.*"'"$1"'":\([0-9][0-9]*\).*/\1/p' "$OFFER_MANIFEST" | head -n 1; }
|
||||
@@ -277,7 +250,6 @@ do_check() {
|
||||
}
|
||||
|
||||
do_apply() {
|
||||
requested_seq="$(fetch_request)"
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
"$ENGINE" run --pull=never --rm --network none --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
-v "$DATA_VOLUME:/etc/moshiro" --entrypoint /bin/sh "$HELPER_IMAGE" -c 'rm -f /etc/moshiro/update/apply.request'
|
||||
@@ -287,10 +259,6 @@ do_apply() {
|
||||
fetch_offer
|
||||
verify_offer || { write_status invalid null "$ERR"; echo "refusing to apply: $ERR" >&2; return 1; }
|
||||
adopt_next_key
|
||||
if [ "$COMPONENT" = backend ]; then
|
||||
case "$requested_seq" in ''|*[!0-9]*) write_status failed "$(available_json)" "a confirmed release sequence is required"; return 1 ;; esac
|
||||
[ "$requested_seq" = "$M_SEQ" ] || { write_status available "$(available_json)" "release changed; confirm again"; return 1; }
|
||||
fi
|
||||
avail="$(available_json)"
|
||||
write_status applying "$avail" ""
|
||||
ref="$IMAGE_REPO@$M_DIGEST"
|
||||
@@ -298,7 +266,6 @@ do_apply() {
|
||||
write_status failed "$avail" "pull failed"; echo "pull failed: $ref" >&2; return 1
|
||||
fi
|
||||
prev="$(cat "$ENV_FILE" 2>/dev/null || true)"
|
||||
if [ "$COMPONENT" = backend ]; then backend_backup || return 1; fi
|
||||
printf 'IMAGE=%s\n' "$ref" > "$ENV_FILE.new" && mv -f "$ENV_FILE.new" "$ENV_FILE"
|
||||
service_ctl restart "$UNIT" || true
|
||||
# 健康确认:服务在跑,并且 20 秒内没有再被重启(崩溃循环算失败)
|
||||
@@ -317,12 +284,6 @@ do_apply() {
|
||||
return 0
|
||||
fi
|
||||
echo "new version did not become healthy, rolling back" >&2
|
||||
if [ "$COMPONENT" = backend ]; then
|
||||
if ! backend_restore; then
|
||||
write_status failed "$avail" "database restore failed; backup retained"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if [ -n "$prev" ]; then printf '%s\n' "$prev" > "$ENV_FILE"; fi
|
||||
service_ctl restart "$UNIT" || true
|
||||
write_status rolled_back "$avail" "new version did not become healthy"
|
||||
|
||||
Reference in new issue
Block a user