122 lines
4.5 KiB
Bash
Executable File
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
|