#!/usr/bin/env bash # ════════════════════════════════════════════════════════════════ # 国内灯塔 · 真正的版本回滚 · rollback.sh # Sovereign: TCS-0002∞ · 国作登字-2026-A-00037559 # 守护: 铸渊 · ICE-GL-ZY001 # ════════════════════════════════════════════════════════════════ # # 与 bootstrap.sh 配套. bootstrap 在每次 update 之前会把当前 # compose / .env / app.ini / nginx.conf 打包成一个快照存进 # $DATA_ROOT/lighthouse/snapshots//. 这里负责把它恢复回去. # # 用法: # bash rollback.sh # 回滚到上一个快照 (默认) # bash rollback.sh --list # 列出所有可用快照 # bash rollback.sh --to # 回滚到指定快照 # # 设计理念: # - 数据盘 (Gitea repo / Postgres / Redis) **不动**, 只回滚配置 # 和 compose 编排; 这样数据安全, 行为可逆 # - 自动停 compose → 还原配置 → docker compose up -d → 健康检查 # - 写出回执到 _logs/rollback-*.json # ════════════════════════════════════════════════════════════════ set -euo pipefail DATA_ROOT="${DATA_ROOT:-/data}" DEPLOY_ROOT="${DEPLOY_ROOT:-/opt/guanghu}" SNAP_ROOT="$DATA_ROOT/lighthouse/snapshots" COMPOSE_DIR="$DATA_ROOT/lighthouse" mkdir -p "$SNAP_ROOT" "$DEPLOY_ROOT/_logs" ACTION="rollback" TARGET_TS="" while [ "$#" -gt 0 ]; do case "$1" in --list) ACTION="list"; shift ;; --to) TARGET_TS="$2"; shift 2 ;; -h|--help) sed -n '1,30p' "$0"; exit 0 ;; *) echo "未知参数: $1" >&2; exit 2 ;; esac done # ─── 列出快照 ───────────────────────────────────────────────── list_snapshots() { if [ ! -d "$SNAP_ROOT" ] || [ -z "$(ls -A "$SNAP_ROOT" 2>/dev/null)" ]; then echo "(还没有快照. bootstrap.sh 在 update 之前会自动建.)" return fi printf '%-22s %-12s %s\n' "快照时间戳" "大小" "备注" printf '%-22s %-12s %s\n' "----------------------" "------------" "------------------" for d in $(ls -1tr "$SNAP_ROOT"); do SZ="$(du -sh "$SNAP_ROOT/$d" 2>/dev/null | awk '{print $1}')" NOTE="$(cat "$SNAP_ROOT/$d/NOTE" 2>/dev/null || echo '-')" printf '%-22s %-12s %s\n' "$d" "$SZ" "$NOTE" done } if [ "$ACTION" = "list" ]; then list_snapshots exit 0 fi # ─── 选目标快照 ─────────────────────────────────────────────── if [ -z "$TARGET_TS" ]; then TARGET_TS="$(ls -1 "$SNAP_ROOT" 2>/dev/null | sort | tail -1 || true)" fi if [ -z "$TARGET_TS" ] || [ ! -d "$SNAP_ROOT/$TARGET_TS" ]; then echo "❌ 没有可用快照. 请先用 bootstrap.sh 跑一次 update (会自动建快照)" >&2 echo " 或: bash rollback.sh --list" >&2 exit 1 fi SNAP_DIR="$SNAP_ROOT/$TARGET_TS" echo "═══════════════════════════════════════════════════════════" echo " [rollback] 准备回滚" echo "═══════════════════════════════════════════════════════════" echo " 目标快照: $TARGET_TS" echo " 快照路径: $SNAP_DIR" echo " 备注: $(cat "$SNAP_DIR/NOTE" 2>/dev/null || echo '-')" echo "─────────────────────────────────────────────────────────" # ─── 1. 停现役 compose ───────────────────────────────────────── if [ -f "$COMPOSE_DIR/docker-compose.yml" ]; then echo " [1/4] 停现役 compose..." ( cd "$COMPOSE_DIR" && docker compose down ) || true fi # ─── 2. 当前态再存一份 (rollback 也要有快照可回溯) ───────────── NOW_TS="$(date +%Y%m%d-%H%M%S)" PRE_ROLLBACK="$SNAP_ROOT/$NOW_TS-pre-rollback" mkdir -p "$PRE_ROLLBACK" for f in docker-compose.yml .env .env.tune; do [ -f "$COMPOSE_DIR/$f" ] && cp -a "$COMPOSE_DIR/$f" "$PRE_ROLLBACK/" || true done [ -f "$COMPOSE_DIR/gitea/conf/app.ini" ] && { mkdir -p "$PRE_ROLLBACK/gitea/conf" cp -a "$COMPOSE_DIR/gitea/conf/app.ini" "$PRE_ROLLBACK/gitea/conf/" || true } echo "preserved before rollback to $TARGET_TS" > "$PRE_ROLLBACK/NOTE" echo " [2/4] 当前态已保存到 $PRE_ROLLBACK" # ─── 3. 恢复快照 ────────────────────────────────────────────── echo " [3/4] 恢复快照文件..." for f in docker-compose.yml .env .env.tune; do if [ -f "$SNAP_DIR/$f" ]; then cp -a "$SNAP_DIR/$f" "$COMPOSE_DIR/$f" echo " ↳ $f" fi done if [ -f "$SNAP_DIR/gitea/conf/app.ini" ]; then mkdir -p "$COMPOSE_DIR/gitea/conf" cp -a "$SNAP_DIR/gitea/conf/app.ini" "$COMPOSE_DIR/gitea/conf/app.ini" echo " ↳ gitea/conf/app.ini" fi # ─── 4. 起服 + 健康检查 ─────────────────────────────────────── echo " [4/4] 启动恢复后的 compose..." ( cd "$COMPOSE_DIR" && docker compose up -d ) HEALTH_OK="false" GITEA_PORT="${GITEA_HTTP_PORT:-3000}" for i in $(seq 1 30); do if curl -fs "http://127.0.0.1:$GITEA_PORT/api/v1/version" >/dev/null 2>&1; then HEALTH_OK="true" break fi sleep 2 done # ─── 回执 ───────────────────────────────────────────────────── RECEIPT="$DEPLOY_ROOT/_logs/rollback-$NOW_TS.json" cat > "$RECEIPT" <