backend: require confirmation for signed channel updates [CI]
This commit is contained in:
1 parent
7160de2a36
commit
84f9d211bc
62 files changed
+1717
-70
No files matched your search
@@ -18,6 +18,7 @@ services:
|
||||
- /sys:/host/sys:ro
|
||||
- /:/host/root:ro
|
||||
environment:
|
||||
MOSHIRO_NETWORK_SCOPE: host
|
||||
HOST_PROC: /host/proc
|
||||
HOST_SYS: /host/sys
|
||||
HOST_ROOT: /host/root
|
||||
|
||||
@@ -8,6 +8,8 @@ services:
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
network_mode: host
|
||||
environment:
|
||||
MOSHIRO_NETWORK_SCOPE: host
|
||||
volumes:
|
||||
- probe-config:/etc/moshiro
|
||||
- probe-status:/etc/moshiro/update-status:ro
|
||||
|
||||
@@ -66,7 +66,7 @@ write_agent_config() {
|
||||
cat >"$CONFIG/agent.json" <<JSON
|
||||
{
|
||||
"fleet": { "server": "$1", "identity_dir": "$CONFIG" },
|
||||
"listen": { "control_port": 7443, "udp_ports": [7443], "tcp_ports": [7444] },
|
||||
"listen": { "bind": "::", "control_port": 7443, "udp_ports": [7443], "tcp_ports": [7444] },
|
||||
"limits": { "max_total_mbps": 200, "max_total_pps": 20000, "allowed_target_cidrs": [], "max_concurrent_tests": 1 }
|
||||
}
|
||||
JSON
|
||||
|
||||
@@ -9,7 +9,7 @@ Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/podman rm -f moshiro-agent
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-agent --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root -v moshiro-agent-config:/etc/moshiro -v moshiro-agent-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-agent --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro -e MOSHIRO_NETWORK_SCOPE=host -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root -v moshiro-agent-config:/etc/moshiro -v moshiro-agent-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/podman stop -t 20 moshiro-agent
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -9,7 +9,7 @@ Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/docker rm -f moshiro-agent
|
||||
ExecStart=/usr/bin/docker run --rm --name moshiro-agent --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root -v moshiro-agent-config:/etc/moshiro -v moshiro-agent-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStart=/usr/bin/docker run --rm --name moshiro-agent --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro -e MOSHIRO_NETWORK_SCOPE=host -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root -v moshiro-agent-config:/etc/moshiro -v moshiro-agent-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/docker stop -t 20 moshiro-agent
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -9,7 +9,7 @@ Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/podman rm -f moshiro-agent
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-agent --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root -v moshiro-agent-config:/etc/moshiro -v moshiro-agent-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-agent --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro -e MOSHIRO_NETWORK_SCOPE=host -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root -v moshiro-agent-config:/etc/moshiro -v moshiro-agent-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/podman stop -t 20 moshiro-agent
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[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 --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
|
||||
@@ -9,7 +9,7 @@ Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/podman rm -f moshiro-probe
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -e MOSHIRO_NETWORK_SCOPE=host -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/podman stop -t 20 moshiro-probe
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -9,7 +9,7 @@ Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/docker rm -f moshiro-probe
|
||||
ExecStart=/usr/bin/docker run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStart=/usr/bin/docker run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -e MOSHIRO_NETWORK_SCOPE=host -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/docker stop -t 20 moshiro-probe
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -9,7 +9,7 @@ Restart=always
|
||||
RestartSec=5
|
||||
TimeoutStopSec=30
|
||||
ExecStartPre=-/usr/bin/podman rm -f moshiro-probe
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStart=/usr/bin/podman run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -e MOSHIRO_NETWORK_SCOPE=host -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
|
||||
ExecStop=/usr/bin/podman stop -t 20 moshiro-probe
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
set -eu
|
||||
|
||||
COMPONENT="${1:-}"; shift || true
|
||||
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] [--authfile F] [--no-enable]" >&2; exit 2 ;; esac
|
||||
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
|
||||
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
|
||||
case "$1" in
|
||||
--user) SERVICE_SCOPE=user; shift ;;
|
||||
@@ -18,6 +19,8 @@ while [ $# -gt 0 ]; do
|
||||
--image-repo) IMAGE_REPO="$2"; shift 2 ;;
|
||||
--unit) UNIT="$2"; shift 2 ;;
|
||||
--policy) POLICY="$2"; shift 2 ;;
|
||||
--channel) CHANNEL="$2"; shift 2 ;;
|
||||
--health-url) HEALTH_URL="$2"; shift 2 ;;
|
||||
--authfile) AUTHFILE="$2"; shift 2 ;;
|
||||
--no-enable) ENABLE=false; shift ;;
|
||||
*) echo "unknown option: $1" >&2; exit 2 ;;
|
||||
@@ -28,6 +31,10 @@ 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; }
|
||||
@@ -79,12 +86,13 @@ if [ -z "$DATA_VOLUME" ]; then install -d -m 755 "$DATA_DIR/update-status"; fi
|
||||
ENV_FILE="$CONF_DIR/$COMPONENT.image"
|
||||
PUBKEY="$CONF_DIR/release-key.pub.pem"
|
||||
{
|
||||
for name in DATA_DIR IMAGE_REPO UNIT PUBKEY ENV_FILE STATE_DIR POLICY AUTHFILE ENGINE SERVICE_SCOPE DATA_VOLUME STATUS_VOLUME HELPER_IMAGE; do
|
||||
for name in DATA_DIR IMAGE_REPO UNIT PUBKEY ENV_FILE STATE_DIR POLICY AUTHFILE ENGINE SERVICE_SCOPE DATA_VOLUME STATUS_VOLUME HELPER_IMAGE CHANNEL HEALTH_URL; do
|
||||
case "$name" in
|
||||
DATA_DIR) value="$DATA_DIR" ;; IMAGE_REPO) value="$IMAGE_REPO" ;; UNIT) value="$UNIT" ;;
|
||||
PUBKEY) value="$PUBKEY" ;; ENV_FILE) value="$ENV_FILE" ;; STATE_DIR) value="$STATE_DIR" ;;
|
||||
DATA_VOLUME) value="$DATA_VOLUME" ;; STATUS_VOLUME) value="$STATUS_VOLUME" ;; HELPER_IMAGE) value="$HELPER_IMAGE" ;;
|
||||
POLICY) value="$POLICY" ;; AUTHFILE) value="$AUTHFILE" ;; ENGINE) value="$ENGINE" ;; SERVICE_SCOPE) value="$SERVICE_SCOPE" ;;
|
||||
CHANNEL) value="$CHANNEL" ;; HEALTH_URL) value="$HEALTH_URL" ;;
|
||||
esac
|
||||
printf '%s=' "$name"; shell_quote "$value"; printf '\n'
|
||||
done
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
# Shell snippets below execute inside helper containers.
|
||||
# shellcheck disable=SC2016
|
||||
set -eu
|
||||
umask 077
|
||||
|
||||
CMD="${1:-}"
|
||||
COMPONENT="${2:-}"
|
||||
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
|
||||
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
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
DEFAULT_CONF_DIR=/etc/moshiro-update
|
||||
@@ -34,6 +35,14 @@ ENGINE="${ENGINE:-podman}"
|
||||
SERVICE_SCOPE="${SERVICE_SCOPE:-system}"
|
||||
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() {
|
||||
@@ -117,6 +126,37 @@ write_status() { # state available_json error
|
||||
current_digest() { [ -r "$INSTALLED" ] && sed -n 2p "$INSTALLED" || echo ""; }
|
||||
installed_seq() { [ -r "$INSTALLED" ] && sed -n 1p "$INSTALLED" || echo 0; }
|
||||
|
||||
# Backend 的请求带用户确认的 seq;复制以后再验清单,避免后来到达的 tag 被自动套用。
|
||||
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"
|
||||
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
|
||||
fi
|
||||
}
|
||||
|
||||
backend_backup() {
|
||||
BACKUP="$STATE_DIR/backend-before-$M_SEQ.tar.gz"
|
||||
service_ctl stop "$UNIT"
|
||||
if ! "$ENGINE" run --pull=never --rm --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; }
|
||||
@@ -131,6 +171,9 @@ verify_offer() {
|
||||
openssl dgst -sha256 -verify "$PUBKEY" -signature "$sigbin" "$OFFER_MANIFEST" >/dev/null 2>&1 || { ERR="signature check failed"; return 1; }
|
||||
[ "$(field component)" = "$COMPONENT" ] || { ERR="manifest is for another component"; return 1; }
|
||||
[ "$(field image)" = "$IMAGE_REPO" ] || { ERR="manifest names another repository"; return 1; }
|
||||
if [ -n "$CHANNEL" ]; then
|
||||
[ "$(field channel)" = "$CHANNEL" ] || { ERR="manifest is for another channel"; return 1; }
|
||||
fi
|
||||
M_DIGEST="$(field digest)"
|
||||
case "$M_DIGEST" in sha256:*) ;; *) ERR="bad digest"; return 1 ;; esac
|
||||
[ "${#M_DIGEST}" -eq 71 ] && [ -z "$(printf '%s' "${M_DIGEST#sha256:}" | tr -d '0-9a-f')" ] || { ERR="bad digest"; return 1; }
|
||||
@@ -162,6 +205,7 @@ 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'
|
||||
@@ -170,6 +214,10 @@ do_apply() {
|
||||
fi
|
||||
fetch_offer
|
||||
verify_offer || { write_status invalid null "$ERR"; echo "refusing to apply: $ERR" >&2; return 1; }
|
||||
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"
|
||||
@@ -177,6 +225,7 @@ 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 秒内没有再被重启(崩溃循环算失败)
|
||||
@@ -184,13 +233,23 @@ do_apply() {
|
||||
before="$(service_ctl show -p NRestarts --value "$UNIT" 2>/dev/null || echo 0)"
|
||||
sleep 15
|
||||
after="$(service_ctl show -p NRestarts --value "$UNIT" 2>/dev/null || echo 0)"
|
||||
if service_ctl is-active --quiet "$UNIT" && [ "$before" = "$after" ]; then
|
||||
healthy=true
|
||||
if [ -n "$HEALTH_URL" ]; then
|
||||
if ! curl --fail --silent --show-error --max-time 5 "$HEALTH_URL" | grep -q '"status":"ok"'; then healthy=false; fi
|
||||
fi
|
||||
if [ "$healthy" = true ] && service_ctl is-active --quiet "$UNIT" && [ "$before" = "$after" ]; then
|
||||
printf '%s\n%s\n' "$M_SEQ" "$M_DIGEST" > "$INSTALLED"
|
||||
write_status up_to_date null ""
|
||||
echo "updated to $M_TAG"
|
||||
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"
|
||||
@@ -200,10 +259,8 @@ do_apply() {
|
||||
case "$CMD" in
|
||||
check)
|
||||
if [ -n "$DATA_VOLUME" ] && [ "$POLICY" != auto ]; then
|
||||
requested="$($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 ] || printf yes')"
|
||||
if [ "$requested" = yes ]; then do_apply; else do_check; fi
|
||||
requested="$(fetch_request)"
|
||||
if [ -n "$requested" ]; then do_apply; else do_check; fi
|
||||
else
|
||||
do_check
|
||||
fi ;;
|
||||
|
||||
Reference in new issue
Block a user