#!/usr/bin/env bash # Comprehensive audit of NPMplus proxy host mappings against all Proxmox VMs # Identifies inconsistencies, missing mappings, and incorrect routes set -euo pipefail PROXMOX_HOST="${1:-192.168.11.11}" CONTAINER_ID="${2:-10233}" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' 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"; } log_section() { echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; } echo "" log_section echo "🔍 NPMplus VM Mapping Audit" log_section echo "" # Step 1: Get all VMs from Proxmox log_info "Step 1: Collecting all VMs from Proxmox..." VM_LIST=$(ssh -o StrictHostKeyChecking=no root@"$PROXMOX_HOST" " for vmid in \$(pct list 2>/dev/null | awk 'NR>1 {print \$1}' | grep -E '^[0-9]+$'); do status=\$(pct status \$vmid 2>&1 | head -1) if echo \"\$status\" | grep -q 'running\|stopped'; then hostname=\$(pct config \$vmid 2>/dev/null | grep '^hostname:' | awk '{print \$2}' || echo 'unknown') # Extract IP, removing CIDR notation ip=\$(pct config \$vmid 2>/dev/null | grep -E '^ip[0-9]+' | head -1 | awk -F'=' '{print \$2}' | awk '{print \$1}' | sed 's|/.*||' || echo '') if [ -n \"\$ip\" ] && [ \"\$ip\" != \"\" ]; then echo \"\$vmid|\$hostname|\$ip|\$status\" fi fi done " 2>&1) # Step 2: Get all NPMplus proxy hosts log_info "Step 2: Collecting NPMplus proxy host configurations..." NPMPLUS_HOSTS=$(ssh -o StrictHostKeyChecking=no root@"$PROXMOX_HOST" "pct exec $CONTAINER_ID -- docker exec npmplus node -e \" const Database = require('better-sqlite3'); const db = new Database('/data/npmplus/database.sqlite', { readonly: true }); const hosts = db.prepare('SELECT id, domain_names, forward_scheme, forward_host, forward_port, ssl_forced FROM proxy_host ORDER BY id').all(); console.log(JSON.stringify(hosts)); db.close(); \" 2>&1" || echo "[]") # Step 3: Build VM IP to VMID mapping log_info "Step 3: Building VM inventory..." declare -A IP_TO_VMID declare -A IP_TO_HOSTNAME declare -A IP_TO_STATUS while IFS='|' read -r vmid hostname ip status; do if [ -n "$ip" ] && [ "$ip" != "" ]; then IP_TO_VMID["$ip"]="$vmid" IP_TO_HOSTNAME["$ip"]="$hostname" IP_TO_STATUS["$ip"]="$status" fi done <<< "$VM_LIST" # Step 4: Analyze NPMplus mappings log_info "Step 4: Analyzing NPMplus mappings..." echo "" # Use temp files for arrays (bash limitation with subshells) TMPDIR=$(mktemp -d) INCONSISTENCIES_FILE="$TMPDIR/inconsistencies.txt" MISSING_VMS_FILE="$TMPDIR/missing_vms.txt" CORRECT_MAPPINGS_FILE="$TMPDIR/correct_mappings.txt" IP_CONFLICTS_FILE="$TMPDIR/ip_conflicts.txt" touch "$INCONSISTENCIES_FILE" "$MISSING_VMS_FILE" "$CORRECT_MAPPINGS_FILE" "$IP_CONFLICTS_FILE" # Parse NPMplus hosts echo "$NPMPLUS_HOSTS" | jq -r '.[] | "\(.id)|\(.domain_names)|\(.forward_host)|\(.forward_port)"' 2>/dev/null | while IFS='|' read -r host_id domains_json forward_host forward_port; do domain=$(echo "$domains_json" | jq -r '.[0] // empty' 2>/dev/null || echo "$domains_json") if [ -z "$forward_host" ] || [ "$forward_host" = "null" ]; then continue fi # Check if IP maps to a VM - use associative array lookup # Export the arrays by checking the VM_LIST directly vmid="" expected_hostname="" status="" # Search VM_LIST for matching IP vm_match=$(echo "$VM_LIST" | grep "|$forward_host|" | head -1) if [ -n "$vm_match" ]; then IFS='|' read -r vmid expected_hostname ip_match status <<< "$vm_match" fi if [ -z "$vmid" ]; then # IP not found in VM list - might be external or missing echo "$domain|$forward_host|$forward_port|External or missing VM" >> "$MISSING_VMS_FILE" else # Check for IP conflicts (multiple VMs with same IP) conflict_count=$(echo "$VM_LIST" | grep -c "|$forward_host|" || echo "0") if [ "$conflict_count" -gt 1 ]; then echo "$domain|$forward_host|Multiple VMs share this IP" >> "$IP_CONFLICTS_FILE" fi # Check if service is running if echo "$status" | grep -q "stopped"; then echo "$domain|$forward_host:$forward_port|VMID $vmid ($expected_hostname) is STOPPED" >> "$INCONSISTENCIES_FILE" else echo "$domain|$forward_host:$forward_port|VMID $vmid ($expected_hostname)" >> "$CORRECT_MAPPINGS_FILE" fi fi done # Step 5: Find VMs that might need NPMplus mappings log_info "Step 5: Identifying VMs that might need NPMplus mappings..." POTENTIAL_SERVICES_FILE="$TMPDIR/potential_services.txt" touch "$POTENTIAL_SERVICES_FILE" while IFS='|' read -r vmid hostname ip status; do if [ -z "$ip" ] || [ "$ip" = "" ]; then continue fi # Check if this VM is already in NPMplus in_npmplus=$(echo "$NPMPLUS_HOSTS" | jq -r ".[] | select(.forward_host == \"$ip\") | .domain_names[0]" 2>/dev/null | head -1) if [ -z "$in_npmplus" ] && echo "$status" | grep -q "running"; then # Check if it's a web service (has ports 80, 443, 3000, 4000, 8080, etc.) # This is a heuristic - you may need to adjust if [[ "$hostname" =~ (portal|web|api|frontend|admin|dashboard|explorer|gitea|firefly) ]]; then echo "$vmid|$hostname|$ip|Potential web service" >> "$POTENTIAL_SERVICES_FILE" fi fi done <<< "$VM_LIST" # Step 6: Generate report log_section echo "📊 Audit Report" log_section echo "" CORRECT_COUNT=$(wc -l < "$CORRECT_MAPPINGS_FILE" | tr -d ' ') INCONSISTENCY_COUNT=$(wc -l < "$INCONSISTENCIES_FILE" | tr -d ' ') CONFLICT_COUNT=$(wc -l < "$IP_CONFLICTS_FILE" | tr -d ' ') MISSING_COUNT=$(wc -l < "$MISSING_VMS_FILE" | tr -d ' ') POTENTIAL_COUNT=$(wc -l < "$POTENTIAL_SERVICES_FILE" | tr -d ' ') echo "✅ Correct Mappings ($CORRECT_COUNT):" if [ "$CORRECT_COUNT" -eq 0 ]; then log_warn " No correct mappings found (check script logic)" else while IFS='|' read -r domain target vm_info; do printf " %-50s → %-25s (%s)\n" "$domain" "$target" "$vm_info" done < "$CORRECT_MAPPINGS_FILE" | head -20 fi echo "" echo "⚠️ Inconsistencies ($INCONSISTENCY_COUNT):" if [ "$INCONSISTENCY_COUNT" -eq 0 ]; then log_success " No inconsistencies found" else while IFS='|' read -r domain target issue_desc; do log_warn " $domain → $target: $issue_desc" done < "$INCONSISTENCIES_FILE" fi echo "" echo "🔴 IP Conflicts ($CONFLICT_COUNT):" if [ "$CONFLICT_COUNT" -eq 0 ]; then log_success " No IP conflicts found" else while IFS='|' read -r domain ip issue_desc; do log_error " $domain → $ip: $issue_desc" # Show which VMs share this IP echo "$VM_LIST" | grep "|$ip|" | while IFS='|' read -r vmid hostname ip2 status; do echo " - VMID $vmid: $hostname ($status)" done done < "$IP_CONFLICTS_FILE" fi echo "" echo "❓ Missing/External IPs ($MISSING_COUNT):" if [ "$MISSING_COUNT" -eq 0 ]; then log_success " All IPs map to known VMs" else while IFS='|' read -r domain ip port reason; do log_warn " $domain → $ip:$port ($reason)" done < "$MISSING_VMS_FILE" fi echo "" echo "💡 Potential Services Not in NPMplus ($POTENTIAL_COUNT):" if [ "$POTENTIAL_COUNT" -eq 0 ]; then log_info " No obvious missing services" else while IFS='|' read -r vmid hostname ip reason; do log_info " VMID $vmid: $hostname ($ip) - $reason" done < "$POTENTIAL_SERVICES_FILE" fi echo "" log_section echo "📋 Summary" log_section echo "Total VMs found: $(echo "$VM_LIST" | wc -l)" echo "NPMplus proxy hosts: $(echo "$NPMPLUS_HOSTS" | jq '. | length' 2>/dev/null || echo "0")" echo "Correct mappings: $CORRECT_COUNT" echo "Issues found: $((INCONSISTENCY_COUNT + CONFLICT_COUNT + MISSING_COUNT))" echo "" # Cleanup rm -rf "$TMPDIR" if [ $((INCONSISTENCY_COUNT + CONFLICT_COUNT)) -gt 0 ]; then log_warn "⚠️ Action required: Review inconsistencies and IP conflicts above" else log_success "✅ No critical issues found" fi