#!/usr/bin/env bash # Automated NPMplus Backup Script # Backs up database, proxy hosts, certificates, and configuration. # Usage: bash scripts/verify/backup-npmplus.sh [--dry-run] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[✓]${NC} $1"; } log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; } log_error() { echo -e "${RED}[✗]${NC} $1"; } cd "$PROJECT_ROOT" # Source dotenv (operator creds): repo root .env then smom-dbis-138/.env if [ -f .env ]; then set +euo pipefail source .env 2>/dev/null || true set -euo pipefail fi if [ -f smom-dbis-138/.env ]; then set +euo pipefail source smom-dbis-138/.env 2>/dev/null || true set -euo pipefail fi # Load ip-addresses.conf for fallbacks (before cd) [ -f "${PROJECT_ROOT}/config/ip-addresses.conf" ] && source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true # Configuration (from .env; NPMPLUS_* fall back to NPM_* / PROXMOX_HOST per .env.example) NPMPLUS_VMID="${NPMPLUS_VMID:-${NPM_VMID:-10233}}" NPMPLUS_HOST="${NPMPLUS_HOST:-${NPM_PROXMOX_HOST:-${PROXMOX_HOST:-${PROXMOX_HOST_R630_01:-192.168.11.11}}}}" NPM_URL="${NPM_URL:-https://${IP_NPMPLUS:-${IP_NPMPLUS:-192.168.11.167}}:81}" NPM_EMAIL="${NPM_EMAIL:-nsatoshi2007@hotmail.com}" NPM_PASSWORD="${NPM_PASSWORD:-}" DRY_RUN=false [[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true # Backup destination BACKUP_BASE_DIR="${BACKUP_DIR:-$PROJECT_ROOT/backups/npmplus}" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="$BACKUP_BASE_DIR/backup-$TIMESTAMP" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "💾 NPMplus Backup Script" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Validate NPM password (skip for dry-run) if [ -z "$NPM_PASSWORD" ] && [[ "$DRY_RUN" != true ]]; then log_error "NPM_PASSWORD environment variable is required" log_info "Set it in .env file or export it before running this script" exit 1 fi if [[ "$DRY_RUN" == true ]]; then log_info "DRY-RUN: would backup NPMplus (database, API exports, certs) to $BACKUP_DIR" log_info "Run without --dry-run to perform backup." exit 0 fi mkdir -p "$BACKUP_DIR" log_info "Backup destination: $BACKUP_DIR" echo "" # Step 1: Backup SQLite Database log_info "Step 1: Backing up NPMplus database..." DB_BACKUP_DIR="$BACKUP_DIR/database" mkdir -p "$DB_BACKUP_DIR" # Method 1: SQL dump log_info " Creating SQL dump..." ssh root@"$NPMPLUS_HOST" "pct exec $NPMPLUS_VMID -- bash -c ' if [ -f /data/database.sqlite ]; then sqlite3 /data/database.sqlite \".dump\" > /tmp/npm-database.sql 2>/dev/null || echo \"Database export may have issues\" cat /tmp/npm-database.sql else echo \"Database file not found\" fi '" > "$DB_BACKUP_DIR/database.sql" || { log_warn " SQL dump failed, trying direct copy..." } # Method 2: Direct file copy log_info " Copying database file..." ssh root@"$NPMPLUS_HOST" "pct exec $NPMPLUS_VMID -- cat /data/database.sqlite" > "$DB_BACKUP_DIR/database.sqlite" 2>/dev/null || { log_warn " Direct copy failed - database may not exist or container may be down" } if [ -s "$DB_BACKUP_DIR/database.sql" ] || [ -s "$DB_BACKUP_DIR/database.sqlite" ]; then log_success " Database backup completed" else log_warn " Database backup may be empty - check container status" fi # Step 2: Export Proxy Hosts via API log_info "Step 2: Exporting proxy hosts configuration..." API_BACKUP_DIR="$BACKUP_DIR/api" mkdir -p "$API_BACKUP_DIR" # Authenticate log_info " Authenticating to NPMplus API..." TOKEN_RESPONSE=$(curl -s -k -X POST "$NPM_URL/api/tokens" \ -H "Content-Type: application/json" \ -d "{\"identity\":\"$NPM_EMAIL\",\"secret\":\"$NPM_PASSWORD\"}") TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token // empty' 2>/dev/null || echo "") if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then log_error " Failed to authenticate to NPMplus API" log_warn " Skipping API-based exports" else log_success " Authenticated successfully" # Export proxy hosts log_info " Exporting proxy hosts..." curl -s -k -X GET "$NPM_URL/api/nginx/proxy-hosts" \ -H "Authorization: Bearer $TOKEN" | jq '.' > "$API_BACKUP_DIR/proxy_hosts.json" || { log_warn " Failed to export proxy hosts" } # Export certificates log_info " Exporting certificates..." curl -s -k -X GET "$NPM_URL/api/nginx/certificates" \ -H "Authorization: Bearer $TOKEN" | jq '.' > "$API_BACKUP_DIR/certificates.json" || { log_warn " Failed to export certificates" } # Export access lists log_info " Exporting access lists..." curl -s -k -X GET "$NPM_URL/api/nginx/access-lists" \ -H "Authorization: Bearer $TOKEN" | jq '.' > "$API_BACKUP_DIR/access_lists.json" 2>/dev/null || { log_warn " Failed to export access lists (may not be supported)" } log_success " API exports completed" fi # Step 3: Backup Certificate Files log_info "Step 3: Backing up certificate files..." CERT_BACKUP_DIR="$BACKUP_DIR/certificates" mkdir -p "$CERT_BACKUP_DIR" # List all certificates log_info " Listing certificates..." ssh root@"$NPMPLUS_HOST" "pct exec $NPMPLUS_VMID -- ls -1 /data/tls/certbot/live/ 2>/dev/null" > "$CERT_BACKUP_DIR/cert_list.txt" 2>/dev/null || { log_warn " Could not list certificates - path may differ" } # Copy certificate files if [ -s "$CERT_BACKUP_DIR/cert_list.txt" ]; then log_info " Copying certificate files..." while IFS= read -r cert_dir; do if [ -n "$cert_dir" ] && [ "$cert_dir" != "lost+found" ]; then mkdir -p "$CERT_BACKUP_DIR/$cert_dir" # Copy fullchain.pem ssh root@"$NPMPLUS_HOST" "pct exec $NPMPLUS_VMID -- cat /data/tls/certbot/live/$cert_dir/fullchain.pem" > "$CERT_BACKUP_DIR/$cert_dir/fullchain.pem" 2>/dev/null || { log_warn " Failed to copy fullchain.pem for $cert_dir" } # Copy privkey.pem ssh root@"$NPMPLUS_HOST" "pct exec $NPMPLUS_VMID -- cat /data/tls/certbot/live/$cert_dir/privkey.pem" > "$CERT_BACKUP_DIR/$cert_dir/privkey.pem" 2>/dev/null || { log_warn " Failed to copy privkey.pem for $cert_dir" } fi done < "$CERT_BACKUP_DIR/cert_list.txt" log_success " Certificate files backed up" else log_warn " No certificates found to backup" fi # Step 4: Backup Docker Volume (if accessible) log_info "Step 4: Attempting Docker volume backup..." VOLUME_BACKUP_DIR="$BACKUP_DIR/volumes" mkdir -p "$VOLUME_BACKUP_DIR" # Try to export Docker volume ssh root@"$NPMPLUS_HOST" "pct exec $NPMPLUS_VMID -- docker volume ls" > "$VOLUME_BACKUP_DIR/volume_list.txt" 2>/dev/null || { log_warn " Could not list Docker volumes" } # Step 5: Create backup manifest log_info "Step 5: Creating backup manifest..." cat > "$BACKUP_DIR/manifest.json" </dev/null || { log_warn " Compression failed - backup directory remains uncompressed" } if [ -f "backup-$TIMESTAMP.tar.gz" ]; then BACKUP_SIZE=$(du -h "backup-$TIMESTAMP.tar.gz" | cut -f1) log_success " Backup compressed: backup-$TIMESTAMP.tar.gz ($BACKUP_SIZE)" # Optionally remove uncompressed directory # rm -rf "backup-$TIMESTAMP" fi echo "" log_success "Backup completed successfully!" log_info "Backup location: $BACKUP_DIR" if [ -f "$BACKUP_BASE_DIR/backup-$TIMESTAMP.tar.gz" ]; then log_info "Compressed backup: $BACKUP_BASE_DIR/backup-$TIMESTAMP.tar.gz" fi echo ""