Files
plainleaf/deploy/nas/update-plainleaf.sh
T
wushenghuaandCodex a9e653009a
CI / config (push) Canceled after 0s
CI / test-frontend (push) Canceled after 0s
CI / test-rust (push) Canceled after 0s
CI / test-e2e (push) Canceled after 0s
CI / test-e2e-release (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / release (push) Canceled after 0s
CI / docker (push) Canceled after 0s
CI / docker-website (push) Canceled after 0s
feat: 支持主动触发 NAS 安全更新
Co-Authored-By: Codex <noreply@anthropic.com>
2026-08-12 23:07:46 +08:00

110 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
readonly DEPLOY_DIR="${PLAINLEAF_DEPLOY_DIR:-/etc/plainleaf}"
readonly COMPOSE_FILE="${DEPLOY_DIR}/compose.yaml"
readonly ENV_FILE="${DEPLOY_DIR}/plainleaf.env"
readonly EXPECTED_IMAGE_PREFIX="gitea.aichickenfarm.cn/wushenghua/plainleaf:"
readonly HEALTH_TIMEOUT_SECONDS="${PLAINLEAF_HEALTH_TIMEOUT_SECONDS:-120}"
compose=(docker compose --env-file "$ENV_FILE" --file "$COMPOSE_FILE")
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*"
}
wait_for_healthy_container() {
local deadline=$((SECONDS + HEALTH_TIMEOUT_SECONDS))
local container_id state
while ((SECONDS < deadline)); do
container_id="$("${compose[@]}" ps --quiet plainleaf 2>/dev/null || true)"
if [[ -n "$container_id" ]]; then
state="$(docker inspect \
--format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
"$container_id" 2>/dev/null || true)"
case "$state" in
healthy | running)
return 0
;;
exited | dead)
return 1
;;
esac
fi
sleep 3
done
return 1
}
restore_previous_image() {
local previous_image_id="$1"
local target_image="$2"
if [[ -z "$previous_image_id" ]]; then
log "首次部署没有可回滚的旧镜像,请检查 Plainleaf 日志。"
return 1
fi
log "新版本启动失败,正在恢复旧镜像 ${previous_image_id}。"
docker image tag "$previous_image_id" "$target_image"
"${compose[@]}" up --detach --no-deps --force-recreate plainleaf
wait_for_healthy_container
}
if [[ ! -r "$COMPOSE_FILE" || ! -r "$ENV_FILE" ]]; then
log "缺少部署文件:${COMPOSE_FILE} 或 ${ENV_FILE}。"
exit 1
fi
mapfile -t services < <("${compose[@]}" config --services)
if [[ "${#services[@]}" -ne 1 || "${services[0]}" != "plainleaf" ]]; then
log "安全检查失败:该 Compose 必须只包含 plainleaf 服务。"
exit 1
fi
target_image="$("${compose[@]}" config --images)"
if [[ "$target_image" != "${EXPECTED_IMAGE_PREFIX}"* ]]; then
log "安全检查失败:不允许更新镜像 ${target_image}。"
exit 1
fi
current_container_id="$("${compose[@]}" ps --quiet plainleaf 2>/dev/null || true)"
current_image_id=""
if [[ -n "$current_container_id" ]]; then
current_image_id="$(docker inspect --format '{{.Image}}' "$current_container_id")"
rollback_image="${EXPECTED_IMAGE_PREFIX}rollback"
docker image tag "$current_image_id" "$rollback_image"
log "已保留回滚镜像 ${rollback_image}。"
fi
log "检查 ${target_image} 是否有新版本。"
"${compose[@]}" pull plainleaf
target_image_id="$(docker image inspect --format '{{.Id}}' "$target_image")"
if [[ -n "$current_image_id" && "$current_image_id" == "$target_image_id" ]]; then
log "当前已经是最新镜像,无需重建容器。"
exit 0
fi
log "发现新镜像 ${target_image_id},仅重建 plainleaf 服务。"
if ! "${compose[@]}" up --detach --no-deps --force-recreate plainleaf; then
restore_previous_image "$current_image_id" "$target_image"
exit 1
fi
if wait_for_healthy_container; then
log "Plainleaf 已更新并通过健康检查。"
exit 0
fi
docker logs --tail 100 plainleaf >&2 2>/dev/null || true
if restore_previous_image "$current_image_id" "$target_image"; then
log "已恢复旧版本;新版本仍保留在本机供排查。"
else
log "自动回滚失败,需要人工检查 Plainleaf。"
fi
exit 1