文件
hyb-oa/scripts/backup-all.sh
T

94 行
3.3 KiB
Bash
可执行文件

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKUP_DIR="${BACKUP_DIR:-$ROOT_DIR/deploy/trialrun/backups}"
BACKUP_RUN_ID="${BACKUP_RUN_ID:-$(date -u +%Y%m%dT%H%M%SZ)}"
if [[ ! "$BACKUP_RUN_ID" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$ ]]; then
echo "[ERR] BACKUP_RUN_ID must use 1-80 safe characters" >&2
exit 1
fi
mkdir -p "$BACKUP_DIR"
FINAL_DIR="$BACKUP_DIR/hyb-oa-backup-$BACKUP_RUN_ID"
if [[ -e "$FINAL_DIR" ]]; then
echo "[ERR] backup batch already exists: hyb-oa-backup-$BACKUP_RUN_ID" >&2
exit 1
fi
TEMP_DIR="$(mktemp -d "$BACKUP_DIR/.hyb-oa-backup-$BACKUP_RUN_ID.tmp.XXXXXX")"
cleanup() {
if [[ -n "${TEMP_DIR:-}" && -d "$TEMP_DIR" && "$TEMP_DIR" == "$BACKUP_DIR"/.hyb-oa-backup-*.tmp.* ]]; then
rm -rf -- "$TEMP_DIR"
fi
}
trap cleanup EXIT
DB_NAME="database.dump"
STORAGE_NAME="storage.tar.gz"
MANIFEST_NAME="manifest.json"
DB_FILE="$TEMP_DIR/$DB_NAME"
STORAGE_FILE="$TEMP_DIR/$STORAGE_NAME"
BACKUP_DIR="$TEMP_DIR" OUT_FILE="$DB_FILE" "$ROOT_DIR/scripts/backup-db.sh"
BACKUP_DIR="$TEMP_DIR" OUT_FILE="$STORAGE_FILE" "$ROOT_DIR/scripts/backup-storage.sh"
DB_VALIDATED="false"
if [[ "${DB_TOOL_MODE:-auto}" != "docker" ]] && command -v pg_restore >/dev/null 2>&1; then
if pg_restore --list "$DB_FILE" >/dev/null; then
DB_VALIDATED="true"
elif [[ "${DB_TOOL_MODE:-auto}" == "host" ]]; then
echo "[ERR] host pg_restore could not validate the DB dump" >&2
exit 1
fi
fi
if [[ "$DB_VALIDATED" != "true" ]] && command -v docker >/dev/null 2>&1 && [[ -n "${DB_CONTAINER:-}" ]]; then
docker exec -i "$DB_CONTAINER" pg_restore --list <"$DB_FILE" >/dev/null
DB_VALIDATED="true"
fi
if [[ "$DB_VALIDATED" != "true" ]]; then
echo "[ERR] pg_restore or DB_CONTAINER is required to validate the DB dump" >&2
exit 1
fi
tar -tzf "$STORAGE_FILE" >/dev/null
while IFS= read -r listing_line; do
if [[ "${listing_line:0:1}" == "l" || "${listing_line:0:1}" == "h" ]]; then
echo "[ERR] storage backup contains links that the safe restore path will reject" >&2
exit 1
fi
done < <(tar -tvzf "$STORAGE_FILE")
DB_SHA256="$(sha256sum "$DB_FILE" | awk '{print $1}')"
STORAGE_SHA256="$(sha256sum "$STORAGE_FILE" | awk '{print $1}')"
DB_BYTES="$(wc -c <"$DB_FILE" | tr -d ' ')"
STORAGE_BYTES="$(wc -c <"$STORAGE_FILE" | tr -d ' ')"
BUILD_SHA="$(git -C "$ROOT_DIR" rev-parse --short=12 HEAD 2>/dev/null || true)"
CREATED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
node - "$TEMP_DIR/$MANIFEST_NAME" "$BACKUP_RUN_ID" "$CREATED_AT" "$BUILD_SHA" \
"$DB_NAME" "$DB_BYTES" "$DB_SHA256" "$STORAGE_NAME" "$STORAGE_BYTES" "$STORAGE_SHA256" <<'NODE'
const fs = require("node:fs");
const [manifestPath, runId, createdAt, buildSha, dbFile, dbBytes, dbSha256, storageFile, storageBytes, storageSha256] = process.argv.slice(2);
const manifest = {
schemaVersion: 1,
runId,
createdAt,
buildSha: buildSha || null,
status: "complete",
artifacts: {
database: { file: dbFile, bytes: Number(dbBytes), sha256: dbSha256 },
storage: { file: storageFile, bytes: Number(storageBytes), sha256: storageSha256 },
},
};
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
NODE
mv "$TEMP_DIR" "$FINAL_DIR"
TEMP_DIR=""
trap - EXIT
echo "[OK] Paired backup complete: hyb-oa-backup-$BACKUP_RUN_ID"
echo "[INFO] Copy the complete batch directory to approved off-host/off-disk storage"