加固备份恢复本地基线(07-26-internal-ops-recovery-baseline)

这个提交包含在:
2026-07-26 20:41:22 +08:00
父节点 b3df123330
当前提交 706941eb5c
共修改 16 个文件,包含 792 行新增和 61 行删除
可执行文件
+93
查看文件
@@ -0,0 +1,93 @@
#!/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"
+44 -7
查看文件
@@ -9,17 +9,46 @@ mkdir -p "$BACKUP_DIR"
TS="$(date +%Y%m%d_%H%M%S)"
OUT_FILE="${OUT_FILE:-$BACKUP_DIR/hyb-oa-db-${TS}.dump}"
if [[ -e "$OUT_FILE" ]]; then
echo "[ERR] backup output already exists: $OUT_FILE" >&2
exit 1
fi
mkdir -p "$(dirname "$OUT_FILE")"
if [[ -z "${DATABASE_URL:-}" ]]; then
echo "[ERR] DATABASE_URL is required" >&2
exit 1
fi
DB_TOOL_MODE="${DB_TOOL_MODE:-auto}"
if [[ "$DB_TOOL_MODE" != "auto" && "$DB_TOOL_MODE" != "host" && "$DB_TOOL_MODE" != "docker" ]]; then
echo "[ERR] DB_TOOL_MODE must be auto, host, or docker" >&2
exit 1
fi
echo "[INFO] DB backup start: $OUT_FILE"
if command -v pg_dump >/dev/null 2>&1; then
pg_dump "$DATABASE_URL" --format=c --no-owner --no-acl --file "$OUT_FILE"
echo "[OK] DB backup done (pg_dump): $OUT_FILE"
exit 0
if [[ "$DB_TOOL_MODE" != "docker" ]] && command -v pg_dump >/dev/null 2>&1; then
if pg_dump "$DATABASE_URL" --format=c --no-owner --no-acl --file "$OUT_FILE"; then
if [[ ! -s "$OUT_FILE" ]]; then
echo "[ERR] DB backup is empty: $OUT_FILE" >&2
exit 1
fi
echo "[OK] DB backup done (pg_dump): $OUT_FILE"
exit 0
fi
rm -f -- "$OUT_FILE"
if [[ "$DB_TOOL_MODE" == "host" ]]; then
echo "[ERR] host pg_dump failed" >&2
exit 1
fi
echo "[WARN] host pg_dump failed; trying docker fallback" >&2
fi
if [[ "$DB_TOOL_MODE" == "host" ]]; then
echo "[ERR] DB_TOOL_MODE=host requires pg_dump" >&2
exit 1
fi
if ! command -v docker >/dev/null 2>&1; then
@@ -37,9 +66,17 @@ if [[ -z "$POSTGRES_DB" || -z "$POSTGRES_USER" || -z "$POSTGRES_PASSWORD" ]]; th
exit 1
fi
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" "$DB_CONTAINER" \
if ! docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" "$DB_CONTAINER" \
pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=c --no-owner --no-acl \
>"$OUT_FILE"
>"$OUT_FILE"; then
rm -f -- "$OUT_FILE"
echo "[ERR] docker pg_dump failed" >&2
exit 1
fi
if [[ ! -s "$OUT_FILE" ]]; then
echo "[ERR] DB backup is empty: $OUT_FILE" >&2
exit 1
fi
echo "[OK] DB backup done (docker exec): $OUT_FILE"
+22 -2
查看文件
@@ -9,6 +9,11 @@ mkdir -p "$BACKUP_DIR"
TS="$(date +%Y%m%d_%H%M%S)"
OUT_FILE="${OUT_FILE:-$BACKUP_DIR/hyb-oa-storage-${TS}.tar.gz}"
if [[ -e "$OUT_FILE" ]]; then
echo "[ERR] backup output already exists: $OUT_FILE" >&2
exit 1
fi
STORAGE_DIR_HOST="${STORAGE_DIR_HOST:-}"
if [[ -z "$STORAGE_DIR_HOST" ]]; then
if [[ -n "${STORAGE_DIR:-}" && -d "${STORAGE_DIR:-}" ]]; then
@@ -21,10 +26,25 @@ if [[ -z "$STORAGE_DIR_HOST" ]]; then
fi
fi
if [[ ! -d "$STORAGE_DIR_HOST" ]]; then
echo "[ERR] storage dir not found: $STORAGE_DIR_HOST" >&2
exit 1
fi
mkdir -p "$(dirname "$OUT_FILE")"
echo "[INFO] Storage backup start: $OUT_FILE"
echo "[INFO] Storage dir: $STORAGE_DIR_HOST"
tar -czf "$OUT_FILE" -C "$STORAGE_DIR_HOST" .
if ! tar -czf "$OUT_FILE" -C "$STORAGE_DIR_HOST" .; then
rm -f -- "$OUT_FILE"
echo "[ERR] storage backup failed" >&2
exit 1
fi
if [[ ! -s "$OUT_FILE" ]]; then
echo "[ERR] storage backup is empty: $OUT_FILE" >&2
exit 1
fi
echo "[OK] Storage backup done: $OUT_FILE"
+150
查看文件
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
DRILL_RUN_ID="drill-$(date -u +%Y%m%dT%H%M%SZ)-$$"
CONTAINER_NAME="hyb-oa-backup-drill-$$"
DRILL_DIR="$(mktemp -d)"
POSTGRES_USER="drill_user"
POSTGRES_PASSWORD="drill_password"
POSTGRES_DB="hyb_oa_restore_shadow"
cleanup() {
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
if [[ -n "${DRILL_DIR:-}" && -d "$DRILL_DIR" && "$DRILL_DIR" == /tmp/* ]]; then
rm -rf -- "$DRILL_DIR"
fi
}
trap cleanup EXIT
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "[ERR] required command missing: $1" >&2
exit 1
fi
}
for command_name in docker pg_dump pg_restore psql tar sha256sum node; do
require_command "$command_name"
done
START_EPOCH="$(date +%s)"
SOURCE_STORAGE="$DRILL_DIR/source-storage"
RESTORED_STORAGE="$DRILL_DIR/restored-storage"
NONEMPTY_STORAGE="$DRILL_DIR/nonempty-storage"
BACKUP_ROOT="$DRILL_DIR/backups"
mkdir -p "$SOURCE_STORAGE/nested" "$NONEMPTY_STORAGE" "$BACKUP_ROOT"
printf 'synthetic attachment for restore drill\n' >"$SOURCE_STORAGE/nested/marker.txt"
printf 'must not be overwritten\n' >"$NONEMPTY_STORAGE/existing.txt"
docker run -d --rm --name "$CONTAINER_NAME" \
-e "POSTGRES_USER=$POSTGRES_USER" \
-e "POSTGRES_PASSWORD=$POSTGRES_PASSWORD" \
-e "POSTGRES_DB=$POSTGRES_DB" \
-p 127.0.0.1::5432 postgres:16-alpine >/dev/null
for _ in $(seq 1 60); do
if docker exec "$CONTAINER_NAME" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null 2>&1; then
break
fi
sleep 1
done
docker exec "$CONTAINER_NAME" pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" >/dev/null
HOST_PORT="$(docker port "$CONTAINER_NAME" 5432/tcp | awk -F: 'NR==1 {print $NF}')"
DATABASE_URL="postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:$HOST_PORT/$POSTGRES_DB"
export DATABASE_URL STORAGE_DIR_HOST="$SOURCE_STORAGE" BACKUP_DIR="$BACKUP_ROOT" BACKUP_RUN_ID="$DRILL_RUN_ID"
export DB_TOOL_MODE="docker" DB_CONTAINER="$CONTAINER_NAME" POSTGRES_USER POSTGRES_PASSWORD POSTGRES_DB
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -qc 'CREATE TABLE restore_drill_marker (value text NOT NULL)'
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -qc "INSERT INTO restore_drill_marker(value) VALUES ('synthetic-round-trip')"
"$ROOT_DIR/scripts/backup-all.sh"
BATCH_DIR="$BACKUP_ROOT/hyb-oa-backup-$DRILL_RUN_ID"
DB_BACKUP="$BATCH_DIR/database.dump"
STORAGE_BACKUP="$BATCH_DIR/storage.tar.gz"
node - "$BATCH_DIR/manifest.json" "$DB_BACKUP" "$STORAGE_BACKUP" <<'NODE'
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
const [manifestPath, dbPath, storagePath] = process.argv.slice(2);
const manifestText = fs.readFileSync(manifestPath, "utf8");
const manifest = JSON.parse(manifestText);
if (manifest.status !== "complete" || manifest.schemaVersion !== 1) throw new Error("manifest contract mismatch");
if (path.isAbsolute(manifest.artifacts.database.file) || path.isAbsolute(manifest.artifacts.storage.file)) {
throw new Error("manifest contains absolute artifact path");
}
if (/postgresql:|DATABASE_URL|drill_password/.test(manifestText)) throw new Error("manifest contains sensitive connection data");
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
if (sha256(dbPath) !== manifest.artifacts.database.sha256) throw new Error("database checksum mismatch");
if (sha256(storagePath) !== manifest.artifacts.storage.sha256) throw new Error("storage checksum mismatch");
NODE
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -qc 'DROP TABLE restore_drill_marker'
docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" "$CONTAINER_NAME" \
createdb -U "$POSTGRES_USER" hyb_oa_prod
if DATABASE_URL="postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:$HOST_PORT/hyb_oa_prod" \
POSTGRES_DB=hyb_oa_prod RESTORE_SCOPE=shadow RESTORE_CONFIRM_DATABASE=hyb_oa_prod \
"$ROOT_DIR/scripts/restore-db.sh" "$DB_BACKUP" >/dev/null 2>&1; then
echo "[ERR] production-like DB name was accepted as a shadow target" >&2
exit 1
fi
CORRUPT_DB="$DRILL_DIR/corrupt.dump"
head -c 32 "$DB_BACKUP" >"$CORRUPT_DB"
if RESTORE_SCOPE=shadow RESTORE_CONFIRM_DATABASE="$POSTGRES_DB" \
"$ROOT_DIR/scripts/restore-db.sh" "$CORRUPT_DB" >/dev/null 2>&1; then
echo "[ERR] corrupt DB dump was accepted" >&2
exit 1
fi
if RESTORE_SCOPE=shadow RESTORE_CONFIRM_DATABASE=wrong_target \
"$ROOT_DIR/scripts/restore-db.sh" "$DB_BACKUP" >/dev/null 2>&1; then
echo "[ERR] mismatched DB confirmation was accepted" >&2
exit 1
fi
RESTORE_SCOPE=shadow RESTORE_CONFIRM_DATABASE="$POSTGRES_DB" \
"$ROOT_DIR/scripts/restore-db.sh" "$DB_BACKUP"
RESTORED_VALUE="$(psql "$DATABASE_URL" -Atqc 'SELECT value FROM restore_drill_marker')"
if [[ "$RESTORED_VALUE" != "synthetic-round-trip" ]]; then
echo "[ERR] DB round-trip marker mismatch" >&2
exit 1
fi
if RESTORE_SCOPE=shadow "$ROOT_DIR/scripts/restore-storage.sh" "$STORAGE_BACKUP" "$NONEMPTY_STORAGE" >/dev/null 2>&1; then
echo "[ERR] non-empty storage target was accepted" >&2
exit 1
fi
if RESTORE_SCOPE=shadow "$ROOT_DIR/scripts/restore-storage.sh" \
"$STORAGE_BACKUP" "$ROOT_DIR/deploy/trialrun/data/storage" >/dev/null 2>&1; then
echo "[ERR] default live storage path was accepted as a shadow target" >&2
exit 1
fi
RESTORE_SCOPE=shadow "$ROOT_DIR/scripts/restore-storage.sh" "$STORAGE_BACKUP" "$RESTORED_STORAGE"
cmp "$SOURCE_STORAGE/nested/marker.txt" "$RESTORED_STORAGE/nested/marker.txt"
CORRUPT_TAR="$DRILL_DIR/corrupt.tar.gz"
head -c 32 "$STORAGE_BACKUP" >"$CORRUPT_TAR"
if RESTORE_SCOPE=shadow "$ROOT_DIR/scripts/restore-storage.sh" "$CORRUPT_TAR" "$DRILL_DIR/corrupt-target" >/dev/null 2>&1; then
echo "[ERR] corrupt storage archive was accepted" >&2
exit 1
fi
DANGEROUS_TAR="$DRILL_DIR/dangerous.tar.gz"
tar -czf "$DANGEROUS_TAR" --transform='s|nested/marker.txt|../escape.txt|' \
-C "$SOURCE_STORAGE" nested/marker.txt
if RESTORE_SCOPE=shadow "$ROOT_DIR/scripts/restore-storage.sh" "$DANGEROUS_TAR" "$DRILL_DIR/dangerous-target" >/dev/null 2>&1; then
echo "[ERR] dangerous storage archive path was accepted" >&2
exit 1
fi
END_EPOCH="$(date +%s)"
ELAPSED_SECONDS="$((END_EPOCH - START_EPOCH))"
echo "[PASS] isolated backup/restore drill completed"
echo "[INFO] local synthetic RTO seconds: $ELAPSED_SECONDS"
+73 -10
查看文件
@@ -1,8 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BACKUP_FILE="${1:-${BACKUP_FILE:-}}"
if [[ -z "${BACKUP_FILE:-}" ]]; then
echo "Usage: $0 <backup-file.dump>" >&2
@@ -19,16 +17,69 @@ if [[ -z "${DATABASE_URL:-}" ]]; then
exit 1
fi
echo "[INFO] DB restore start: $BACKUP_FILE"
RESTORE_SCOPE="${RESTORE_SCOPE:-}"
RESTORE_CONFIRM_DATABASE="${RESTORE_CONFIRM_DATABASE:-}"
DB_TOOL_MODE="${DB_TOOL_MODE:-auto}"
if command -v pg_restore >/dev/null 2>&1; then
if [[ "$DB_TOOL_MODE" != "auto" && "$DB_TOOL_MODE" != "host" && "$DB_TOOL_MODE" != "docker" ]]; then
echo "[ERR] DB_TOOL_MODE must be auto, host, or docker" >&2
exit 1
fi
if [[ "$RESTORE_SCOPE" != "shadow" ]]; then
echo "[ERR] RESTORE_SCOPE=shadow is required; production restore is not available from this command path" >&2
exit 1
fi
if [[ -z "$RESTORE_CONFIRM_DATABASE" ]]; then
echo "[ERR] RESTORE_CONFIRM_DATABASE is required" >&2
exit 1
fi
validate_dump_with_host() {
pg_restore --list "$BACKUP_FILE" >/dev/null
}
validate_shadow_database_name() {
local database_name="$1"
if [[ ! "$database_name" =~ (^|[_-])(shadow|drill|restore_test)([_-]|$) ]]; then
echo "[ERR] restore target name is not an explicit shadow/drill database" >&2
exit 1
fi
}
restore_with_host() {
local actual_database
actual_database="$(psql "$DATABASE_URL" -Atqc 'SELECT current_database()')"
if [[ -z "$actual_database" || "$actual_database" != "$RESTORE_CONFIRM_DATABASE" ]]; then
echo "[ERR] restore target confirmation mismatch: actual=${actual_database:-unknown}" >&2
exit 1
fi
validate_shadow_database_name "$actual_database"
echo "[INFO] DB restore target confirmed: $actual_database (shadow)"
pg_restore --clean --if-exists --no-owner --no-acl -d "$DATABASE_URL" "$BACKUP_FILE"
echo "[OK] DB restore done (pg_restore): $BACKUP_FILE"
exit 0
}
if [[ "$DB_TOOL_MODE" != "docker" ]] && command -v pg_restore >/dev/null 2>&1 && command -v psql >/dev/null 2>&1; then
if validate_dump_with_host; then
restore_with_host
echo "[OK] DB restore done (pg_restore): $BACKUP_FILE"
exit 0
fi
if [[ "$DB_TOOL_MODE" == "host" ]]; then
echo "[ERR] host pg_restore could not validate the dump" >&2
exit 1
fi
echo "[WARN] host pg_restore could not validate the dump; trying docker fallback" >&2
fi
if [[ "$DB_TOOL_MODE" == "host" ]]; then
echo "[ERR] DB_TOOL_MODE=host requires pg_restore and psql" >&2
exit 1
fi
if ! command -v docker >/dev/null 2>&1; then
echo "[ERR] pg_restore not found and docker not available" >&2
echo "[ERR] pg_restore/psql not found and docker not available" >&2
exit 1
fi
@@ -42,8 +93,20 @@ if [[ -z "$POSTGRES_DB" || -z "$POSTGRES_USER" || -z "$POSTGRES_PASSWORD" ]]; th
exit 1
fi
cat "$BACKUP_FILE" | docker exec -i -e PGPASSWORD="$POSTGRES_PASSWORD" "$DB_CONTAINER" \
pg_restore --clean --if-exists --no-owner --no-acl -U "$POSTGRES_USER" -d "$POSTGRES_DB"
ACTUAL_DATABASE="$(docker exec -e PGPASSWORD="$POSTGRES_PASSWORD" "$DB_CONTAINER" \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atqc 'SELECT current_database()')"
if [[ -z "$ACTUAL_DATABASE" || "$ACTUAL_DATABASE" != "$RESTORE_CONFIRM_DATABASE" ]]; then
echo "[ERR] restore target confirmation mismatch: actual=${ACTUAL_DATABASE:-unknown}" >&2
exit 1
fi
validate_shadow_database_name "$ACTUAL_DATABASE"
docker exec -i "$DB_CONTAINER" pg_restore --list <"$BACKUP_FILE" >/dev/null
echo "[INFO] DB restore target confirmed: $ACTUAL_DATABASE (shadow)"
docker exec -i -e PGPASSWORD="$POSTGRES_PASSWORD" "$DB_CONTAINER" \
pg_restore --clean --if-exists --no-owner --no-acl -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
<"$BACKUP_FILE"
echo "[OK] DB restore done (docker exec): $BACKUP_FILE"
+51 -9
查看文件
@@ -16,19 +16,61 @@ fi
STORAGE_DIR_HOST="${2:-${STORAGE_DIR_HOST:-}}"
if [[ -z "$STORAGE_DIR_HOST" ]]; then
if [[ -n "${STORAGE_DIR:-}" && -d "${STORAGE_DIR:-}" ]]; then
STORAGE_DIR_HOST="$STORAGE_DIR"
else
STORAGE_DIR_HOST="$ROOT_DIR/deploy/trialrun/data/storage"
fi
echo "[ERR] explicit restore target is required as the second argument or STORAGE_DIR_HOST" >&2
exit 1
fi
mkdir -p "$STORAGE_DIR_HOST"
if [[ "${RESTORE_SCOPE:-}" != "shadow" ]]; then
echo "[ERR] RESTORE_SCOPE=shadow is required; production restore is not available from this command path" >&2
exit 1
fi
TARGET_PATH="$(realpath -m "$STORAGE_DIR_HOST")"
LIVE_STORAGE_PATH="$(realpath -m "$ROOT_DIR/deploy/trialrun/data/storage")"
if [[ "$TARGET_PATH" == "/" || "$TARGET_PATH" == "$ROOT_DIR" || "$TARGET_PATH" == "$LIVE_STORAGE_PATH" ]]; then
echo "[ERR] unsafe storage restore target" >&2
exit 1
fi
if [[ -d "$TARGET_PATH" && -n "$(find "$TARGET_PATH" -mindepth 1 -print -quit)" ]]; then
echo "[ERR] storage restore target must be empty: $TARGET_PATH" >&2
exit 1
fi
validate_archive_entries() {
local entry normalized part listing_line
tar -tzf "$BACKUP_FILE" >/dev/null
while IFS= read -r entry; do
if [[ -z "$entry" || "$entry" == /* ]]; then
echo "[ERR] unsafe archive path" >&2
return 1
fi
normalized="${entry#./}"
[[ -z "$normalized" || "$normalized" == "." ]] && continue
IFS='/' read -r -a path_parts <<<"$normalized"
for part in "${path_parts[@]}"; do
if [[ "$part" == ".." ]]; then
echo "[ERR] unsafe archive path segment" >&2
return 1
fi
done
done < <(tar -tzf "$BACKUP_FILE")
tar -tvzf "$BACKUP_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 archive links are not allowed" >&2
return 1
fi
done < <(tar -tvzf "$BACKUP_FILE")
}
validate_archive_entries
mkdir -p "$TARGET_PATH"
echo "[INFO] Storage restore start: $BACKUP_FILE"
echo "[INFO] Storage dir: $STORAGE_DIR_HOST"
echo "[INFO] Storage target confirmed: shadow"
tar -xzf "$BACKUP_FILE" -C "$STORAGE_DIR_HOST"
tar -xzf "$BACKUP_FILE" --no-same-owner --no-same-permissions -C "$TARGET_PATH"
echo "[OK] Storage restore done: $BACKUP_FILE"