gitea 备份与升级脚本缺陷修复
backup.sh: - 备份产出后校验完整性(gzip、mysqldump 结束标记、建表数、tar 清单), 校验不通过即中止,且不清理旧备份 - 清理只删自有文件名、按备份集整组判断、保底保留 3 份, 不再误删 upgrade.sh 的 pre-upgrade-* 回滚锚点与无关文件 - 密码改用 MYSQL_PWD 传递,不再出现在进程命令行 - 新增 --cold(停 Gitea 取一致快照,MySQL 不停)与 --verify-last - 备份包含 SSH host key 与 JWT 签名密钥,权限收紧为 600 - 补充 root/依赖/磁盘预检与 flock 防 cron 并发 upgrade.sh: - 修复 curl 状态码被拼接成 000000,导致 wait_healthy 误判就绪。 该缺陷曾使一次正常的数据库迁移被误杀并触发回滚 - SSH 检测改为按行读 banner;head -c 会阻塞到超时, 把健康的服务误报成故障 - 跨小版本升级若迁移版本未前进则判定失败, 避免迁移根本没执行却报告成功 - 健康检查端口参数化 GITEA_HTTP_PORT(默认 3000), 避免探测到其它实例而验证了错误的对象 - 回滚后提示 .env 已钉在只存在于本机的镜像标签上,并给出解绑方法 - 清理回滚镜像的建议补充说明:删除后 --rollback 会失效 - 数据备份包权限收紧为 600 - 容量显示不再把 780KB 显示成 0MB 验证:生产 1.25.5 → 1.27.1 升级完成,数据逐项核验一致; 隔离沙箱完整演练升级与 --rollback,回滚后数据精确还原。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+59
-6
@@ -160,6 +160,9 @@ load_env() {
|
||||
BACKUP_BASE="${BACKUP_DIR:-/var/backups/gitea}"
|
||||
DOMAIN="${GITEA_DOMAIN:-}"
|
||||
CURRENT_IMAGE="${GITEA_IMAGE:-gitea/gitea:1.25}"
|
||||
# 健康检查探测的本地端口。默认 3000 与生产一致;隔离演练环境
|
||||
# 可在 .env 里设 GITEA_HTTP_PORT,避免探到别的实例上去。
|
||||
HTTP_PORT="${GITEA_HTTP_PORT:-3000}"
|
||||
|
||||
if [ -z "${DB_ROOT_PASSWORD:-}" ]; then
|
||||
error ".env 中缺少 DB_ROOT_PASSWORD,无法备份数据库"
|
||||
@@ -177,6 +180,17 @@ db_query() {
|
||||
db_exec mysql -u root -N -B -e "$1" 2>/dev/null | tr -d '\r'
|
||||
}
|
||||
|
||||
# 输入 KB,输出人类可读。整数除法会把 780KB 显示成 "0MB",
|
||||
# 让人误以为没有数据要备份,所以小于 1MB 时改用 KB 显示。
|
||||
fmt_size() {
|
||||
local kb="${1:-0}"
|
||||
[[ "$kb" =~ ^[0-9]+$ ]] || { echo "未知"; return; }
|
||||
if [ "$kb" -lt 1024 ]; then echo "${kb}KB"
|
||||
elif [ "$kb" -lt 1048576 ]; then echo "$((kb/1024))MB"
|
||||
else echo "$((kb/1048576))GB"
|
||||
fi
|
||||
}
|
||||
|
||||
db_running() {
|
||||
local cid
|
||||
cid=$(docker compose ps -a -q "$SVC_DB" 2>/dev/null | head -1)
|
||||
@@ -250,7 +264,7 @@ preflight() {
|
||||
fi
|
||||
|
||||
log "root 权限、依赖命令、数据目录、磁盘空间 均正常"
|
||||
info "数据库 $((db_kb/1024))MB,待备份数据 $((data_kb/1024))MB,可用空间 $((avail_kb/1024))MB"
|
||||
info "数据库 $(fmt_size "$db_kb"),待备份数据 $(fmt_size "$data_kb"),可用空间 $(fmt_size "$avail_kb")"
|
||||
}
|
||||
|
||||
# =============================================================
|
||||
@@ -346,7 +360,7 @@ detect_current_version() {
|
||||
v=$(docker exec "$cid" gitea --version 2>/dev/null | head -1 | awk '{print $3}' | cut -d+ -f1 || true)
|
||||
fi
|
||||
if [ -z "$v" ]; then
|
||||
v=$(curl -sf --max-time 10 "http://127.0.0.1:3000/api/v1/version" 2>/dev/null \
|
||||
v=$(curl -sf --max-time 10 "http://127.0.0.1:${HTTP_PORT}/api/v1/version" 2>/dev/null \
|
||||
| grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
||||
| sed 's/.*"\([^"]*\)"$/\1/' | cut -d+ -f1 || true)
|
||||
fi
|
||||
@@ -473,6 +487,8 @@ cold_backup() {
|
||||
printf ' %s\n' "${BACKUP_PATHS[@]}"
|
||||
|
||||
tar czf "$DATA_BACKUP" -C "$(dirname "$DATA_DIR")" "${BACKUP_PATHS[@]}"
|
||||
# 包里有 SSH host key 和 OAuth2 JWT 签名密钥,权限必须收紧
|
||||
chmod 600 "$DATA_BACKUP"
|
||||
verify_data_backup "$DATA_BACKUP"
|
||||
log "数据备份完成: $(du -h "$DATA_BACKUP" | cut -f1)"
|
||||
|
||||
@@ -601,7 +617,7 @@ wait_healthy() {
|
||||
|
||||
info "等待 Gitea 就绪(最多 ${max_wait}s,跨版本迁移会比较慢)..."
|
||||
for i in $(seq 1 "$max_wait"); do
|
||||
code="$(http_code "http://127.0.0.1:3000/api/v1/version" 3)"
|
||||
code="$(http_code "http://127.0.0.1:${HTTP_PORT}/api/v1/version" 3)"
|
||||
if [ "$code" != "000" ]; then
|
||||
log "Gitea 已就绪(${i}s,HTTP ${code})"
|
||||
return 0
|
||||
@@ -668,7 +684,7 @@ verify_endpoints() {
|
||||
step "接口验证"
|
||||
local failed=0 code
|
||||
|
||||
code="$(http_code "http://127.0.0.1:3000/api/v1/version" 10)"
|
||||
code="$(http_code "http://127.0.0.1:${HTTP_PORT}/api/v1/version" 10)"
|
||||
case "$code" in
|
||||
200) log "/api/v1/version → 200" ;;
|
||||
401|403) log "/api/v1/version → ${code}(已开启强制登录,属预期)" ;;
|
||||
@@ -677,7 +693,7 @@ verify_endpoints() {
|
||||
esac
|
||||
|
||||
# 首页:模板损坏(自定义模板与新版不兼容)会在这里暴露成 5xx
|
||||
code="$(http_code "http://127.0.0.1:3000/" 10)"
|
||||
code="$(http_code "http://127.0.0.1:${HTTP_PORT}/" 10)"
|
||||
if [ "$code" = "000" ] || [ "$code" -ge 500 ]; then
|
||||
error "首页 → ${code}(模板渲染失败?)"; failed=1
|
||||
else
|
||||
@@ -727,6 +743,18 @@ verify_data_integrity() {
|
||||
error "数据库迁移版本倒退: $MIGRATION_BEFORE → $migration_after"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 跨小版本升级必然伴随数据库结构变更,迁移版本理应前进。它没动就说明
|
||||
# 新版本压根没对这个库执行迁移 —— 可能是健康检查探到了别的实例、
|
||||
# 容器连到了另一个数据库,或验证跑在迁移完成之前。
|
||||
# 这种情况下「报成功」远比「报失败」危险,必须拦下来。
|
||||
if [ -n "$CURRENT_VERSION" ] \
|
||||
&& [ "$(ver_series "$CURRENT_VERSION")" != "$(ver_series "$TARGET_VERSION")" ] \
|
||||
&& [ "$migration_after" -eq "$MIGRATION_BEFORE" ]; then
|
||||
error "跨小版本升级 ${CURRENT_VERSION} → ${TARGET_VERSION},迁移版本却没有前进(仍为 ${MIGRATION_BEFORE})"
|
||||
error "新版本似乎没有对这个数据库执行迁移,不能认定升级成功"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 逐表比对:行数只允许持平或增加,减少视为数据丢失
|
||||
@@ -866,6 +894,26 @@ on_exit() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# 回滚后 .env 会被钉在 pre-upgrade-<时间戳> 这个「只存在于本机」的标签上。
|
||||
# 而升级成功时脚本又会建议 `docker image rm` 掉这类标签 —— 处在回滚状态的人
|
||||
# 照做(或例行清理旧镜像)就会把自己的部署搞挂。必须把这层依赖讲明白。
|
||||
warn_rollback_pinning() {
|
||||
local restored_ver="${1:-}"
|
||||
echo ""
|
||||
warn "══════ 重要:.env 现在钉在回滚镜像上 ══════"
|
||||
warn " GITEA_IMAGE=${ROLLBACK_TAG}"
|
||||
warn "该标签只存在于本机,删掉它服务将无法启动。"
|
||||
warn "清理 docker 镜像时务必跳过它!"
|
||||
if [ -n "$restored_ver" ]; then
|
||||
warn ""
|
||||
warn "确认服务稳定后,可以这样回到常规版本号标签(无需联网重新拉取):"
|
||||
warn " docker tag ${ROLLBACK_TAG} ${IMAGE_REPO}:${restored_ver}"
|
||||
warn " sed -i 's|^GITEA_IMAGE=.*|GITEA_IMAGE=${IMAGE_REPO}:${restored_ver}|' ${SCRIPT_DIR}/.env"
|
||||
warn " cd ${SCRIPT_DIR} && docker compose up -d --force-recreate --no-deps ${SVC_APP}"
|
||||
fi
|
||||
warn "═══════════════════════════════════════════"
|
||||
}
|
||||
|
||||
do_rollback() {
|
||||
trap - ERR EXIT # 先解除,避免回滚过程自身出错导致递归
|
||||
SAFETY_STAGE="done"
|
||||
@@ -899,6 +947,7 @@ do_rollback() {
|
||||
warn "升级失败已回滚。备份保留在:"
|
||||
warn " 数据库: $DB_BACKUP"
|
||||
warn " 数据: $DATA_BACKUP"
|
||||
warn_rollback_pinning "${CURRENT_VERSION:-}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -945,8 +994,10 @@ manual_rollback() {
|
||||
if wait_healthy 180; then
|
||||
log "回滚完成"
|
||||
verify_endpoints || warn "接口验证有告警,请检查日志"
|
||||
warn_rollback_pinning "${FROM_VERSION:-}"
|
||||
else
|
||||
error "服务未就绪,请检查: docker compose logs --tail 100 $SVC_APP"
|
||||
warn_rollback_pinning "${FROM_VERSION:-}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -1084,7 +1135,9 @@ main() {
|
||||
echo ""
|
||||
warn "请登录 Web 端确认仓库、议题、LFS 都正常后,再考虑清理回滚镜像:"
|
||||
warn " docker image rm $ROLLBACK_TAG"
|
||||
warn "升级前的备份请至少保留到确认无误之后,不要急着删。"
|
||||
warn "但要清楚代价:删掉之后 'bash upgrade.sh --rollback' 会因找不到镜像而失效,"
|
||||
warn "届时只能靠 SQL 备份手工恢复。建议先跑一段时间确认无误再删。"
|
||||
warn "升级前的备份同理,不要急着删。"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user