115 lines
3.4 KiB
Bash
Executable File
115 lines
3.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
APP_DIR="${APP_DIR:-/opt/proxpanel}"
|
|
SECRET_FILE="${SECRET_FILE:-$APP_DIR/.backup.env}"
|
|
BACKUP_ROOT="${BACKUP_ROOT:-/opt/proxpanel-backups/daily}"
|
|
TMP_ROOT="${TMP_ROOT:-/tmp/proxpanel-restore-test}"
|
|
TEST_CONTAINER="${TEST_CONTAINER:-proxpanel-restore-test}"
|
|
PG_IMAGE="${PG_IMAGE:-postgres:16-alpine}"
|
|
PG_USER="${PG_USER:-proxpanel}"
|
|
PG_PASSWORD="${PG_PASSWORD:-restoretestpass}"
|
|
PG_DB="${PG_DB:-proxpanel_restore}"
|
|
|
|
cleanup() {
|
|
docker rm -f "$TEST_CONTAINER" >/dev/null 2>&1 || true
|
|
rm -rf "$TMP_ROOT"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$(date -u +'%Y-%m-%d %H:%M:%S UTC')" "$*"
|
|
}
|
|
|
|
die() {
|
|
printf '[ERROR] %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_file() {
|
|
[[ -f "$1" ]] || die "Missing required file: $1"
|
|
}
|
|
|
|
require_command() {
|
|
command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
|
|
}
|
|
|
|
find_latest_encrypted_backup() {
|
|
find "$BACKUP_ROOT" -mindepth 2 -maxdepth 2 -type f -name 'proxpanel.sql.enc' | sort | tail -n 1
|
|
}
|
|
|
|
wait_pg_ready() {
|
|
local tries=60
|
|
local i
|
|
for ((i=1; i<=tries; i++)); do
|
|
if docker exec "$TEST_CONTAINER" pg_isready -U "$PG_USER" -d "$PG_DB" >/dev/null 2>&1; then
|
|
return
|
|
fi
|
|
sleep 1
|
|
done
|
|
die "Restore test postgres did not become ready."
|
|
}
|
|
|
|
main() {
|
|
require_command docker
|
|
require_command openssl
|
|
require_command sha256sum
|
|
require_file "$SECRET_FILE"
|
|
|
|
# shellcheck disable=SC1090
|
|
source "$SECRET_FILE"
|
|
[[ -n "${BACKUP_ENCRYPTION_KEY:-}" ]] || die "BACKUP_ENCRYPTION_KEY is empty in $SECRET_FILE"
|
|
export BACKUP_ENCRYPTION_KEY
|
|
|
|
local encrypted_backup checksum_file latest_dir decrypted_sql
|
|
encrypted_backup="$(find_latest_encrypted_backup)"
|
|
[[ -n "$encrypted_backup" ]] || die "No encrypted backup found in $BACKUP_ROOT"
|
|
checksum_file="${encrypted_backup}.sha256"
|
|
require_file "$checksum_file"
|
|
|
|
latest_dir="$(dirname "$encrypted_backup")"
|
|
mkdir -p "$TMP_ROOT"
|
|
chmod 700 "$TMP_ROOT"
|
|
decrypted_sql="${TMP_ROOT}/restore.sql"
|
|
|
|
log "Verifying checksum for $encrypted_backup"
|
|
(cd "$latest_dir" && sha256sum -c "$(basename "$checksum_file")")
|
|
|
|
log "Decrypting latest backup"
|
|
openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
|
|
-in "$encrypted_backup" \
|
|
-out "$decrypted_sql" \
|
|
-pass env:BACKUP_ENCRYPTION_KEY
|
|
|
|
log "Starting isolated restore-test postgres container"
|
|
docker rm -f "$TEST_CONTAINER" >/dev/null 2>&1 || true
|
|
docker run -d --name "$TEST_CONTAINER" \
|
|
-e POSTGRES_USER="$PG_USER" \
|
|
-e POSTGRES_PASSWORD="$PG_PASSWORD" \
|
|
-e POSTGRES_DB="$PG_DB" \
|
|
"$PG_IMAGE" >/dev/null
|
|
|
|
wait_pg_ready
|
|
|
|
log "Applying restored SQL into test DB"
|
|
cat "$decrypted_sql" | docker exec -i "$TEST_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" >/dev/null
|
|
|
|
log "Running restore sanity checks"
|
|
local table_count required_table_count
|
|
table_count="$(
|
|
docker exec "$TEST_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -Atc \
|
|
"select count(*) from information_schema.tables where table_schema='public';"
|
|
)"
|
|
required_table_count="$(
|
|
docker exec "$TEST_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -Atc \
|
|
"select count(*) from information_schema.tables where table_schema='public' and table_name in ('User','Tenant','AuditLog');"
|
|
)"
|
|
|
|
[[ "${table_count:-0}" -ge 10 ]] || die "Restore sanity check failed (unexpected table count: $table_count)"
|
|
[[ "${required_table_count:-0}" -eq 3 ]] || die "Restore sanity check failed (required tables missing)"
|
|
|
|
log "Restore test passed (tables=$table_count)"
|
|
}
|
|
|
|
main "$@"
|