#!/usr/bin/env bash # List all private IP address assignments in Proxmox # Works both via API (remote) and direct commands (on Proxmox host) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Try to load environment if available if [[ -f "$SCRIPT_DIR/load-env.sh" ]]; then source "$SCRIPT_DIR/load-env.sh" load_env_file 2>/dev/null || true fi PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}" PROXMOX_PORT="${PROXMOX_PORT:-8006}" # Check if running on Proxmox host ON_PROXMOX_HOST=false if command -v pct >/dev/null 2>&1 && command -v qm >/dev/null 2>&1; then ON_PROXMOX_HOST=true fi # Function to check if IP is private is_private_ip() { local ip="$1" # Private IP ranges: # 10.0.0.0/8 # 172.16.0.0/12 # 192.168.0.0/16 if [[ "$ip" =~ ^10\. ]] || \ [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] || \ [[ "$ip" =~ ^192\.168\. ]]; then return 0 fi return 1 } # Function to get LXC container IP from config get_lxc_config_ip() { local vmid="$1" local config_file="/etc/pve/lxc/${vmid}.conf" if [[ -f "$config_file" ]]; then # Extract IP from net0: or net1: lines local ip=$(grep -E "^net[0-9]+:" "$config_file" 2>/dev/null | \ grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | \ cut -d'=' -f2 | head -1) if [[ -n "$ip" ]]; then # Remove CIDR notation if present echo "${ip%/*}" fi fi } # Function to get LXC container actual IP (if running) get_lxc_actual_ip() { local vmid="$1" if ! pct status "$vmid" 2>/dev/null | grep -q "status: running"; then return fi # Try to get IP from container local ip=$(timeout 3s pct exec "$vmid" -- ip -4 addr show 2>/dev/null | \ grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+' | \ grep -v '127.0.0.1' | head -1 | cut -d'/' -f1) if [[ -n "$ip" ]]; then echo "$ip" fi } # Function to get VM IP from config (cloud-init or static) get_vm_config_ip() { local vmid="$1" local config_file="/etc/pve/qemu-server/${vmid}.conf" if [[ -f "$config_file" ]]; then # Check for cloud-init IP configuration local ip=$(grep -E "^ipconfig[0-9]+:" "$config_file" 2>/dev/null | \ grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | \ cut -d'=' -f2 | head -1) if [[ -n "$ip" ]]; then echo "${ip%/*}" fi fi } # Function to get VM actual IP via guest agent get_vm_actual_ip() { local vmid="$1" if ! qm status "$vmid" 2>/dev/null | grep -q "status: running"; then return fi # Try guest agent local ip=$(timeout 5s qm guest cmd "$vmid" network-get-interfaces 2>/dev/null | \ grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | \ grep -v "127.0.0.1" | head -1) if [[ -n "$ip" ]]; then echo "$ip" fi } # Function to get VM IP via ARP table get_vm_arp_ip() { local vmid="$1" local config_file="/etc/pve/qemu-server/${vmid}.conf" if [[ ! -f "$config_file" ]]; then return fi # Get MAC address from config local mac=$(grep -E "^net[0-9]+:" "$config_file" 2>/dev/null | \ grep -oE "([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}" | head -1) if [[ -n "$mac" ]]; then local ip=$(ip neighbor show 2>/dev/null | grep -i "$mac" | \ grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) if [[ -n "$ip" ]]; then echo "$ip" fi fi } # Function to extract IP from config content extract_ip_from_config() { local config_content="$1" local type="$2" # "lxc" or "qemu" if [[ "$type" == "lxc" ]]; then # Extract IP from net0: or net1: lines for LXC echo "$config_content" | grep -E "^net[0-9]+:" | \ grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | \ cut -d'=' -f2 | head -1 | sed 's|/.*||' else # Extract IP from ipconfig0: or ipconfig1: lines for VMs echo "$config_content" | grep -E "^ipconfig[0-9]+:" | \ grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | \ cut -d'=' -f2 | head -1 | sed 's|/.*||' fi } # Function to get config IP via API get_config_ip_api() { local node="$1" local vmid="$2" local type="$3" # "qemu" or "lxc" local config_response=$(curl -k -s -m 5 \ -H "Authorization: PVEAPIToken=${PROXMOX_USER}!${PROXMOX_TOKEN_NAME}=${PROXMOX_TOKEN_VALUE}" \ "https://${PROXMOX_HOST}:${PROXMOX_PORT}/api2/json/nodes/${node}/${type}/${vmid}/config" 2>/dev/null) if [[ -z "$config_response" ]]; then return fi # Extract IP from JSON config if [[ "$type" == "lxc" ]]; then # For LXC: extract from net0, net1, etc. fields echo "$config_response" | python3 -c " import sys, json try: data = json.load(sys.stdin).get('data', {}) # Check net0, net1, net2, etc. for i in range(10): net_key = f'net{i}' if net_key in data: net_value = data[net_key] if 'ip=' in net_value: # Extract IP from ip=192.168.11.100/24 format import re match = re.search(r'ip=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})', net_value) if match: print(match.group(1)) break except: pass " 2>/dev/null else # For VM: extract from ipconfig0, ipconfig1, etc. fields echo "$config_response" | python3 -c " import sys, json try: data = json.load(sys.stdin).get('data', {}) # Check ipconfig0, ipconfig1, etc. for i in range(10): ipconfig_key = f'ipconfig{i}' if ipconfig_key in data: ipconfig_value = data[ipconfig_key] if 'ip=' in ipconfig_value: # Extract IP from ip=192.168.11.100/24 format import re match = re.search(r'ip=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})', ipconfig_value) if match: print(match.group(1)) break except: pass " 2>/dev/null fi } # Function to list IPs via API list_ips_via_api() { local node="$1" echo "Fetching data from Proxmox API..." echo "" # Get all VMs local vms_response=$(curl -k -s -m 10 \ -H "Authorization: PVEAPIToken=${PROXMOX_USER}!${PROXMOX_TOKEN_NAME}=${PROXMOX_TOKEN_VALUE}" \ "https://${PROXMOX_HOST}:${PROXMOX_PORT}/api2/json/nodes/${node}/qemu" 2>/dev/null) # Get all LXC containers local lxc_response=$(curl -k -s -m 10 \ -H "Authorization: PVEAPIToken=${PROXMOX_USER}!${PROXMOX_TOKEN_NAME}=${PROXMOX_TOKEN_VALUE}" \ "https://${PROXMOX_HOST}:${PROXMOX_PORT}/api2/json/nodes/${node}/lxc" 2>/dev/null) echo "VMID Type Name Status Config IP Actual IP" echo "--------------------------------------------------------------------------------" # Process VMs - store in temp file to avoid subshell issues local temp_file=$(mktemp) echo "$vms_response" | python3 -c " import sys, json try: data = json.load(sys.stdin)['data'] for vm in sorted(data, key=lambda x: x['vmid']): vmid = vm['vmid'] name = vm.get('name', 'N/A') status = vm.get('status', 'unknown') print(f'{vmid}|{name}|{status}') except: pass " 2>/dev/null > "$temp_file" while IFS='|' read -r vmid name status; do [[ -z "$vmid" ]] && continue config_ip=$(get_config_ip_api "$node" "$vmid" "qemu") if [[ -n "$config_ip" ]] && is_private_ip "$config_ip"; then printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" \ "$vmid" "VM" "${name:0:23}" "$status" "$config_ip" "N/A (remote)" elif [[ -z "$config_ip" ]]; then # Show even without config IP if it's a known VM printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" \ "$vmid" "VM" "${name:0:23}" "$status" "N/A" "N/A (remote)" fi done < "$temp_file" rm -f "$temp_file" # Process LXC containers echo "$lxc_response" | python3 -c " import sys, json try: data = json.load(sys.stdin)['data'] for lxc in sorted(data, key=lambda x: x['vmid']): vmid = lxc['vmid'] name = lxc.get('name', 'N/A') status = lxc.get('status', 'unknown') print(f'{vmid}|{name}|{status}') except: pass " 2>/dev/null > "$temp_file" while IFS='|' read -r vmid name status; do [[ -z "$vmid" ]] && continue config_ip=$(get_config_ip_api "$node" "$vmid" "lxc") if [[ -n "$config_ip" ]] && is_private_ip "$config_ip"; then printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" \ "$vmid" "LXC" "${name:0:23}" "$status" "$config_ip" "N/A (remote)" elif [[ -z "$config_ip" ]]; then # Show even without config IP if it's a known container printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" \ "$vmid" "LXC" "${name:0:23}" "$status" "N/A" "N/A (remote)" fi done < "$temp_file" rm -f "$temp_file" echo "" echo "Note: Actual IP addresses (runtime) require running on Proxmox host." echo " Config IP shows static IP configuration from Proxmox config files." } # Function to list IPs directly (on Proxmox host) list_ips_direct() { echo "Listing private IP address assignments on Proxmox host..." echo "" printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" "VMID" "Type" "Name" "Status" "Config IP" "Actual IP" echo "--------------------------------------------------------------------------------" # List all LXC containers if command -v pct >/dev/null 2>&1; then while IFS= read -r line; do [[ -z "$line" ]] && continue local vmid=$(echo "$line" | awk '{print $1}') local status=$(echo "$line" | awk '{print $2}') # Get container name local name="N/A" local config_file="/etc/pve/lxc/${vmid}.conf" if [[ -f "$config_file" ]]; then name=$(grep "^hostname:" "$config_file" 2>/dev/null | cut -d' ' -f2 || echo "N/A") fi # Get IPs local config_ip=$(get_lxc_config_ip "$vmid") local actual_ip="" if [[ "$status" == "running" ]]; then actual_ip=$(get_lxc_actual_ip "$vmid") fi # Only show private IPs local display_config_ip="" local display_actual_ip="" if [[ -n "$config_ip" ]] && is_private_ip "$config_ip"; then display_config_ip="$config_ip" fi if [[ -n "$actual_ip" ]] && is_private_ip "$actual_ip"; then display_actual_ip="$actual_ip" fi # Only print if we have at least one private IP if [[ -n "$display_config_ip" ]] || [[ -n "$display_actual_ip" ]]; then printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" \ "$vmid" "LXC" "${name:0:23}" "$status" \ "${display_config_ip:-N/A}" "${display_actual_ip:-N/A}" fi done < <(pct list 2>/dev/null | tail -n +2) fi # List all VMs if command -v qm >/dev/null 2>&1; then while IFS= read -r line; do [[ -z "$line" ]] && continue local vmid=$(echo "$line" | awk '{print $1}') local status=$(echo "$line" | awk '{print $2}') # Get VM name local name="N/A" local config_file="/etc/pve/qemu-server/${vmid}.conf" if [[ -f "$config_file" ]]; then name=$(grep "^name:" "$config_file" 2>/dev/null | cut -d' ' -f2 || echo "N/A") fi # Get IPs local config_ip=$(get_vm_config_ip "$vmid") local actual_ip="" if [[ "$status" == "running" ]]; then actual_ip=$(get_vm_actual_ip "$vmid") # Fallback to ARP if guest agent fails if [[ -z "$actual_ip" ]]; then actual_ip=$(get_vm_arp_ip "$vmid") fi fi # Only show private IPs local display_config_ip="" local display_actual_ip="" if [[ -n "$config_ip" ]] && is_private_ip "$config_ip"; then display_config_ip="$config_ip" fi if [[ -n "$actual_ip" ]] && is_private_ip "$actual_ip"; then display_actual_ip="$actual_ip" fi # Only print if we have at least one private IP if [[ -n "$display_config_ip" ]] || [[ -n "$display_actual_ip" ]]; then printf "%-7s %-6s %-23s %-10s %-15s %-15s\n" \ "$vmid" "VM" "${name:0:23}" "$status" \ "${display_config_ip:-N/A}" "${display_actual_ip:-N/A}" fi done < <(qm list 2>/dev/null | tail -n +2) fi } # Main execution if [[ "$ON_PROXMOX_HOST" == "true" ]]; then list_ips_direct else # Try API method if [[ -n "${PROXMOX_USER:-}" ]] && [[ -n "${PROXMOX_TOKEN_NAME:-}" ]] && [[ -n "${PROXMOX_TOKEN_VALUE:-}" ]]; then # Get first node nodes_response=$(curl -k -s -m 10 \ -H "Authorization: PVEAPIToken=${PROXMOX_USER}!${PROXMOX_TOKEN_NAME}=${PROXMOX_TOKEN_VALUE}" \ "https://${PROXMOX_HOST}:${PROXMOX_PORT}/api2/json/nodes" 2>/dev/null) first_node=$(echo "$nodes_response" | python3 -c "import sys, json; print(json.load(sys.stdin)['data'][0]['node'])" 2>/dev/null) if [[ -n "$first_node" ]]; then list_ips_via_api "$first_node" else echo "Error: Could not connect to Proxmox API or get node list" echo "Please run this script on the Proxmox host for full IP information" exit 1 fi else echo "Error: Not on Proxmox host and API credentials not configured" echo "Please either:" echo " 1. Run this script on the Proxmox host, or" echo " 2. Configure PROXMOX_USER, PROXMOX_TOKEN_NAME, and PROXMOX_TOKEN_VALUE" exit 1 fi fi