#!/bin/sh set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) INFRA_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) PROJECT_DIR=$(CDPATH= cd -- "$INFRA_DIR/.." && pwd) COMPOSE_FILE=${COMPOSE_FILE:-"$INFRA_DIR/docker-compose.prod.yml"} BACKUP_ROOT=${BACKUP_ROOT:-"$PROJECT_DIR/backups"} STAMP=$(date -u +%Y%m%dT%H%M%SZ) TARGET="$BACKUP_ROOT/$STAMP" mkdir -p "$TARGET" TARGET=$(CDPATH= cd -- "$TARGET" && pwd) case "$TARGET" in "$PROJECT_DIR"/*) ;; *) echo "拒绝写入项目目录之外的备份路径:$TARGET" >&2; exit 2 ;; esac POSTGRES_DB=${POSTGRES_DB:-ai_ticket_platform} POSTGRES_USER=${POSTGRES_USER:-ai_ticket} BACKUP_HELPER_IMAGE=${BACKUP_HELPER_IMAGE:-alpine:3.21} RUNNING_APPLICATION_SERVICES="" MINIO_WAS_RUNNING=false cleanup() { MINIO_READY=true if [ "$MINIO_WAS_RUNNING" = "true" ]; then if ! docker compose -f "$COMPOSE_FILE" up -d --wait minio >/dev/null; then echo "备份结束后 MinIO 未恢复健康,应用服务保持停止" >&2 MINIO_READY=false fi fi if [ "$MINIO_READY" = "true" ] && [ -n "$RUNNING_APPLICATION_SERVICES" ]; then docker compose -f "$COMPOSE_FILE" start $RUNNING_APPLICATION_SERVICES >/dev/null || true fi } trap cleanup EXIT INT TERM RUNNING_SERVICES=$(docker compose -f "$COMPOSE_FILE" ps --services --filter status=running) if printf '%s\n' "$RUNNING_SERVICES" | grep -qx "minio"; then MINIO_WAS_RUNNING=true fi if [ "${BACKUP_SKIP_APPLICATION_SERVICES:-false}" != "true" ]; then for service in backend worker beat; do if printf '%s\n' "$RUNNING_SERVICES" | grep -qx "$service"; then RUNNING_APPLICATION_SERVICES="$RUNNING_APPLICATION_SERVICES $service" fi done if [ -n "$RUNNING_APPLICATION_SERVICES" ]; then docker compose -f "$COMPOSE_FILE" stop $RUNNING_APPLICATION_SERVICES fi fi if [ "$MINIO_WAS_RUNNING" = "true" ]; then docker compose -f "$COMPOSE_FILE" stop minio fi docker compose -f "$COMPOSE_FILE" exec -T postgres \ pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom > "$TARGET/postgres.dump" docker compose -f "$COMPOSE_FILE" exec -T postgres \ pg_restore --list < "$TARGET/postgres.dump" > "$TARGET/postgres.contents" MINIO_CONTAINER=$(docker compose -f "$COMPOSE_FILE" ps -q minio) if [ -z "$MINIO_CONTAINER" ]; then echo "未找到 MinIO 容器" >&2 exit 3 fi docker run --rm \ --volumes-from "$MINIO_CONTAINER" \ -v "$TARGET:/backup" \ "$BACKUP_HELPER_IMAGE" \ tar -C /data -czf /backup/minio-data.tar.gz . ( cd "$TARGET" sha256sum postgres.dump postgres.contents minio-data.tar.gz > SHA256SUMS ) cat > "$TARGET/manifest.txt" <