Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands - CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround - CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check - NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere - MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates - LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference Co-authored-by: Cursor <cursoragent@cursor.com>
230 lines
9.2 KiB
Bash
Executable File
230 lines
9.2 KiB
Bash
Executable File
#!/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" <<EOF
|
|
NPMplus Backup Manifest
|
|
=======================
|
|
Date: $(date)
|
|
Host: $NPMPLUS_HOST
|
|
VMID: $NPMPLUS_VMID
|
|
NPM URL: $NPM_URL
|
|
|
|
Contents:
|
|
- database.sqlite or database.sql: NPMplus SQLite database
|
|
- proxy_hosts.json: Proxy hosts configuration (if API available)
|
|
- certificates.json: Certificates metadata (if API available)
|
|
- certificates/: Certificate files from disk
|
|
- nginx/: Nginx configuration files
|
|
|
|
Backup Size: $(du -sh "$BACKUP_DIR" | awk '{print $1}')
|
|
|
|
Restore Instructions:
|
|
See: docs/04-configuration/NPMPLUS_BACKUP_RESTORE.md
|
|
EOF
|
|
|
|
log_success "Backup manifest created"
|
|
|
|
# 6. Compress Backup
|
|
log_info "Compressing backup..."
|
|
cd "$BACKUP_DEST"
|
|
tar czf "npmplus-backup-$TIMESTAMP.tar.gz" "npmplus-backup-$TIMESTAMP" 2>&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 ""
|