feat(scripts): add scripts for backup/restore + upload to s3 storage
This commit is contained in:
135
scripts/restore.sh
Normal file
135
scripts/restore.sh
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
restore.sh <backup-archive.tar.gz>
|
||||
|
||||
Environment:
|
||||
DEPLOY_ROOT Deployment directory. Defaults to ~/guilan-ace-deployment.
|
||||
RESTORE_SKIP_ENV Set to 1 to keep current .env files.
|
||||
RESTORE_SKIP_MEDIA Set to 1 to keep current media files.
|
||||
RESTORE_SKIP_DB Set to 1 to keep current database.
|
||||
|
||||
This script restores:
|
||||
- deployment, backend, and frontend .env files
|
||||
- media files into the Django media volume
|
||||
- PostgreSQL database from database.sql
|
||||
|
||||
Database restore is destructive unless RESTORE_SKIP_DB=1 is set.
|
||||
EOF
|
||||
}
|
||||
|
||||
log() {
|
||||
printf '[restore] %s\n' "$*"
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf '[restore] %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_file() {
|
||||
local path="$1"
|
||||
[[ -f "$path" ]] || fail "Required file not found: $path"
|
||||
}
|
||||
|
||||
compose() {
|
||||
docker compose --env-file "$DEPLOY_ENV" -f "$DEPLOY_ROOT/docker-compose.yml" "$@"
|
||||
}
|
||||
|
||||
wait_for_db() {
|
||||
compose exec -T db sh -c '
|
||||
set -eu
|
||||
: "${DB_USER:?DB_USER is missing}"
|
||||
: "${DB_NAME:?DB_NAME is missing}"
|
||||
until pg_isready --username="$DB_USER" --dbname="$DB_NAME" --host=127.0.0.1; do
|
||||
sleep 1
|
||||
done
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
restore_env_if_present() {
|
||||
local source_path="$1"
|
||||
local target_path="$2"
|
||||
|
||||
if [[ -f "$source_path" ]]; then
|
||||
mkdir -p "$(dirname "$target_path")"
|
||||
cp "$source_path" "$target_path"
|
||||
chmod 600 "$target_path" || true
|
||||
log "Restored $target_path"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ARCHIVE_PATH="${1:-}"
|
||||
[[ -n "$ARCHIVE_PATH" ]] || {
|
||||
usage
|
||||
exit 1
|
||||
}
|
||||
|
||||
DEPLOY_ROOT="${DEPLOY_ROOT:-$HOME/guilan-ace-deployment}"
|
||||
BACKEND_ENV="$DEPLOY_ROOT/backend/guilan-ace-backend/.env"
|
||||
FRONTEND_ENV="$DEPLOY_ROOT/frontend/guilan-ace-frontend/.env"
|
||||
DEPLOY_ENV="$DEPLOY_ROOT/.env"
|
||||
|
||||
require_file "$ARCHIVE_PATH"
|
||||
require_file "$DEPLOY_ROOT/docker-compose.yml"
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
log "Extracting backup archive"
|
||||
tar -C "$WORK_DIR" -xzf "$ARCHIVE_PATH"
|
||||
|
||||
if [[ "${RESTORE_SKIP_ENV:-0}" != "1" ]]; then
|
||||
log "Restoring environment files"
|
||||
restore_env_if_present "$WORK_DIR/env/deployment.env" "$DEPLOY_ENV"
|
||||
restore_env_if_present "$WORK_DIR/env/backend.env" "$BACKEND_ENV"
|
||||
restore_env_if_present "$WORK_DIR/env/frontend.env" "$FRONTEND_ENV"
|
||||
else
|
||||
log "Skipping environment restore"
|
||||
fi
|
||||
|
||||
require_file "$BACKEND_ENV"
|
||||
require_file "$DEPLOY_ENV"
|
||||
|
||||
log "Checking Docker Compose configuration"
|
||||
compose config -q
|
||||
|
||||
if [[ "${RESTORE_SKIP_MEDIA:-0}" != "1" ]]; then
|
||||
require_file "$WORK_DIR/media.tar.gz"
|
||||
log "Restoring media files into Django media volume"
|
||||
compose run --rm --no-deps --entrypoint sh web -c 'rm -rf /app/media/* /app/media/.[!.]* /app/media/..?* 2>/dev/null || true'
|
||||
compose run --rm --no-deps -T --entrypoint sh web -c 'mkdir -p /app/media && tar -C /app/media -xzf -' < "$WORK_DIR/media.tar.gz"
|
||||
else
|
||||
log "Skipping media restore"
|
||||
fi
|
||||
|
||||
if [[ "${RESTORE_SKIP_DB:-0}" != "1" ]]; then
|
||||
require_file "$WORK_DIR/database.sql"
|
||||
log "Starting database service"
|
||||
compose up -d db
|
||||
wait_for_db
|
||||
|
||||
log "Recreating and restoring database"
|
||||
compose exec -T db sh -c '
|
||||
set -eu
|
||||
: "${DB_USER:?DB_USER is missing}"
|
||||
: "${DB_NAME:?DB_NAME is missing}"
|
||||
psql --username="$DB_USER" --dbname=postgres --command="SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '\''$DB_NAME'\'' AND pid <> pg_backend_pid();" >/dev/null
|
||||
dropdb --username="$DB_USER" --if-exists "$DB_NAME"
|
||||
createdb --username="$DB_USER" "$DB_NAME"
|
||||
psql --username="$DB_USER" --dbname="$DB_NAME"
|
||||
' < "$WORK_DIR/database.sql"
|
||||
else
|
||||
log "Skipping database restore"
|
||||
fi
|
||||
|
||||
log "Restore completed"
|
||||
log "Run: docker compose up -d --build"
|
||||
Reference in New Issue
Block a user