#!/usr/bin/env bash set -Eeuo pipefail usage() { cat <<'EOF' Usage: restore.sh 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"