05/19 Enhance codes 4
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# /home/spuser/PassKeeper/scripts/restore_db.sh
|
||||
#
|
||||
# Restore a PassKeeper MySQL backup created by backup_db.sh.
|
||||
#
|
||||
# SAFETY FEATURES:
|
||||
# - Requires explicit confirmation before overwriting the live database.
|
||||
# - Creates a pre-restore safety dump of the current DB before touching it.
|
||||
# - Verifies the backup file is a valid gzip before proceeding.
|
||||
# - Stops the PassKeeper service before restore, restarts it after.
|
||||
# - Runs flask db upgrade after restore to ensure the schema is current.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/restore_db.sh /path/to/passkeeper_20260101_020000.sql.gz
|
||||
#
|
||||
# Dry-run (verify only, no changes):
|
||||
# DRY_RUN=1 bash scripts/restore_db.sh /path/to/backup.sql.gz
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Configuration ──────────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(dirname "$(realpath "$0")")"
|
||||
PROJECT_DIR="$(realpath "$SCRIPT_DIR/..")"
|
||||
ENV_FILE="$PROJECT_DIR/.env"
|
||||
SERVICE_NAME="${PASSKEEPER_SERVICE:-passkeeper}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/home/spuser/backups/passkeeper}"
|
||||
LOG_FILE="${BACKUP_DIR}/restore.log"
|
||||
DRY_RUN="${DRY_RUN:-0}"
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────────────────────────
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
|
||||
die() { log "ERROR $*"; exit 1; }
|
||||
info() { log "INFO $*"; }
|
||||
|
||||
# ── Argument check ─────────────────────────────────────────────────────────────
|
||||
[[ $# -lt 1 ]] && die "Usage: $0 <backup_file.sql.gz>"
|
||||
BACKUP_FILE="$(realpath "$1")"
|
||||
[[ -f "$BACKUP_FILE" ]] || die "Backup file not found: $BACKUP_FILE"
|
||||
|
||||
# ── Load .env ─────────────────────────────────────────────────────────────────
|
||||
[[ -f "$ENV_FILE" ]] || die ".env file not found at $ENV_FILE"
|
||||
set -o allexport
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +o allexport
|
||||
|
||||
DB_HOST="${MYSQL_HOST:-127.0.0.1}"
|
||||
DB_PORT="${MYSQL_PORT:-3306}"
|
||||
DB_USER="${MYSQL_USER:-passkeeper}"
|
||||
DB_PASS="${MYSQL_PASSWORD}"
|
||||
DB_NAME="${MYSQL_DB:-passkeeper}"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
info "=== PassKeeper database restore ==="
|
||||
info "Backup file : $BACKUP_FILE"
|
||||
info "Target DB : $DB_NAME @ $DB_HOST:$DB_PORT"
|
||||
info "Service : $SERVICE_NAME"
|
||||
[[ "$DRY_RUN" == "1" ]] && info "MODE : DRY RUN — no changes will be made"
|
||||
|
||||
# ── Validate backup file ───────────────────────────────────────────────────────
|
||||
info "Validating backup file integrity…"
|
||||
if ! gzip -t "$BACKUP_FILE" 2>/dev/null; then
|
||||
die "Backup file failed gzip integrity check: $BACKUP_FILE"
|
||||
fi
|
||||
BACKUP_SIZE="$(du -sh "$BACKUP_FILE" | cut -f1)"
|
||||
info "Backup size : $BACKUP_SIZE — gzip OK"
|
||||
|
||||
# ── Confirmation ───────────────────────────────────────────────────────────────
|
||||
if [[ "$DRY_RUN" != "1" ]]; then
|
||||
echo ""
|
||||
echo " ⚠️ WARNING: This will OVERWRITE the live database '$DB_NAME'."
|
||||
echo " All current data will be replaced with the contents of:"
|
||||
echo " $BACKUP_FILE"
|
||||
echo ""
|
||||
read -r -p " Type 'yes' to continue: " CONFIRM
|
||||
[[ "$CONFIRM" == "yes" ]] || { info "Restore cancelled by user."; exit 0; }
|
||||
fi
|
||||
|
||||
[[ "$DRY_RUN" == "1" ]] && { info "Dry run complete — backup file is valid."; exit 0; }
|
||||
|
||||
# ── Pre-restore safety dump ────────────────────────────────────────────────────
|
||||
SAFETY_TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
||||
SAFETY_FILE="${BACKUP_DIR}/pre_restore_safety_${SAFETY_TIMESTAMP}.sql.gz"
|
||||
info "Creating pre-restore safety dump → $SAFETY_FILE"
|
||||
MYSQL_PWD="$DB_PASS" mysqldump \
|
||||
--host="$DB_HOST" \
|
||||
--port="$DB_PORT" \
|
||||
--user="$DB_USER" \
|
||||
--single-transaction \
|
||||
--no-tablespaces \
|
||||
"$DB_NAME" \
|
||||
| gzip -9 > "$SAFETY_FILE" \
|
||||
|| die "Safety dump failed — aborting restore. Database is unchanged."
|
||||
info "Safety dump complete: $(du -sh "$SAFETY_FILE" | cut -f1)"
|
||||
|
||||
# ── Stop service ───────────────────────────────────────────────────────────────
|
||||
info "Stopping $SERVICE_NAME…"
|
||||
sudo systemctl stop "$SERVICE_NAME" || die "Failed to stop $SERVICE_NAME"
|
||||
info "$SERVICE_NAME stopped"
|
||||
|
||||
RESTORE_OK=0
|
||||
|
||||
# ── Restore ────────────────────────────────────────────────────────────────────
|
||||
info "Restoring database from backup…"
|
||||
if MYSQL_PWD="$DB_PASS" gunzip -c "$BACKUP_FILE" | mysql \
|
||||
--host="$DB_HOST" \
|
||||
--port="$DB_PORT" \
|
||||
--user="$DB_USER" \
|
||||
"$DB_NAME"; then
|
||||
info "Database restore complete"
|
||||
RESTORE_OK=1
|
||||
else
|
||||
log "ERROR Database restore failed — attempting to roll back from safety dump"
|
||||
if MYSQL_PWD="$DB_PASS" gunzip -c "$SAFETY_FILE" | mysql \
|
||||
--host="$DB_HOST" \
|
||||
--port="$DB_PORT" \
|
||||
--user="$DB_USER" \
|
||||
"$DB_NAME"; then
|
||||
log "WARN Rolled back to pre-restore state from $SAFETY_FILE"
|
||||
else
|
||||
log "ERROR Rollback also failed — database may be in an inconsistent state"
|
||||
log "ERROR Manual recovery required. Safety dump: $SAFETY_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Run migrations ─────────────────────────────────────────────────────────────
|
||||
if [[ "$RESTORE_OK" == "1" ]]; then
|
||||
info "Running flask db upgrade to ensure schema is current…"
|
||||
cd "$PROJECT_DIR"
|
||||
if source .venv/bin/activate 2>/dev/null; then
|
||||
flask db upgrade && info "flask db upgrade OK" || \
|
||||
log "WARN flask db upgrade failed — check migrations manually"
|
||||
deactivate 2>/dev/null || true
|
||||
else
|
||||
log "WARN Could not activate .venv — skipping flask db upgrade"
|
||||
log "WARN Run 'flask db upgrade' manually before restarting the service"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Restart service ────────────────────────────────────────────────────────────
|
||||
info "Starting $SERVICE_NAME…"
|
||||
sudo systemctl start "$SERVICE_NAME" && info "$SERVICE_NAME started" || \
|
||||
die "$SERVICE_NAME failed to start — check 'journalctl -u $SERVICE_NAME -n 50'"
|
||||
|
||||
# ── Summary ────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
if [[ "$RESTORE_OK" == "1" ]]; then
|
||||
info "=== Restore finished successfully ==="
|
||||
info "Restored from : $BACKUP_FILE"
|
||||
info "Safety dump : $SAFETY_FILE (kept for 24 h — delete manually when satisfied)"
|
||||
else
|
||||
die "=== Restore FAILED — see log for details: $LOG_FILE ==="
|
||||
fi
|
||||
Reference in New Issue
Block a user