Files
Momento/scripts/backup/restore.sh
Antigravity 2db12adffc
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Test & Build (push) Has been cancelled
chore(prod): support .env.docker in crons, fix WAL mapping and add canvas copy in Dockerfile
2026-05-20 19:21:49 +00:00

122 lines
4.5 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
# Load environment variables from .env.docker if it exists
if [ -f "/opt/memento/.env.docker" ]; then
set -a
source "/opt/memento/.env.docker"
set +a
fi
BACKUP_DIR="/opt/memento/backups"
SNAPSHOT_DIR="$BACKUP_DIR/snapshots"
PG_CONTAINER="memento-postgres"
PG_USER="${POSTGRES_USER:-memento}"
PG_DB="${POSTGRES_DB:-memento}"
LOG_FILE="$BACKUP_DIR/backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
RESTORE_TYPE="${1:-snapshot}"
PITR_TIME="${2:-}"
if [ "$RESTORE_TYPE" = "pitr" ] && [ -z "$PITR_TIME" ]; then
echo "Usage: $0 pitr \"2026-05-17 14:30:00\""
echo " $0 snapshot [snapshot_file.sql.gz]"
exit 1
fi
log "=== EMERGENCY PRE-RESTORE: Dumping current state ==="
PRE_RESTORE="$BACKUP_DIR/pre-restore-$(date +%Y%m%d-%H%M%S).sql.gz"
docker exec "$PG_CONTAINER" pg_dump -U "$PG_USER" -d "$PG_DB" --format=custom 2>/dev/null | gzip > "$PRE_RESTORE" || true
APP_CONTAINER="${APP_CONTAINER:-memento-web}"
log "Stopping app container: $APP_CONTAINER"
docker stop "$APP_CONTAINER" 2>/dev/null || true
if [ "$RESTORE_TYPE" = "snapshot" ]; then
SNAPSHOT_FILE="${2:-$(ls -t "$SNAPSHOT_DIR"/*.sql.gz 2>/dev/null | head -1)}"
if [ -z "$SNAPSHOT_FILE" ] || [ ! -f "$SNAPSHOT_FILE" ]; then
log "ERROR: No snapshot file found!"
exit 1
fi
log "Restoring from snapshot: $SNAPSHOT_FILE"
FILE_SIZE=$(stat -c%s "$SNAPSHOT_FILE")
log "File size: $(( FILE_SIZE / 1024 ))KB"
log "Dropping existing connections..."
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -c \
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$PG_DB' AND pid <> pg_backend_pid();" 2>/dev/null || true
log "Terminating connections to template..."
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d postgres -c \
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$PG_DB';" 2>/dev/null || true
log "Recreating database..."
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d postgres -c "DROP DATABASE IF EXISTS ${PG_DB}_restore;"
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d postgres -c "CREATE DATABASE ${PG_DB}_restore;"
log "Restoring data..."
gunzip -c "$SNAPSHOT_FILE" | docker exec -i "$PG_CONTAINER" pg_restore -U "$PG_USER" -d "${PG_DB}_restore" --no-owner --no-privileges 2>/dev/null || true
log "Swapping databases..."
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d postgres -c "DROP DATABASE IF EXISTS ${PG_DB}_old;"
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d postgres -c "ALTER DATABASE \"$PG_DB\" RENAME TO ${PG_DB}_old;"
docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d postgres -c "ALTER DATABASE ${PG_DB}_restore RENAME TO \"$PG_DB\";"
log "Running migrations..."
docker exec "$APP_CONTAINER" node ./node_modules/prisma/build/index.js migrate deploy 2>/dev/null || true
elif [ "$RESTORE_TYPE" = "pitr" ]; then
log "PITR restore to: $PITR_TIME"
log "Stopping PostgreSQL..."
docker stop "$PG_CONTAINER"
log "WARNING: PITR restore requires manual intervention."
log "Steps:"
log " 1. Copy latest base backup to PGDATA"
log " 2. Create recovery.signal in PGDATA"
log " 3. Set restore_command and recovery_target_time in postgresql.conf"
log " 4. Start PostgreSQL"
log " 5. App will reconnect automatically"
log ""
log "Recovery target: $PITR_TIME"
log "WAL files location: $BACKUP_DIR/wal/"
exit 0
fi
log "Verifying restore..."
NOTE_COUNT=$(docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -c "SELECT COUNT(*) FROM \"Note\";" 2>/dev/null | tr -d ' ')
NOTEBOOK_COUNT=$(docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -c "SELECT COUNT(*) FROM \"Notebook\";" 2>/dev/null | tr -d ' ')
USER_COUNT=$(docker exec "$PG_CONTAINER" psql -U "$PG_USER" -d "$PG_DB" -t -c "SELECT COUNT(*) FROM \"User\";" 2>/dev/null | tr -d ' ')
log "After restore: $NOTE_COUNT notes, $NOTEBOOK_COUNT notebooks, $USER_COUNT users"
if [ "$NOTE_COUNT" -eq 0 ]; then
log "ERROR: 0 notes after restore! Something went wrong."
exit 1
fi
log "Starting app container..."
docker start "$APP_CONTAINER" 2>/dev/null || true
HEALTHY=false
for i in $(seq 1 36); do
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "http://localhost:3000/api/build-info" 2>/dev/null || echo "000")
if [ "$CODE" != "000" ] && [ "$CODE" -lt 500 ]; then
HEALTHY=true
break
fi
sleep 5
done
if [ "$HEALTHY" = true ]; then
log "=== RESTORE SUCCESSFUL === App is healthy"
else
log "WARNING: App not healthy after restore. Check manually."
fi