From e4bdb1cf8825cc03dabc616403c747092d87ebb7 Mon Sep 17 00:00:00 2001 From: "CanbiZ (MickLesk)" <47820557+MickLesk@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:52:37 +0100 Subject: [PATCH] core: IP-Range-Scan Support (app.vars / default.vars) (#10038) --- docs/guides/DEFAULTS_SYSTEM_GUIDE.md | 16 ++- docs/guides/UNATTENDED_DEPLOYMENTS.md | 25 +++++ misc/build.func | 135 +++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 3 deletions(-) diff --git a/docs/guides/DEFAULTS_SYSTEM_GUIDE.md b/docs/guides/DEFAULTS_SYSTEM_GUIDE.md index 724798515..2fcda738a 100644 --- a/docs/guides/DEFAULTS_SYSTEM_GUIDE.md +++ b/docs/guides/DEFAULTS_SYSTEM_GUIDE.md @@ -434,12 +434,24 @@ var_unprivileged=1 # 0=privileged, 1=unprivileged #### Network ```bash var_brg=vmbr0 # Bridge interface -var_net=veth # Network driver -var_gateway=192.168.1.1 # Default gateway +var_net=dhcp # dhcp, static IP/CIDR, or IP range (see below) +var_gateway=192.168.1.1 # Default gateway (required for static IP) var_mtu=1500 # MTU size var_vlan=100 # VLAN ID ``` +#### IP Range Scanning + +You can specify an IP range instead of a static IP. The system will ping each IP in the range and automatically assign the first free IP: + +```bash +# Format: START_IP/CIDR-END_IP/CIDR +var_net=192.168.1.100/24-192.168.1.200/24 +var_gateway=192.168.1.1 +``` + +This is useful for automated deployments where you want static IPs but don't want to track which IPs are already in use. + #### System ```bash var_hostname=pihole # Container name diff --git a/docs/guides/UNATTENDED_DEPLOYMENTS.md b/docs/guides/UNATTENDED_DEPLOYMENTS.md index 3566c7d25..ff41fbd21 100644 --- a/docs/guides/UNATTENDED_DEPLOYMENTS.md +++ b/docs/guides/UNATTENDED_DEPLOYMENTS.md @@ -122,6 +122,31 @@ var_verbose=no \ echo "✓ Container deployed successfully" ``` +### Using IP Range Scan for Automatic IP Assignment + +Instead of manually specifying static IPs, you can define an IP range. The system will automatically ping each IP and assign the first free one: + +```bash +#!/bin/bash +# deploy-with-ip-scan.sh - Auto-assign first free IP from range + +var_unprivileged=1 \ +var_cpu=4 \ +var_ram=4096 \ +var_hostname=web-server \ +var_net=192.168.1.100/24-192.168.1.150/24 \ +var_gateway=192.168.1.1 \ + bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)" + +# The script will: +# 1. Ping 192.168.1.100 - if responds, skip +# 2. Ping 192.168.1.101 - if responds, skip +# 3. Continue until first IP that doesn't respond +# 4. Assign that IP to the container +``` + +> **Note**: IP range format is `START_IP/CIDR-END_IP/CIDR`. Both sides must include the same CIDR notation. + ### Using App Defaults **Step 1: Create defaults once (interactive)** diff --git a/misc/build.func b/misc/build.func index 1c1b12ba2..3a7b8c0a7 100644 --- a/misc/build.func +++ b/misc/build.func @@ -291,6 +291,90 @@ find_host_ssh_keys() { ) } +# ============================================================================== +# SECTION 3B: IP RANGE SCANNING +# ============================================================================== + +# ------------------------------------------------------------------------------ +# ip_to_int() / int_to_ip() +# +# - Converts IP address to integer and vice versa for range iteration +# ------------------------------------------------------------------------------ +ip_to_int() { + local IFS=. + read -r i1 i2 i3 i4 <<<"$1" + echo $(((i1 << 24) + (i2 << 16) + (i3 << 8) + i4)) +} + +int_to_ip() { + local ip=$1 + echo "$(((ip >> 24) & 0xFF)).$(((ip >> 16) & 0xFF)).$(((ip >> 8) & 0xFF)).$((ip & 0xFF))" +} + +# ------------------------------------------------------------------------------ +# resolve_ip_from_range() +# +# - Takes an IP range in format "10.0.0.1/24-10.0.0.10/24" +# - Pings each IP in the range to find the first available one +# - Returns the first free IP with CIDR notation +# - Sets NET_RESOLVED to the resolved IP or empty on failure +# ------------------------------------------------------------------------------ +resolve_ip_from_range() { + local range="$1" + local ip_cidr_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$' + local ip_start ip_end + + # Parse range: "10.0.0.1/24-10.0.0.10/24" + ip_start="${range%%-*}" + ip_end="${range##*-}" + + if [[ ! "$ip_start" =~ $ip_cidr_regex ]] || [[ ! "$ip_end" =~ $ip_cidr_regex ]]; then + NET_RESOLVED="" + return 1 + fi + + local ip1="${ip_start%%/*}" + local ip2="${ip_end%%/*}" + local cidr="${ip_start##*/}" + + local start_int=$(ip_to_int "$ip1") + local end_int=$(ip_to_int "$ip2") + + for ((ip_int = start_int; ip_int <= end_int; ip_int++)); do + local ip=$(int_to_ip $ip_int) + msg_info "Checking IP: $ip" + if ! ping -c 1 -W 1 "$ip" >/dev/null 2>&1; then + NET_RESOLVED="$ip/$cidr" + msg_ok "Found free IP: ${BGN}$NET_RESOLVED${CL}" + return 0 + fi + done + + NET_RESOLVED="" + msg_error "No free IP found in range $range" + return 1 +} + +# ------------------------------------------------------------------------------ +# is_ip_range() +# +# - Checks if a string is an IP range (contains - and looks like IP/CIDR) +# - Returns 0 if it's a range, 1 otherwise +# ------------------------------------------------------------------------------ +is_ip_range() { + local value="$1" + local ip_start ip_end + if [[ "$value" == *-* ]] && [[ "$value" != "dhcp" ]]; then + local ip_cidr_regex='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$' + ip_start="${value%%-*}" + ip_end="${value##*-}" + if [[ "$ip_start" =~ $ip_cidr_regex ]] && [[ "$ip_end" =~ $ip_cidr_regex ]]; then + return 0 + fi + fi + return 1 +} + # ============================================================================== # SECTION 4: STORAGE & RESOURCE MANAGEMENT # ============================================================================== @@ -403,6 +487,18 @@ base_settings() { HN=${var_hostname:-$NSAPP} BRG=${var_brg:-"vmbr0"} NET=${var_net:-"dhcp"} + + # Resolve IP range if NET contains a range (e.g., 192.168.1.100/24-192.168.1.200/24) + if is_ip_range "$NET"; then + msg_info "Scanning IP range: $NET" + if resolve_ip_from_range "$NET"; then + NET="$NET_RESOLVED" + else + msg_error "Could not find free IP in range. Falling back to DHCP." + NET="dhcp" + fi + fi + IPV6_METHOD=${var_ipv6_method:-"none"} IPV6_STATIC=${var_ipv6_static:-""} GATE=${var_gateway:-""} @@ -1324,9 +1420,10 @@ advanced_settings() { if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \ --title "IPv4 CONFIGURATION" \ --ok-button "Next" --cancel-button "Back" \ - --menu "\nSelect IPv4 Address Assignment:" 14 60 2 \ + --menu "\nSelect IPv4 Address Assignment:" 16 65 3 \ "dhcp" "Automatic (DHCP, recommended)" \ "static" "Static (manual entry)" \ + "range" "IP Range Scan (find first free IP)" \ 3>&1 1>&2 2>&3); then if [[ "$result" == "static" ]]; then @@ -1357,6 +1454,42 @@ advanced_settings() { whiptail --msgbox "Invalid IPv4 CIDR format.\nExample: 192.168.1.100/24" 8 58 fi fi + elif [[ "$result" == "range" ]]; then + # IP Range Scan + local ip_range + if ip_range=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \ + --title "IP RANGE SCAN" \ + --ok-button "Scan" --cancel-button "Back" \ + --inputbox "\nEnter IP range to scan for free address\n(e.g. 192.168.1.100/24-192.168.1.200/24)" 12 65 "" \ + 3>&1 1>&2 2>&3); then + if is_ip_range "$ip_range"; then + # Exit whiptail screen temporarily to show scan progress + clear + header_info + echo -e "${INFO}${BOLD}${DGN}Scanning IP range for free address...${CL}\n" + if resolve_ip_from_range "$ip_range"; then + # Get gateway + local gateway_ip + if gateway_ip=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \ + --title "GATEWAY IP" \ + --ok-button "Next" --cancel-button "Back" \ + --inputbox "\nFound free IP: $NET_RESOLVED\n\nEnter Gateway IP address" 12 58 "" \ + 3>&1 1>&2 2>&3); then + if [[ "$gateway_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + _net="$NET_RESOLVED" + _gate=",gw=$gateway_ip" + ((STEP++)) + else + whiptail --msgbox "Invalid Gateway IP format." 8 58 + fi + fi + else + whiptail --msgbox "No free IP found in the specified range.\nAll IPs responded to ping." 10 58 + fi + else + whiptail --msgbox "Invalid IP range format.\n\nExample: 192.168.1.100/24-192.168.1.200/24" 10 58 + fi + fi else _net="dhcp" _gate=""