#!/bin/bash # ============================================================================= # Server Monitorizare v2 - Migration Restore Script # ============================================================================= # Restores a backup archive produced by backup_system.sh onto a new server. # # Usage: # ./scripts/restore_backup.sh [--db-only] [--ansible-only] # # What it does: # 1. Extracts the archive to a temp staging area # 2. Restores the SQLite database (with a safety backup of any existing DB) # 3. Restores Ansible inventory, playbooks, ssh_keys, ansible.cfg # 4. Restores WMT releases and ansible_settings.json # 5. Fixes SSH key permissions (must be 600 for Ansible) # 6. Prints post-restore checklist # # For a fresh server install, run this AFTER: # pip install -r requirements.txt # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_DIR="$(dirname "$SCRIPT_DIR")" TIMESTAMP=$(date +"%Y%m%d_%H%M%S") # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- log() { echo "[$(date '+%H:%M:%S')] $*"; } ok() { echo " [OK] $*"; } warn() { echo " [WARN] $*"; } err() { echo " [ERROR] $*" >&2; } usage() { echo "Usage: $0 [--db-only | --ansible-only]" echo "" echo "Options:" echo " --db-only Restore only the SQLite database" echo " --ansible-only Restore only Ansible files (inventory, playbooks, ssh_keys)" echo "" echo "Example:" echo " $0 data/backups/smv2_backup_20260708_020000.tar.gz" exit 1 } # --------------------------------------------------------------------------- # Parse args # --------------------------------------------------------------------------- ARCHIVE="${1:-}" MODE="all" # all | db-only | ansible-only YES=0 # set to 1 by --yes to skip interactive confirmation [[ -n "$ARCHIVE" ]] || usage [[ -f "$ARCHIVE" ]] || { err "Archive not found: $ARCHIVE"; exit 1; } for arg in "${@:2}"; do case "$arg" in --db-only) MODE="db-only" ;; --ansible-only) MODE="ansible-only" ;; --yes|-y) YES=1 ;; *) err "Unknown option: $arg"; usage ;; esac done # --------------------------------------------------------------------------- # Extract archive # --------------------------------------------------------------------------- log "Extracting archive: $ARCHIVE" STAGE_DIR=$(mktemp -d) trap 'rm -rf "$STAGE_DIR"' EXIT tar -xzf "$ARCHIVE" -C "$STAGE_DIR" log "Extracted to staging: $STAGE_DIR" # Show manifest if present if [[ -f "$STAGE_DIR/BACKUP_MANIFEST.txt" ]]; then echo "" echo "=== Backup Manifest ===" cat "$STAGE_DIR/BACKUP_MANIFEST.txt" echo "========================" echo "" fi # --------------------------------------------------------------------------- # Confirm before proceeding # --------------------------------------------------------------------------- if [[ "$YES" == "1" ]]; then log "Non-interactive mode (--yes) — proceeding automatically." else read -r -p "Proceed with restore to $APP_DIR ? [y/N] " confirm [[ "${confirm,,}" == "y" ]] || { log "Aborted."; exit 0; } fi # --------------------------------------------------------------------------- # Restore: SQLite database # --------------------------------------------------------------------------- restore_db() { local src="$STAGE_DIR/enhanced_monitoring.db" local dst="$APP_DIR/data/enhanced_monitoring.db" if [[ ! -f "$src" ]]; then warn "No database file in archive — skipping DB restore" return fi mkdir -p "$APP_DIR/data" # Safety backup of any existing database if [[ -f "$dst" ]]; then local safety_bak="${dst}.pre_restore_${TIMESTAMP}.bak" cp "$dst" "$safety_bak" ok "Existing DB saved as safety backup: $(basename "$safety_bak")" fi cp "$src" "$dst" ok "Database restored: $dst ($(du -sh "$dst" | cut -f1))" # Quick sanity check local dev_count; dev_count=$(sqlite3 "$dst" "SELECT COUNT(*) FROM devices;" 2>/dev/null || echo "?") ok "Devices in restored DB: $dev_count" } # --------------------------------------------------------------------------- # Restore: Ansible files # --------------------------------------------------------------------------- restore_ansible() { local src="$STAGE_DIR/ansible" if [[ ! -d "$src" ]]; then warn "No ansible/ directory in archive — skipping" return fi # Backup existing ansible dir if present if [[ -d "$APP_DIR/ansible" ]]; then local bak="$APP_DIR/ansible.pre_restore_${TIMESTAMP}.bak" mv "$APP_DIR/ansible" "$bak" ok "Existing ansible/ moved to: $(basename "$bak")" fi cp -a "$src" "$APP_DIR/ansible" # Critical: SSH private key must be 600 or Ansible rejects it if [[ -f "$APP_DIR/ansible/ssh_keys/app_key" ]]; then chmod 600 "$APP_DIR/ansible/ssh_keys/app_key" ok "SSH private key permissions set to 600" fi ok "Ansible directory restored" ok "Playbooks: $(ls "$APP_DIR/ansible/playbooks/"*.yml 2>/dev/null | wc -l) files" ok "Inventory hosts: $(grep -c 'ansible_host:' "$APP_DIR/ansible/inventory/dynamic_inventory.yaml" 2>/dev/null || echo 0)" } # --------------------------------------------------------------------------- # Restore: WMT releases # --------------------------------------------------------------------------- restore_wmt_releases() { local src="$STAGE_DIR/wmt_releases" if [[ ! -d "$src" ]]; then return; fi mkdir -p "$APP_DIR/data/wmt_releases" # Only copy files not already present (don't overwrite newer releases) for f in "$src"/*; do local fname; fname=$(basename "$f") if [[ ! -f "$APP_DIR/data/wmt_releases/$fname" ]]; then cp "$f" "$APP_DIR/data/wmt_releases/" ok "WMT release restored: $fname" else ok "WMT release already present (skipped): $fname" fi done } # --------------------------------------------------------------------------- # Restore: ansible_settings.json # --------------------------------------------------------------------------- restore_settings() { local src="$STAGE_DIR/ansible_settings.json" if [[ ! -f "$src" ]]; then return; fi local dst="$APP_DIR/data/ansible_settings.json" if [[ -f "$dst" ]]; then cp "$dst" "${dst}.pre_restore_${TIMESTAMP}.bak" fi cp "$src" "$dst" ok "ansible_settings.json restored" } # --------------------------------------------------------------------------- # Restore: config/ # --------------------------------------------------------------------------- restore_config() { local src="$STAGE_DIR/config" if [[ ! -d "$src" ]]; then return; fi if [[ -d "$APP_DIR/config" ]]; then ok "config/ already present — skipping (manual merge may be needed)" warn "Backup config is in: $STAGE_DIR/config/ (not auto-applied)" return fi cp -a "$src" "$APP_DIR/config" ok "config/ restored" } # --------------------------------------------------------------------------- # Dispatch # --------------------------------------------------------------------------- log "Restore mode: $MODE" case "$MODE" in all) restore_db restore_ansible restore_wmt_releases restore_settings restore_config ;; db-only) restore_db ;; ansible-only) restore_ansible restore_settings ;; esac # --------------------------------------------------------------------------- # Post-restore checklist # --------------------------------------------------------------------------- echo "" echo "================================================================" echo " Post-Restore Checklist" echo "================================================================" echo " 1. Install Python dependencies (if fresh server):" echo " cd $APP_DIR && pip install -r requirements.txt" echo "" echo " 2. Start the application:" echo " cd $APP_DIR && python main.py" echo "" echo " 3. Verify device list loads at: http://:5000" echo "" echo " 4. Test Ansible connectivity:" echo " cd $APP_DIR/ansible && ansible -i inventory/dynamic_inventory.yaml all -m ping --limit 1" echo "" echo " 5. If the new server IP has changed, update any WMT devices" echo " pointing to the old server URL (wmt_global_config.server_log_url)" echo " via the admin UI or:" echo " sqlite3 data/enhanced_monitoring.db \\" echo " \"UPDATE wmt_global_config SET server_log_url='http://:5000/api/log';\"" echo "" echo " 6. Re-install cron backup on the new server:" echo " bash $SCRIPT_DIR/backup_system.sh install-cron" echo "" echo "================================================================" log "Restore completed."