#!/bin/bash # Automated backup of NPMplus configuration and data # Backs up database, proxy hosts, certificates, and configuration files set -euo pipefail # Load IP configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" source "${PROJECT_ROOT}/config/ip-addresses.conf" 2>/dev/null || true SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" if [ -f "$PROJECT_ROOT/.env" ]; then set +euo pipefail source "$PROJECT_ROOT/.env" 2>/dev/null || true set -euo pipefail fi NPMPLUS_HOST="${NPMPLUS_HOST:-192.168.11.11}" NPMPLUS_VMID="${NPMPLUS_VMID:-10233}" NPM_URL="${NPM_URL:-https://${IP_NPMPLUS_ETH0:-192.168.11.166}:81}" NPM_EMAIL="${NPM_EMAIL:-nsatoshi2007@hotmail.com}" NPM_PASSWORD="${NPM_PASSWORD:-}" BACKUP_DEST="${BACKUP_DEST:-$PROJECT_ROOT/backups/npmplus}" RETENTION_DAYS="${RETENTION_DAYS:-30}" # 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"; } TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_DIR="$BACKUP_DEST/npmplus-backup-$TIMESTAMP" mkdir -p "$BACKUP_DIR" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "💾 NPMplus Backup" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" log_info "Backup directory: $BACKUP_DIR" log_info "NPMplus Host: $NPMPLUS_HOST" log_info "NPMplus VMID: $NPMPLUS_VMID" # 1. Backup Database log_info "Backing up NPMplus database..." DB_BACKUP_SUCCESS=false # Try direct file copy first if ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "pct exec $NPMPLUS_VMID -- docker cp npmplus:/data/database.sqlite /tmp/db-backup.sqlite 2>/dev/null"; then scp -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST:/tmp/db-backup.sqlite" \ "$BACKUP_DIR/database.sqlite" 2>/dev/null && \ ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" "rm -f /tmp/db-backup.sqlite" 2>/dev/null && \ DB_BACKUP_SUCCESS=true fi # Try SQL dump as fallback if [ "$DB_BACKUP_SUCCESS" = false ]; then DB_DUMP=$(ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "pct exec $NPMPLUS_VMID -- docker exec npmplus sqlite3 /data/database.sqlite '.dump' 2>/dev/null" || echo "") if [ -n "$DB_DUMP" ] && ! echo "$DB_DUMP" | grep -q "executable file not found"; then echo "$DB_DUMP" > "$BACKUP_DIR/database.sql" DB_BACKUP_SUCCESS=true fi fi if [ "$DB_BACKUP_SUCCESS" = true ]; then DB_SIZE=$(stat -f%z "$BACKUP_DIR/database.sqlite" 2>/dev/null || \ stat -c%s "$BACKUP_DIR/database.sqlite" 2>/dev/null || \ stat -c%s "$BACKUP_DIR/database.sql" 2>/dev/null || echo "0") log_success "Database backed up ($DB_SIZE bytes)" else log_warn "Database backup failed - database may be empty or inaccessible" fi # 2. Backup Proxy Hosts via API if [ -n "$NPM_PASSWORD" ]; then log_info "Backing up proxy hosts via API..." TOKEN_RESPONSE=$(curl -s -k -X POST "$NPM_URL/api/tokens" \ -H "Content-Type: application/json" \ -d "{\"identity\":\"$NPM_EMAIL\",\"secret\":\"$NPM_PASSWORD\"}" 2>/dev/null || echo "{}") TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token // empty' 2>/dev/null || echo "") if [ -n "$TOKEN" ] && [ "$TOKEN" != "null" ]; then # Export proxy hosts PROXY_HOSTS_JSON=$(curl -s -k -X GET "$NPM_URL/api/nginx/proxy-hosts" \ -H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]") echo "$PROXY_HOSTS_JSON" | jq '.' > "$BACKUP_DIR/proxy_hosts.json" 2>/dev/null || echo "[]" > "$BACKUP_DIR/proxy_hosts.json" PROXY_COUNT=$(echo "$PROXY_HOSTS_JSON" | jq '. | length' 2>/dev/null || echo "0") log_success "Proxy hosts backed up ($PROXY_COUNT hosts)" # Export certificates metadata CERTIFICATES_JSON=$(curl -s -k -X GET "$NPM_URL/api/nginx/certificates" \ -H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "[]") echo "$CERTIFICATES_JSON" | jq '.' > "$BACKUP_DIR/certificates.json" 2>/dev/null || echo "[]" > "$BACKUP_DIR/certificates.json" CERT_COUNT=$(echo "$CERTIFICATES_JSON" | jq '. | length' 2>/dev/null || echo "0") log_success "Certificates metadata backed up ($CERT_COUNT certificates)" else log_warn "API authentication failed - skipping API-based backups" fi else log_warn "NPM_PASSWORD not set - skipping API-based backups" fi # 3. Backup Certificate Files log_info "Backing up certificate files..." CERT_BACKUP_DIR="$BACKUP_DIR/certificates" mkdir -p "$CERT_BACKUP_DIR" # Find certificate path CERT_PATH=$(ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "pct exec $NPMPLUS_VMID -- docker volume inspect npmplus_data --format '{{.Mountpoint}}' 2>/dev/null" || echo "") if [ -n "$CERT_PATH" ] && [ "$CERT_PATH" != "null" ]; then if ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "test -d $CERT_PATH/tls/certbot/live 2>/dev/null"; then CERT_SOURCE="$CERT_PATH/tls/certbot/live" elif ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "test -d $CERT_PATH/certbot/live 2>/dev/null"; then CERT_SOURCE="$CERT_PATH/certbot/live" else CERT_SOURCE="" fi if [ -n "$CERT_SOURCE" ]; then rsync -avz --delete \ -e "ssh -o StrictHostKeyChecking=no" \ root@"$NPMPLUS_HOST:$CERT_SOURCE/" \ "$CERT_BACKUP_DIR/" 2>&1 | while IFS= read -r line; do log_info "$line" done CERT_COUNT=$(find "$CERT_BACKUP_DIR" -type d -mindepth 1 -maxdepth 1 2>/dev/null | wc -l || echo "0") log_success "Certificate files backed up ($CERT_COUNT certificate directories)" else log_warn "Certificate directory not found" fi else log_warn "Could not determine certificate path" fi # 4. Backup Nginx Configuration Files log_info "Backing up Nginx configuration files..." NGINX_BACKUP_DIR="$BACKUP_DIR/nginx" mkdir -p "$NGINX_BACKUP_DIR" if ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "pct exec $NPMPLUS_VMID -- docker exec npmplus test -d /data/nginx 2>/dev/null"; then ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" \ "pct exec $NPMPLUS_VMID -- docker exec npmplus tar czf /tmp/nginx-config.tar.gz -C /data nginx 2>/dev/null" && \ scp -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST:/tmp/nginx-config.tar.gz" \ "$NGINX_BACKUP_DIR/nginx-config.tar.gz" 2>/dev/null && \ ssh -o StrictHostKeyChecking=no root@"$NPMPLUS_HOST" "rm -f /tmp/nginx-config.tar.gz" 2>/dev/null && \ log_success "Nginx configuration backed up" else log_warn "Nginx configuration directory not found" fi # 5. Create Backup Manifest log_info "Creating backup manifest..." cat > "$BACKUP_DIR/manifest.txt" <&1 | while IFS= read -r line; do log_info "$line" done if [ -f "npmplus-backup-$TIMESTAMP.tar.gz" ]; then COMPRESSED_SIZE=$(stat -f%z "npmplus-backup-$TIMESTAMP.tar.gz" 2>/dev/null || \ stat -c%s "npmplus-backup-$TIMESTAMP.tar.gz" 2>/dev/null || echo "0") log_success "Backup compressed ($(numfmt --to=iec-i --suffix=B $COMPRESSED_SIZE 2>/dev/null || echo "$COMPRESSED_SIZE bytes"))" # Remove uncompressed directory rm -rf "npmplus-backup-$TIMESTAMP" else log_warn "Compression failed - keeping uncompressed backup" fi # 7. Cleanup Old Backups log_info "Cleaning up old backups (retention: $RETENTION_DAYS days)..." find "$BACKUP_DEST" -name "npmplus-backup-*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete 2>/dev/null || true OLD_COUNT=$(find "$BACKUP_DEST" -name "npmplus-backup-*.tar.gz" -type f | wc -l || echo "0") log_success "Old backups cleaned up ($OLD_COUNT backups retained)" # Summary echo "" log_success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log_success "✅ Backup Complete!" log_success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log_info "Backup location: $BACKUP_DEST/npmplus-backup-$TIMESTAMP.tar.gz" log_info "Manifest: $BACKUP_DIR/manifest.txt" echo ""