From 3a35cc8282d27c8038b260ff48e36afca5991ce3 Mon Sep 17 00:00:00 2001 From: "CanbiZ (MickLesk)" <47820557+MickLesk@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:23:58 +0100 Subject: [PATCH] Refactor: IP-Tag (#10558) --- tools/pve/add-iptag.sh | 321 ++++++++++++++++++++++------------------- 1 file changed, 176 insertions(+), 145 deletions(-) diff --git a/tools/pve/add-iptag.sh b/tools/pve/add-iptag.sh index 7c33dc608..73ff5b3bd 100644 --- a/tools/pve/add-iptag.sh +++ b/tools/pve/add-iptag.sh @@ -57,7 +57,7 @@ spinner() { local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') local spin_i=0 local interval=0.1 - + trap 'exit 0' TERM INT printf "\e[?25l" 2>/dev/null @@ -107,7 +107,6 @@ migrate_config() { fi } - # Update existing installation update_installation() { msg_info "Updating IP-Tag Scripts" @@ -179,7 +178,7 @@ EOF systemctl daemon-reload &>/dev/null systemctl enable -q --now iptag.service &>/dev/null msg_ok "Updated IP-Tag Scripts" - + # Show configuration information after update show_post_install_info } @@ -187,7 +186,7 @@ EOF # Install only command without service install_command_only() { msg_info "Installing IP-Tag Command Only" - + # Create directory if it doesn't exist if [[ ! -d "/opt/iptag" ]]; then mkdir -p /opt/iptag @@ -207,15 +206,15 @@ install_command_only() { echo -e "\n${YW}Configuration file already exists.${CL}" read -p "Do you want to reconfigure tag format? (y/n): " reconfigure case $reconfigure in - [Yy]*) - interactive_config_setup_command - msg_info "Updating Configuration" - generate_config >/opt/iptag/iptag.conf - msg_ok "Updated configuration file" - ;; - *) - msg_ok "Keeping existing configuration file" - ;; + [Yy]*) + interactive_config_setup_command + msg_info "Updating Configuration" + generate_config >/opt/iptag/iptag.conf + msg_ok "Updated configuration file" + ;; + *) + msg_ok "Keeping existing configuration file" + ;; esac fi @@ -240,7 +239,7 @@ exec "$SCRIPT_FILE" EOF chmod +x /usr/local/bin/iptag-run msg_ok "Created iptag-run command" - + msg_ok "IP-Tag Command installed successfully! Use 'iptag-run' to run manually." } @@ -248,14 +247,14 @@ EOF show_post_install_info() { stop_spinner echo -e "\n${YW}=== Next Steps ===${CL}" - + # Show usage information if command -v iptag-run >/dev/null 2>&1; then echo -e "${YW}Run IP tagging manually: ${GN}iptag-run${CL}" echo -e "${YW}Add to cron for scheduled execution if needed${CL}" echo -e "" fi - + echo -e "${RD}IMPORTANT: Configure your network subnets!${CL}" echo -e "" echo -e "${YW}Configuration file: ${GN}/opt/iptag/iptag.conf${CL}" @@ -274,37 +273,37 @@ show_post_install_info() { # Interactive configuration setup for command-only (TAG_FORMAT only) interactive_config_setup_command() { echo -e "\n${YW}=== Configuration Setup ===${CL}" - + # TAG_FORMAT configuration echo -e "\n${YW}Select tag format:${CL}" echo -e "${GN}1)${CL} last_two_octets - Show last two octets (e.g., 0.100) [Default]" echo -e "${GN}2)${CL} last_octet - Show only last octet (e.g., 100)" echo -e "${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)" - + while true; do read -p "Enter your choice (1-3) [1]: " tag_choice case ${tag_choice:-1} in - 1) - TAG_FORMAT="last_two_octets" - echo -e "${GN}✓ Selected: last_two_octets${CL}" - break - ;; - 2) - TAG_FORMAT="last_octet" - echo -e "${GN}✓ Selected: last_octet${CL}" - break - ;; - 3) - TAG_FORMAT="full" - echo -e "${GN}✓ Selected: full${CL}" - break - ;; - *) - echo -e "${RD}Please enter 1, 2, or 3.${CL}" - ;; + 1) + TAG_FORMAT="last_two_octets" + echo -e "${GN}✓ Selected: last_two_octets${CL}" + break + ;; + 2) + TAG_FORMAT="last_octet" + echo -e "${GN}✓ Selected: last_octet${CL}" + break + ;; + 3) + TAG_FORMAT="full" + echo -e "${GN}✓ Selected: full${CL}" + break + ;; + *) + echo -e "${RD}Please enter 1, 2, or 3.${CL}" + ;; esac done - + # Set default LOOP_INTERVAL for command mode LOOP_INTERVAL=300 } @@ -312,46 +311,46 @@ interactive_config_setup_command() { # Interactive configuration setup for service (TAG_FORMAT + LOOP_INTERVAL) interactive_config_setup() { echo -e "\n${YW}=== Configuration Setup ===${CL}" - + # TAG_FORMAT configuration echo -e "\n${YW}Select tag format:${CL}" echo -e "${GN}1)${CL} last_two_octets - Show last two octets (e.g., 0.100) [Default]" echo -e "${GN}2)${CL} last_octet - Show only last octet (e.g., 100)" echo -e "${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)" - + while true; do read -p "Enter your choice (1-3) [1]: " tag_choice case ${tag_choice:-1} in - 1) - TAG_FORMAT="last_two_octets" - echo -e "${GN}✓ Selected: last_two_octets${CL}" - break - ;; - 2) - TAG_FORMAT="last_octet" - echo -e "${GN}✓ Selected: last_octet${CL}" - break - ;; - 3) - TAG_FORMAT="full" - echo -e "${GN}✓ Selected: full${CL}" - break - ;; - *) - echo -e "${RD}Please enter 1, 2, or 3.${CL}" - ;; + 1) + TAG_FORMAT="last_two_octets" + echo -e "${GN}✓ Selected: last_two_octets${CL}" + break + ;; + 2) + TAG_FORMAT="last_octet" + echo -e "${GN}✓ Selected: last_octet${CL}" + break + ;; + 3) + TAG_FORMAT="full" + echo -e "${GN}✓ Selected: full${CL}" + break + ;; + *) + echo -e "${RD}Please enter 1, 2, or 3.${CL}" + ;; esac done - + # LOOP_INTERVAL configuration echo -e "\n${YW}Set check interval (in seconds):${CL}" echo -e "${YW}Default: 300 seconds (5 minutes)${CL}" echo -e "${YW}Recommended range: 300-3600 seconds${CL}" - + while true; do read -p "Enter interval in seconds [300]: " interval_input interval_input=${interval_input:-300} - + if [[ $interval_input =~ ^[0-9]+$ ]] && [ $interval_input -ge 300 ] && [ $interval_input -le 7200 ]; then LOOP_INTERVAL=$interval_input echo -e "${GN}✓ Selected: ${LOOP_INTERVAL} seconds${CL}" @@ -646,21 +645,40 @@ get_vm_ips() { echo "$unique_ips" } +# Cache for configs to avoid repeated reads +declare -A CONFIG_CACHE +declare -A IP_CACHE + # Update tags for container or VM update_tags() { local type="$1" vmid="$2" local current_ips_full + local current_tags_raw="" - if [[ "$type" == "lxc" ]]; then - current_ips_full=$(get_lxc_ips "${vmid}") - while IFS= read -r line; do - [[ "$line" == tags:* ]] && current_tags_raw="${line#tags: }" && break - done < <(pct config "$vmid" 2>/dev/null) + # Get IPs with caching + local cache_key="${type}_${vmid}" + if [[ -n "${IP_CACHE[$cache_key]:-}" ]]; then + current_ips_full="${IP_CACHE[$cache_key]}" + debug_log "$type $vmid: using cached IPs" + else + if [[ "$type" == "lxc" ]]; then + current_ips_full=$(get_lxc_ips "${vmid}") + else + current_ips_full=$(get_vm_ips "${vmid}") + fi + IP_CACHE[$cache_key]="$current_ips_full" + fi + + # Get current tags (optimized file reading) + if [[ "$type" == "lxc" ]]; then + local config_file="/etc/pve/lxc/${vmid}.conf" + if [[ -f "$config_file" ]]; then + current_tags_raw=$(grep "^tags:" "$config_file" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//') + fi else - current_ips_full=$(get_vm_ips "${vmid}") local vm_config="/etc/pve/qemu-server/${vmid}.conf" if [[ -f "$vm_config" ]]; then - local current_tags_raw=$(grep "^tags:" "$vm_config" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//') + current_tags_raw=$(grep "^tags:" "$vm_config" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//') fi fi @@ -818,11 +836,15 @@ update_tags() { update_all_tags() { local type="$1" vmids count=0 + # Get list of all containers/VMs if [[ "$type" == "lxc" ]]; then vmids=($(pct list 2>/dev/null | grep -v VMID | awk '{print $1}')) else - local all_vm_configs=($(ls /etc/pve/qemu-server/*.conf 2>/dev/null | sed 's/.*\/\([0-9]*\)\.conf/\1/' | sort -n)) - vmids=("${all_vm_configs[@]}") + # More efficient: direct file listing instead of ls+sed + vmids=() + for conf in /etc/pve/qemu-server/*.conf 2>/dev/null; do + [[ -f "$conf" ]] && vmids+=("${conf##*/}" | sed 's/\.conf$//') + done fi count=${#vmids[@]} @@ -830,14 +852,16 @@ update_all_tags() { # Display processing header with color if [[ "$type" == "lxc" ]]; then - log_info "Processing ${WHITE}${count}${NC} LXC container(s) sequentially" + log_info "Processing ${WHITE}${count}${NC} LXC container(s)" else - log_info "Processing ${WHITE}${count}${NC} virtual machine(s) sequentially" + log_info "Processing ${WHITE}${count}${NC} virtual machine(s)" fi - # Process each VM/LXC container sequentially + # Process each VM/LXC container + local processed=0 for vmid in "${vmids[@]}"; do update_tags "$type" "$vmid" + ((processed++)) done # Add completion message @@ -848,32 +872,25 @@ update_all_tags() { fi } -# Check if status changed -check_status_changed() { - local type="$1" current - case "$type" in - "lxc") current=$(pct list 2>/dev/null | grep -v VMID) ;; - "vm") current=$(ls -la /etc/pve/qemu-server/*.conf 2>/dev/null) ;; - "fw") current=$(ip link show type bridge 2>/dev/null) ;; - esac - local last_var="last_${type}_status" - [[ "${!last_var}" == "$current" ]] && return 1 - eval "$last_var='$current'" - return 0 -} - # Main check function check() { - local current_time=$(date +%s) + local start_time=$(date +%s) - # Simple periodic check - always update both LXC and VM every loop log_info "Starting periodic check" + # Clear caches before each run + CONFIG_CACHE=() + IP_CACHE=() + # Update LXC containers update_all_tags "lxc" # Update VMs update_all_tags "vm" + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + log_success "Check completed in ${WHITE}${duration}${NC} seconds" } # Main loop @@ -917,40 +934,54 @@ get_lxc_ips() { local ips="" - # Method 1: Check Proxmox config for static IP + # Method 1: Check Proxmox config for ALL static IPs (multiple interfaces) local pve_lxc_config="/etc/pve/lxc/${vmid}.conf" if [[ -f "$pve_lxc_config" ]]; then - local static_ip=$(grep -E "^net[0-9]+:" "$pve_lxc_config" 2>/dev/null | grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | cut -d'=' -f2 | head -1) - if [[ -n "$static_ip" && "$static_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "lxc $vmid: found static IP $static_ip in config" - ips="$static_ip" + local static_ips=$(grep -E "^net[0-9]+:" "$pve_lxc_config" 2>/dev/null | grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | cut -d'=' -f2) + if [[ -n "$static_ips" ]]; then + while IFS= read -r ip; do + if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "lxc $vmid: found static IP $ip in config" + ips="${ips}${ips:+ }${ip}" + fi + done <<< "$static_ips" fi fi - # Method 2: ARP table lookup if no static IP + # Method 2: ARP table lookup for ALL MAC addresses if no static IPs found if [[ -z "$ips" && -f "$pve_lxc_config" ]]; then - local mac_addr=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" | head -1 | cut -d'=' -f2) - if [[ -n "$mac_addr" ]]; then - local bridge_name=$(grep -Eo 'bridge=[^,]+' "$pve_lxc_config" | head -1 | cut -d'=' -f2) - local arp_ip=$(ip neighbor show | grep "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) - if [[ -n "$arp_ip" && "$arp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "lxc $vmid: found IP $arp_ip via ARP table" - ips="$arp_ip" - fi + local mac_addrs=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" | cut -d'=' -f2) + if [[ -n "$mac_addrs" ]]; then + while IFS= read -r mac_addr; do + [[ -z "$mac_addr" ]] && continue + local arp_ip=$(ip neighbor show | grep -i "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$arp_ip" && "$arp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "lxc $vmid: found IP $arp_ip via ARP table for MAC $mac_addr" + ips="${ips}${ips:+ }${arp_ip}" + fi + done <<< "$mac_addrs" fi fi - # Method 3: Direct container command if ARP failed + # Method 3: Direct container command to get ALL IPs if previous methods failed if [[ -z "$ips" ]]; then - local container_ip=$(timeout 5s pct exec "$vmid" -- ip -4 addr show 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v '127.0.0.1' | head -1) - if [[ -n "$container_ip" ]] && is_valid_ipv4 "$container_ip"; then - debug_log "lxc $vmid: found IP $container_ip via pct exec" - ips="$container_ip" + local container_ips=$(timeout 5s pct exec "$vmid" -- ip -4 addr show 2>/dev/null | grep -oE 'inet ([0-9]{1,3}\.){3}[0-9]{1,3}' | awk '{print $2}' | grep -v '127.0.0.1') + if [[ -n "$container_ips" ]]; then + while IFS= read -r ip; do + if is_valid_ipv4 "$ip"; then + debug_log "lxc $vmid: found IP $ip via pct exec" + ips="${ips}${ips:+ }${ip}" + fi + done <<< "$container_ips" fi fi - debug_log "lxc $vmid: final IPs: '$ips'" - echo "$ips" + # Remove duplicates and clean up + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + + debug_log "lxc $vmid: final IPs: '$unique_ips'" + echo "$unique_ips" } main @@ -967,28 +998,28 @@ echo -e "${RD}4)${CL} Cancel" while true; do read -p "Enter your choice (1-4): " choice case $choice in - 1) - INSTALL_MODE="service" - echo -e "${GN}✓ Selected: Service installation${CL}" - break - ;; - 2) - INSTALL_MODE="command" - echo -e "${GN}✓ Selected: Command-only installation${CL}" - break - ;; - 3) - echo -e "${GN}✓ Selected: Update installation${CL}" - update_installation - exit 0 - ;; - 4) - msg_error "Action cancelled." - exit 0 - ;; - *) - msg_error "Please enter 1, 2, 3, or 4." - ;; + 1) + INSTALL_MODE="service" + echo -e "${GN}✓ Selected: Service installation${CL}" + break + ;; + 2) + INSTALL_MODE="command" + echo -e "${GN}✓ Selected: Command-only installation${CL}" + break + ;; + 3) + echo -e "${GN}✓ Selected: Update installation${CL}" + update_installation + exit 0 + ;; + 4) + msg_error "Action cancelled." + exit 0 + ;; + *) + msg_error "Please enter 1, 2, 3, or 4." + ;; esac done @@ -1043,15 +1074,15 @@ if [[ "$INSTALL_MODE" == "service" ]]; then echo -e "\n${YW}Configuration file already exists.${CL}" read -p "Do you want to reconfigure tag format and loop interval? (y/n): " reconfigure case $reconfigure in - [Yy]*) - interactive_config_setup - msg_info "Updating Configuration" - generate_config >/opt/iptag/iptag.conf - msg_ok "Updated configuration file" - ;; - *) - msg_ok "Keeping existing configuration file" - ;; + [Yy]*) + interactive_config_setup + msg_info "Updating Configuration" + generate_config >/opt/iptag/iptag.conf + msg_ok "Updated configuration file" + ;; + *) + msg_ok "Keeping existing configuration file" + ;; esac fi @@ -1083,21 +1114,21 @@ exec "$SCRIPT_FILE" EOF chmod +x /usr/local/bin/iptag-run msg_ok "Created iptag-run command" - + echo -e "\n${GN}${APP} service installation completed successfully! ${CL}" echo -e "${YW}The service is now running automatically.${CL}" echo -e "${YW}You can also run it manually with: ${GN}iptag-run${CL}\n" - + # Show configuration information show_post_install_info - + elif [[ "$INSTALL_MODE" == "command" ]]; then # Command-only installation install_command_only - + stop_spinner echo -e "\n${GN}${APP} command installation completed successfully! ${CL}" - + # Show configuration information show_post_install_info fi