Files
host 64995fafab P2-ops: migration alignment, deploy test list, monitor alerts/single-host, backup/rollback closure, Caddy template
- migrations: MIGRATION_TABLES now equals POSTGRES_SCHEMA tables
  (uploads, task_attachments, task_followups, environments added);
  SEQUENCE_TABLES matches IDENTITY tables; task_followups
  sender_task_id column ensured before insert; UNMIGRATED_TABLES
  and migration_coverage() report tables with no PG schema instead
  of silently skipping them; CLI prints the coverage report.
- deploy: DEFAULT_TEST_SUITES gains deploy/tests (discover -s only,
  never package path); regression tests pin the default list.
- monitor: MONITOR_SINGLE_HOST=1 makes the server check single-host;
  default stays dual to avoid silent downgrade; server_mode is in
  the JSON. Optional fail-only alert outlet (webhook or mail,
  default off, alert body is pass/fail JSON only, no secrets).
- backup/rollback: README and restore-drill document what is
  recoverable (DB, code) vs not (worktrees, traces, uploads, host
  configs); rollback.sh shares the server-unit list and dual-port
  health success standard with host_deploy.sh (sync test guards it).
- public entry: Caddyfile uses native {$DOMAIN}/{$ACME_EMAIL}
  variables plus a caddy.service template; README documents the
  steps and the current BT-nginx production status.
2026-10-27 16:37:28 +00:00

120 lines
4.4 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# 回滚:VM 侧运行,把主机恢复到最近一份代码备份包并重启验证。
#
# 用法:deploy/rollback.sh <host> [user]
# host:主机 IP 或域名(必填);user:SSH 用户(默认 root)。
#
# 密码:与 deploy.sh 同款规则——绝不接受密码参数、不读 env 文件、
# 不打印任何密钥;SSH 密码由交互提示或调用方通道提供;需要代理时
# 用环境变量 SSH_PROXY_CMD 传入 ProxyCommand,未设则直连。
#
# 回滚边界:只回代码,不回数据库(理由见 deploy.sh 头注释与
# deploy/README.md:数据库迁移是只加列的幂等操作,老代码仍能读新列)。
# 备份包来自每次部署前在主机上打的
# /opt/codex-app-server-backups/codex-app-server-<时间戳>.tar.gz,
# 本脚本恢复其中最新的一份;主机上没有备份包时直接报错退出,不编造。
#
# 与 host_deploy.sh 共享的约定(P2-ops,两脚本改动时必须同步,
# deploy/tests/test_p2_ops.py 会双向核对,见文件尾注释):
# - server unit 清单:codex-cloud-server 必含,codex-cloud-server-2
# 以 systemctl cat 探测为准(存在才纳入停/起与健康检查);
# - worker:autoscaler active 时停/起 autoscaler,否则 cloud-worker
# (与 host_deploy.sh 的 worker_unit() 同款);
# - 成功标准:清单里每个 server 端口的 health 都通过(8765 必查,
# server-2 存在时 8766 也必查),只通 8765 不算回滚成功——
# 否则 LB 上游的 server-2 仍跑故障代码,回滚等于没回完。
#
# 仿真覆盖(仅测试用,生产用默认值):CODEX_ROLLBACK_APP_DIR、
# CODEX_ROLLBACK_BACKUP_DIR、CODEX_ROLLBACK_HEALTH_ATTEMPTS、
# CODEX_ROLLBACK_HEALTH_INTERVAL。默认值与旧行为完全一致。
set -eu
HOST=${1:-}
USER_ARG=${2:-root}
if [ -z "$HOST" ]; then
echo "用法:deploy/rollback.sh <host> [user]" >&2
exit 2
fi
ssh_cmd() {
if [ -n "${SSH_PROXY_CMD:-}" ]; then
ssh -o "ProxyCommand $SSH_PROXY_CMD" -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null "$@"
else
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$@"
fi
}
ssh_cmd "$USER_ARG@$HOST" 'sh -s' <<'HOST_SCRIPT'
set -eu
APP_DIR=${CODEX_ROLLBACK_APP_DIR:-/opt/codex-app-server}
BACKUP_DIR=${CODEX_ROLLBACK_BACKUP_DIR:-/opt/codex-app-server-backups}
HEALTH_ATTEMPTS=${CODEX_ROLLBACK_HEALTH_ATTEMPTS:-12}
HEALTH_INTERVAL=${CODEX_ROLLBACK_HEALTH_INTERVAL:-5}
# 与 host_deploy.sh 共享的 unit 清单约定(见本文件头注释):
# server-1 必含,server-2 以 unit 存在为准;改动清单时两脚本同步改。
SERVER_UNITS="codex-cloud-server"
HEALTH_PORTS="8765"
if systemctl cat codex-cloud-server-2 >/dev/null 2>&1; then
SERVER_UNITS="$SERVER_UNITS codex-cloud-server-2"
HEALTH_PORTS="$HEALTH_PORTS 8766"
fi
if systemctl is-active -q codex-autoscaler 2>/dev/null; then
WORKER_UNIT=codex-autoscaler
else
WORKER_UNIT=codex-cloud-worker
fi
check_health() {
for port in $HEALTH_PORTS; do
OUT=$(curl -s --max-time 5 "http://127.0.0.1:$port/health" || true)
[ -n "$OUT" ] || return 1
if ! printf '%s' "$OUT" | python3 -c '
import json, sys
try:
d = json.load(sys.stdin)
except Exception:
sys.exit(1)
sys.exit(0 if (d.get("ok") is True and d.get("backend") == "postgres"
and d.get("db_ok") is True) else 1)
'; then
return 1
fi
done
return 0
}
wait_health() {
i=0
while [ "$i" -lt "$HEALTH_ATTEMPTS" ]; do
if check_health; then return 0; fi
sleep "$HEALTH_INTERVAL"
i=$((i + 1))
done
return 1
}
BACKUP_FILE=$(ls -t "$BACKUP_DIR"/codex-app-server-*.tar.gz 2>/dev/null \
| head -n 1 || true)
if [ -z "$BACKUP_FILE" ]; then
echo "主机上没有代码备份包,无法回滚:$BACKUP_DIR" >&2
exit 1
fi
echo "将恢复备份包:$BACKUP_FILE"
echo "停服务:worker_unit=$WORKER_UNIT server_units=$SERVER_UNITS"
systemctl stop "$WORKER_UNIT" $SERVER_UNITS || true
find "$APP_DIR" -mindepth 1 -maxdepth 1 ! -name work -exec rm -rf {} +
tar -xzf "$BACKUP_FILE" -C "$(dirname "$APP_DIR")"
if id codexsvc >/dev/null 2>&1; then
chown -R codexsvc:codexsvc "$APP_DIR"
fi
systemctl start $SERVER_UNITS "$WORKER_UNIT"
if wait_health; then
echo "回滚成功,全部端口 health 通过($HEALTH_PORTS),当前代码为备份包:$BACKUP_FILE"
exit 0
fi
echo "回滚后 health 仍失败(需全部端口通过),需人工介入" >&2
exit 1
HOST_SCRIPT