From 6a2fc4083b1e9368afbaaaa53ebad85bc23bc844 Mon Sep 17 00:00:00 2001 From: Desert Gamer <44316028+DesertGamer@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:25:37 +0300 Subject: [PATCH] Enhanced IP-Tag installation script with interactive configuration, improved VM IP detection, and better visual indicators (#7366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Interactive Configuration**: Added setup wizard for `TAG_FORMAT` (last_two_octets/last_octet/full) and `LOOP_INTERVAL` (30-7200 seconds) during installation/update - **Unified Installation Menu**: Replaced conditional service detection with unified menu offering install-with-service, install-command-only, and update options - **Post-Installation Guidance**: Added comprehensive information display about configuration file location (`/opt/iptag/iptag.conf`) and required CIDR_LIST setup - **Improved VM IP Detection**: Enhanced IP discovery with QM guest agent priority, ARP table validation via ping, and automatic removal of stale ARP entries - **Visual Indicators**: Replaced emoji symbols with standard ASCII characters (✓/✗) with proper colors for better terminal compatibility - **Simplified Update Process**: Removed complex service detection logic and provided direct update option in main menu These changes address user feedback about complex installation requirements, VM IP detection issues, and provide clearer guidance for network subnet configuration. --- tools/pve/add-iptag.sh | 1074 +++++++++++++++------------------------- 1 file changed, 411 insertions(+), 663 deletions(-) diff --git a/tools/pve/add-iptag.sh b/tools/pve/add-iptag.sh index e676cc3bb..7c33dc608 100644 --- a/tools/pve/add-iptag.sh +++ b/tools/pve/add-iptag.sh @@ -28,15 +28,22 @@ RD=$(echo "\033[01;31m") CL=$(echo "\033[m") BFR="\\r\\033[K" HOLD=" " -CM=" ✔️ ${CL}" -CROSS=" ✖️ ${CL}" +CM="${GN}✓${CL} " +CROSS="${RD}✗${CL} " + +# Stop any running spinner +stop_spinner() { + if [ -n "$SPINNER_PID" ] && kill -0 "$SPINNER_PID" 2>/dev/null; then + kill -TERM "$SPINNER_PID" 2>/dev/null + wait "$SPINNER_PID" 2>/dev/null + fi + SPINNER_PID="" + printf "\e[?25h\r" +} # Error handler for displaying error messages error_handler() { - if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then - kill $SPINNER_PID >/dev/null - fi - printf "\e[?25h" + stop_spinner local exit_code="$?" local line_number="$1" local command="$2" @@ -46,57 +53,44 @@ error_handler() { # Spinner for progress indication spinner() { + local msg="$1" local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') local spin_i=0 local interval=0.1 - printf "\e[?25l" - - local color="${YWB}" + + trap 'exit 0' TERM INT + printf "\e[?25l" 2>/dev/null while true; do - printf "\r ${color}%s${CL}" "${frames[spin_i]}" + printf "\r%s ${YW}%s${CL}" "${frames[spin_i]}" "$msg" 2>/dev/null || exit 0 spin_i=$(((spin_i + 1) % ${#frames[@]})) - sleep "$interval" + sleep "$interval" || exit 0 done } # Info message msg_info() { local msg="$1" - echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" - spinner & + stop_spinner + spinner "$msg" & SPINNER_PID=$! + disown $SPINNER_PID 2>/dev/null } # Success message msg_ok() { - if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then - kill $SPINNER_PID >/dev/null - fi - printf "\e[?25h" + stop_spinner local msg="$1" echo -e "${BFR}${CM}${GN}${msg}${CL}" } # Error message msg_error() { - if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then - kill $SPINNER_PID >/dev/null - fi - printf "\e[?25h" + stop_spinner local msg="$1" echo -e "${BFR}${CROSS}${RD}${msg}${CL}" } -# Check if service exists -check_service_exists() { - if systemctl is-active --quiet iptag.service; then - return 0 - else - return 1 - fi -} - # Migrate configuration from old path to new migrate_config() { local old_config="/opt/lxc-iptag" @@ -113,6 +107,7 @@ migrate_config() { fi } + # Update existing installation update_installation() { msg_info "Updating IP-Tag Scripts" @@ -127,17 +122,19 @@ update_installation() { # Create new config file (check if exists and ask user) if [[ -f "/opt/iptag/iptag.conf" ]]; then echo -e "\n${YW}Configuration file already exists.${CL}" + echo -e "${YW}Note: No critical changes were made in this version.${CL}" while true; do read -p "Do you want to replace it with defaults? (y/n): " yn case $yn in [Yy]*) + interactive_config_setup msg_info "Replacing configuration file" generate_config >/opt/iptag/iptag.conf msg_ok "Configuration file replaced with defaults" break ;; [Nn]*) - echo -e "${GN}✔️ Keeping existing configuration file${CL}" + echo -e "${GN}✓ Keeping existing configuration file${CL}" break ;; *) @@ -146,6 +143,7 @@ update_installation() { esac done else + interactive_config_setup msg_info "Creating new configuration file" generate_config >/opt/iptag/iptag.conf msg_ok "Created new configuration file at /opt/iptag/iptag.conf" @@ -168,7 +166,7 @@ update_installation() { CONFIG_FILE="/opt/iptag/iptag.conf" SCRIPT_FILE="/opt/iptag/iptag" if [[ ! -f "$SCRIPT_FILE" ]]; then - echo "❌ Main script not found: $SCRIPT_FILE" + echo "✗ Main script not found: $SCRIPT_FILE" exit 1 fi export FORCE_SINGLE_RUN=true @@ -181,12 +179,193 @@ 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 +} + +# 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 + fi + + # Migrate config if needed + migrate_config + + # Interactive configuration setup + if [[ ! -f /opt/iptag/iptag.conf ]]; then + interactive_config_setup_command + msg_info "Setup Configuration" + generate_config >/opt/iptag/iptag.conf + msg_ok "Created configuration file at /opt/iptag/iptag.conf" + else + stop_spinner + 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" + ;; + esac + fi + + # Setup main script + msg_info "Setup Main Script" + generate_main_script >/opt/iptag/iptag + chmod +x /opt/iptag/iptag + msg_ok "Created main script" + + # Create manual run command + msg_info "Creating iptag-run command" + cat <<'EOF' >/usr/local/bin/iptag-run +#!/usr/bin/env bash +CONFIG_FILE="/opt/iptag/iptag.conf" +SCRIPT_FILE="/opt/iptag/iptag" +if [[ ! -f "$SCRIPT_FILE" ]]; then + echo "✗ Main script not found: $SCRIPT_FILE" + exit 1 +fi +export FORCE_SINGLE_RUN=true +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." +} + +# Show post-installation information +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}" + echo -e "" + echo -e "${YW}Edit CIDR_LIST with your actual subnets:${CL}" + echo -e "${GN}nano /opt/iptag/iptag.conf${CL} ${YW}or${CL} ${GN}vim /opt/iptag/iptag.conf${CL}" + echo -e "" + echo -e "${YW}Example configuration:${CL}" + echo -e "${GN}CIDR_LIST=(${CL}" + echo -e "${GN} 192.168.1.0/24 # Your actual subnet${CL}" + echo -e "${GN} 10.10.0.0/16 # Another subnet${CL}" + echo -e "${GN})${CL}" + echo -e "" +} + +# 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}" + ;; + esac + done + + # Set default LOOP_INTERVAL for command mode + LOOP_INTERVAL=300 +} + +# 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}" + ;; + 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}" + break + else + echo -e "${RD}Please enter a valid number between 300 and 7200 seconds.${CL}" + fi + done } # Generate configuration file content generate_config() { cat </dev/null 2>&1; then vm_status=$(qm status "$vmid" 2>/dev/null | awk '{print $2}') fi if [[ "$vm_status" != "running" ]]; then - debug_log "vm $vmid: not running (status: $vm_status), skipping expensive detection" + debug_log "vm $vmid: not running (status: $vm_status)" return fi - # Cache for this execution - local cache_file="/tmp/iptag_vm_${vmid}_cache" - local cache_ttl=60 # 60 seconds cache - - # Check cache first - if [[ -f "$cache_file" ]] && [[ $(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0))) -lt $cache_ttl ]]; then - local cached_ips=$(cat "$cache_file" 2>/dev/null) - if [[ -n "$cached_ips" ]]; then - debug_log "vm $vmid: using cached IPs: $cached_ips" - echo "$cached_ips" - return - fi - fi - - # Method 1: Quick ARP table lookup (fastest) + # Get MAC addresses from config local mac_addresses=$(grep -E "^net[0-9]+:" "$vm_config" | grep -oE "([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}" | head -3) debug_log "vm $vmid: found MACs: $mac_addresses" - # Quick ARP check without forced refresh (most common case) - for mac in $mac_addresses; do - local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') - local ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) - if [[ -n "$ip" && "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "vm $vmid: found IP $ip via quick ARP for MAC $mac_lower" - ips+="$ip " - fi - done - - # Early exit if we found IPs via ARP - if [[ -n "$ips" ]]; then - local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') - unique_ips="${unique_ips% }" - debug_log "vm $vmid: early exit with IPs: '$unique_ips'" - echo "$unique_ips" > "$cache_file" - echo "$unique_ips" - return - fi - - # Method 2: QM guest agent (fast if available) + # Method 1: QM guest agent (most reliable for current IP) if command -v qm >/dev/null 2>&1; then - local qm_ips=$(timeout 3 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 -2) + debug_log "vm $vmid: trying qm guest agent first" + local qm_ips=$(timeout 8 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 -3) for qm_ip in $qm_ips; do if [[ "$qm_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then debug_log "vm $vmid: found IP $qm_ip via qm guest cmd" @@ -458,130 +587,62 @@ get_vm_ips() { done fi - # Early exit if we found IPs via QM - if [[ -n "$ips" ]]; then - local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') - unique_ips="${unique_ips% }" - debug_log "vm $vmid: early exit with QM IPs: '$unique_ips'" - echo "$unique_ips" > "$cache_file" - echo "$unique_ips" - return - fi - - # Method 3: DHCP leases check (medium cost) - for mac in $mac_addresses; do - local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') - - for dhcp_file in "/var/lib/dhcp/dhcpd.leases" "/var/lib/dhcpcd5/dhcpcd.leases" "/tmp/dhcp.leases"; do - if [[ -f "$dhcp_file" ]]; then - local dhcp_ip=$(timeout 2 grep -A 10 "ethernet $mac_lower" "$dhcp_file" 2>/dev/null | grep "binding state active" -A 5 | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" | head -1) - if [[ -n "$dhcp_ip" && "$dhcp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "vm $vmid: found IP $dhcp_ip via DHCP leases for MAC $mac_lower" - ips+="$dhcp_ip " - break 2 + # Method 2: Fresh ARP table lookup (force refresh) + if [[ -n "$mac_addresses" ]]; then + debug_log "vm $vmid: refreshing ARP table and checking" + # Try to refresh ARP table by pinging network ranges + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + + # First check current ARP table + local current_ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + + # If found in ARP, verify it's still valid by trying to ping + if [[ -n "$current_ip" && "$current_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $current_ip in ARP table for MAC $mac_lower, verifying..." + # Quick ping test to verify IP is still active + if timeout 2 ping -c 1 "$current_ip" >/dev/null 2>&1; then + debug_log "vm $vmid: verified IP $current_ip is active via ping" + ips+="$current_ip " + else + debug_log "vm $vmid: IP $current_ip failed ping verification, removing from ARP" + # Remove stale ARP entry + ip neighbor del "$current_ip" dev $(ip route get "$current_ip" 2>/dev/null | grep -oE 'dev [^ ]+' | cut -d' ' -f2) 2>/dev/null || true fi fi done - done - - # Early exit if we found IPs via DHCP - if [[ -n "$ips" ]]; then - local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') - unique_ips="${unique_ips% }" - debug_log "vm $vmid: early exit with DHCP IPs: '$unique_ips'" - echo "$unique_ips" > "$cache_file" - echo "$unique_ips" - return fi - # Method 4: Limited network discovery (expensive - only if really needed) - debug_log "vm $vmid: falling back to limited network discovery" - - for mac in $mac_addresses; do - local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') - - # Get bridge interfaces - local bridges=$(grep -E "^net[0-9]+:" "$vm_config" | grep -oE "bridge=\w+" | cut -d= -f2 | head -1) - for bridge in $bridges; do - if [[ -n "$bridge" && -d "/sys/class/net/$bridge" ]]; then - # Get bridge IP range - local bridge_ip=$(ip addr show "$bridge" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}/[0-9]+' | head -1) - if [[ -n "$bridge_ip" ]]; then - local network=$(echo "$bridge_ip" | cut -d'/' -f1) - debug_log "vm $vmid: limited scan on bridge $bridge network $bridge_ip" - - # Force ARP refresh with broadcast ping (limited) - IFS='.' read -r a b c d <<< "$network" - local broadcast="$a.$b.$c.255" - timeout 1 ping -c 1 -b "$broadcast" >/dev/null 2>&1 || true - - # Check ARP again after refresh - sleep 0.5 - local ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) - if [[ -n "$ip" && "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "vm $vmid: found IP $ip via ARP after broadcast for MAC $mac_lower" - ips+="$ip " - break 2 - fi - - # Only do very limited ping scan (reduced range) - IFS='.' read -r a b c d <<< "$network" - local base_net="$a.$b.$c" - - # Try only most common ranges (much smaller than before) - for last_octet in {100..105} {200..205}; do - local test_ip="$base_net.$last_octet" - - # Very quick ping test (reduced timeout) - if timeout 0.2 ping -c 1 -W 1 "$test_ip" >/dev/null 2>&1; then - # Check if this IP corresponds to our MAC - sleep 0.1 - local found_mac=$(ip neighbor show "$test_ip" 2>/dev/null | grep -oE "([0-9a-f]{2}:){5}[0-9a-f]{2}") - if [[ "$found_mac" == "$mac_lower" ]]; then - debug_log "vm $vmid: found IP $test_ip via limited ping scan for MAC $mac_lower" - ips+="$test_ip " - break 2 - fi - fi - done - - # Skip extended scanning entirely (too expensive) - debug_log "vm $vmid: skipping extended scan to preserve CPU" - fi - fi - done - done - - # Method 5: Static configuration check (fast) + # Method 3: DHCP leases (backup method) if [[ -z "$ips" ]]; then - debug_log "vm $vmid: checking for static IP configuration" - - # Check cloud-init configuration if exists - local cloudinit_file="/var/lib/vz/snippets/${vmid}-cloud-init.yml" - if [[ -f "$cloudinit_file" ]]; then - local static_ip=$(grep -E "addresses?:" "$cloudinit_file" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) - if [[ -n "$static_ip" && "$static_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "vm $vmid: found static IP $static_ip in cloud-init config" - ips+="$static_ip " - fi - fi - - # Check VM config for any IP hints - local config_ip=$(grep -E "(ip=|gw=)" "$vm_config" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) - if [[ -n "$config_ip" && "$config_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "vm $vmid: found IP hint $config_ip in VM config" - ips+="$config_ip " - fi + debug_log "vm $vmid: checking DHCP leases as fallback" + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + for dhcp_file in "/var/lib/dhcp/dhcpd.leases" "/var/lib/dhcpcd5/dhcpcd.leases"; do + if [[ -f "$dhcp_file" ]]; then + # Look for most recent lease for this MAC + local dhcp_ip=$(tac "$dhcp_file" 2>/dev/null | grep -A 10 "ethernet $mac_lower" | grep "binding state active" -A 5 | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" | head -1) + if [[ -n "$dhcp_ip" && "$dhcp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $dhcp_ip via DHCP leases" + # Verify this IP responds + if timeout 2 ping -c 1 "$dhcp_ip" >/dev/null 2>&1; then + debug_log "vm $vmid: verified DHCP IP $dhcp_ip is active" + ips+="$dhcp_ip " + break 2 + else + debug_log "vm $vmid: DHCP IP $dhcp_ip failed ping test" + fi + fi + fi + done + done fi - # Remove duplicates and cache result + # Remove duplicates and clean up local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') unique_ips="${unique_ips% }" - # Cache the result (even if empty) - echo "$unique_ips" > "$cache_file" - - debug_log "vm $vmid: final optimized IPs: '$unique_ips'" + debug_log "vm $vmid: final IPs: '$unique_ips'" echo "$unique_ips" } @@ -646,10 +707,15 @@ update_tags() { return fi - # Add new IP tags + # Prepend new IP tags to the beginning of the tag list + local final_tags=() for new_ip in "${formatted_ips[@]}"; do - next_tags+=("$new_ip") + final_tags+=("$new_ip") done + for tag in "${next_tags[@]}"; do + final_tags+=("$tag") + done + next_tags=("${final_tags[@]}") # Update tags if there are changes local old_tags_str=$(IFS=';'; echo "${current_tags[*]}") @@ -764,23 +830,16 @@ update_all_tags() { # Display processing header with color if [[ "$type" == "lxc" ]]; then - log_info "Processing ${WHITE}${count}${NC} LXC container(s) in parallel" - - # Clean up old cache files before processing LXC - cleanup_vm_cache - - # Process LXC containers in parallel for better performance - process_lxc_parallel "${vmids[@]}" + log_info "Processing ${WHITE}${count}${NC} LXC container(s) sequentially" else - log_info "Processing ${WHITE}${count}${NC} virtual machine(s) in parallel" - - # Clean up old cache files before processing VMs - cleanup_vm_cache - - # Process VMs in parallel for better performance - process_vms_parallel "${vmids[@]}" + log_info "Processing ${WHITE}${count}${NC} virtual machine(s) sequentially" fi + # Process each VM/LXC container sequentially + for vmid in "${vmids[@]}"; do + update_tags "$type" "$vmid" + done + # Add completion message if [[ "$type" == "lxc" ]]; then log_success "Completed processing LXC containers" @@ -805,88 +864,26 @@ check_status_changed() { # Main check function check() { - local current_time changes_detected=false - current_time=$(date +%s) - - local update_lxc=false - local update_vm=false - - # Periodic cache cleanup (every 10 minutes) - local time_since_last_cleanup=$((current_time - ${last_cleanup_time:-0})) - if [[ $time_since_last_cleanup -ge 600 ]]; then - cleanup_vm_cache - last_cleanup_time=$current_time - debug_log "Performed periodic cache cleanup" - fi - - # Check LXC status - local time_since_last_lxc_check=$((current_time - last_lxc_status_check_time)) - if [[ "${LXC_STATUS_CHECK_INTERVAL:-60}" -gt 0 ]] && \ - [[ "$time_since_last_lxc_check" -ge "${LXC_STATUS_CHECK_INTERVAL:-60}" ]]; then - last_lxc_status_check_time=$current_time - if check_status_changed "lxc"; then - update_lxc=true - log_warning "LXC status changes detected" - fi - fi - - # Check VM status - local time_since_last_vm_check=$((current_time - last_vm_status_check_time)) - if [[ "${VM_STATUS_CHECK_INTERVAL:-60}" -gt 0 ]] && \ - [[ "$time_since_last_vm_check" -ge "${VM_STATUS_CHECK_INTERVAL:-60}" ]]; then - last_vm_status_check_time=$current_time - if check_status_changed "vm"; then - update_vm=true - log_warning "VM status changes detected" - fi - fi - - # Check network interface changes - local time_since_last_fw_check=$((current_time - last_fw_net_interface_check_time)) - if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL:-60}" -gt 0 ]] && \ - [[ "$time_since_last_fw_check" -ge "${FW_NET_INTERFACE_CHECK_INTERVAL:-60}" ]]; then - last_fw_net_interface_check_time=$current_time - if check_status_changed "fw"; then - update_lxc=true - update_vm=true - log_warning "Network interface changes detected" - fi - fi - - # Force update if interval exceeded - for type in "lxc" "vm"; do - local last_update_var="last_update_${type}_time" - local time_since_last_update=$((current_time - ${!last_update_var})) - if [[ $time_since_last_update -ge ${FORCE_UPDATE_INTERVAL:-1800} ]]; then - if [[ "$type" == "lxc" ]]; then - update_lxc=true - log_info "Scheduled LXC update (every $((FORCE_UPDATE_INTERVAL / 60)) minutes)" - else - update_vm=true - log_info "Scheduled VM update (every $((FORCE_UPDATE_INTERVAL / 60)) minutes)" - fi - eval "${last_update_var}=${current_time}" - fi - done - - # Final execution - $update_lxc && update_all_tags "lxc" - $update_vm && update_all_tags "vm" + local current_time=$(date +%s) + + # Simple periodic check - always update both LXC and VM every loop + log_info "Starting periodic check" + + # Update LXC containers + update_all_tags "lxc" + + # Update VMs + update_all_tags "vm" } -# Initialize time variables -declare -g last_lxc_status="" last_vm_status="" last_fw_status="" -declare -g last_lxc_status_check_time=0 last_vm_status_check_time=0 last_fw_net_interface_check_time=0 -declare -g last_update_lxc_time=0 last_update_vm_time=0 last_cleanup_time=0 - # Main loop main() { # Display startup message echo -e "\n${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" log_success "IP-Tag service started successfully" - echo -e "${BLUE}ℹ${NC} Loop interval: ${WHITE}${LOOP_INTERVAL:-$DEFAULT_CHECK_INTERVAL}${NC} seconds" + echo -e "${BLUE}ℹ${NC} Loop interval: ${WHITE}${LOOP_INTERVAL:-300}${NC} seconds" echo -e "${BLUE}ℹ${NC} Debug mode: ${WHITE}${DEBUG:-false}${NC}" - echo -e "${BLUE}ℹ${NC} Tag format: ${WHITE}${TAG_FORMAT:-$DEFAULT_TAG_FORMAT}${NC}" + echo -e "${BLUE}ℹ${NC} Tag format: ${WHITE}${TAG_FORMAT:-full}${NC}" echo -e "${BLUE}ℹ${NC} Allowed CIDRs: ${WHITE}${CIDR_LIST[*]}${NC}" echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" @@ -902,336 +899,57 @@ main() { } -# Cache cleanup function -cleanup_vm_cache() { - local cache_dir="/tmp" - local vm_cache_ttl=${VM_IP_CACHE_TTL:-120} - local lxc_cache_ttl=${LXC_IP_CACHE_TTL:-120} - local status_cache_ttl=${LXC_STATUS_CACHE_TTL:-30} - local current_time=$(date +%s) - - debug_log "Starting extreme cache cleanup" - - # Clean VM cache files - for cache_file in "$cache_dir"/iptag_vm_*_cache; do - if [[ -f "$cache_file" ]]; then - local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) - if [[ $((current_time - file_time)) -gt $vm_cache_ttl ]]; then - rm -f "$cache_file" 2>/dev/null - debug_log "Cleaned up expired VM cache file: $cache_file" - fi - fi - done - - # Clean LXC IP cache files - for cache_file in "$cache_dir"/iptag_lxc_*_cache; do - if [[ -f "$cache_file" ]]; then - local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) - if [[ $((current_time - file_time)) -gt $lxc_cache_ttl ]]; then - rm -f "$cache_file" 2>/dev/null - # Also clean meta files - rm -f "${cache_file}.meta" 2>/dev/null - debug_log "Cleaned up expired LXC cache file: $cache_file" - fi - fi - done - - # Clean LXC status cache files (shorter TTL) - for cache_file in "$cache_dir"/iptag_lxc_status_*_cache; do - if [[ -f "$cache_file" ]]; then - local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) - if [[ $((current_time - file_time)) -gt $status_cache_ttl ]]; then - rm -f "$cache_file" 2>/dev/null - debug_log "Cleaned up expired LXC status cache: $cache_file" - fi - fi - done - - # Clean LXC PID cache files (60 second TTL) - for cache_file in "$cache_dir"/iptag_lxc_pid_*_cache; do - if [[ -f "$cache_file" ]]; then - local file_time=$(stat -c %Y "$cache_file" 2>/dev/null || echo 0) - if [[ $((current_time - file_time)) -gt 60 ]]; then - rm -f "$cache_file" 2>/dev/null - debug_log "Cleaned up expired LXC PID cache: $cache_file" - fi - fi - done - - # Clean any orphaned meta files - for meta_file in "$cache_dir"/iptag_*.meta; do - if [[ -f "$meta_file" ]]; then - local base_file="${meta_file%.meta}" - if [[ ! -f "$base_file" ]]; then - rm -f "$meta_file" 2>/dev/null - debug_log "Cleaned up orphaned meta file: $meta_file" - fi - fi - done - - debug_log "Completed extreme cache cleanup" -} -# Parallel VM processing function -process_vms_parallel() { - local vm_list=("$@") - local max_parallel=${MAX_PARALLEL_VM_CHECKS:-5} - local job_count=0 - local pids=() - local pid_start_times=() - - for vmid in "${vm_list[@]}"; do - if [[ $job_count -ge $max_parallel ]]; then - local pid_to_wait="${pids[0]}" - local start_time="${pid_start_times[0]}" - local waited=0 - while kill -0 "$pid_to_wait" 2>/dev/null && [[ $waited -lt 10 ]]; do - sleep 1 - ((waited++)) - done - if kill -0 "$pid_to_wait" 2>/dev/null; then - kill -9 "$pid_to_wait" 2>/dev/null - log_warning "VM parallel: killed stuck process $pid_to_wait after 10s timeout" - else - wait "$pid_to_wait" - fi - pids=("${pids[@]:1}") - pid_start_times=("${pid_start_times[@]:1}") - ((job_count--)) - fi - # Start background job - (update_tags "vm" "$vmid") & - pids+=($!) - pid_start_times+=("$(date +%s)") - ((job_count++)) - done - for i in "${!pids[@]}"; do - local pid="${pids[$i]}" - local waited=0 - while kill -0 "$pid" 2>/dev/null && [[ $waited -lt 10 ]]; do - sleep 1 - ((waited++)) - done - if kill -0 "$pid" 2>/dev/null; then - kill -9 "$pid" 2>/dev/null - log_warning "VM parallel: killed stuck process $pid after 10s timeout" - else - wait "$pid" - fi - done -} -# Parallel LXC processing function -process_lxc_parallel() { - local lxc_list=("$@") - local max_parallel=${MAX_PARALLEL_LXC_CHECKS:-2} - local batch_size=${LXC_BATCH_SIZE:-20} - local job_count=0 - local pids=() - local pid_start_times=() - - debug_log "Starting parallel LXC processing: ${#lxc_list[@]} containers, max_parallel=$max_parallel" - - if [[ ${#lxc_list[@]} -gt 5 ]]; then - debug_log "Pre-loading LXC statuses for ${#lxc_list[@]} containers" - local all_statuses=$(pct list 2>/dev/null) - for vmid in "${lxc_list[@]}"; do - local status=$(echo "$all_statuses" | grep "^$vmid" | awk '{print $2}') - if [[ -n "$status" ]]; then - local status_cache_file="/tmp/iptag_lxc_status_${vmid}_cache" - echo "$status" > "$status_cache_file" 2>/dev/null & - fi - done - wait - debug_log "Completed batch status pre-loading" - fi - for vmid in "${lxc_list[@]}"; do - if [[ $job_count -ge $max_parallel ]]; then - local pid_to_wait="${pids[0]}" - local start_time="${pid_start_times[0]}" - local waited=0 - while kill -0 "$pid_to_wait" 2>/dev/null && [[ $waited -lt 10 ]]; do - sleep 1 - ((waited++)) - done - if kill -0 "$pid_to_wait" 2>/dev/null; then - kill -9 "$pid_to_wait" 2>/dev/null - log_warning "LXC parallel: killed stuck process $pid_to_wait after 10s timeout" - else - wait "$pid_to_wait" - fi - pids=("${pids[@]:1}") - pid_start_times=("${pid_start_times[@]:1}") - ((job_count--)) - fi - # Start background job with higher priority - (update_tags "lxc" "$vmid") & - pids+=($!) - pid_start_times+=("$(date +%s)") - ((job_count++)) - done - for i in "${!pids[@]}"; do - local pid="${pids[$i]}" - local waited=0 - while kill -0 "$pid" 2>/dev/null && [[ $waited -lt 10 ]]; do - sleep 1 - ((waited++)) - done - if kill -0 "$pid" 2>/dev/null; then - kill -9 "$pid" 2>/dev/null - log_warning "LXC parallel: killed stuck process $pid after 10s timeout" - else - wait "$pid" - fi - done - debug_log "Completed parallel LXC processing" -} -# Optimized LXC IP detection with caching and alternative methods +# Simple LXC IP detection get_lxc_ips() { local vmid=$1 - local status_cache_file="/tmp/iptag_lxc_status_${vmid}_cache" - local status_cache_ttl=${LXC_STATUS_CACHE_TTL:-30} - debug_log "lxc $vmid: starting extreme optimized IP detection" - - # Check status cache first (avoid expensive pct status calls) - local lxc_status="" - if [[ -f "$status_cache_file" ]] && [[ $(($(date +%s) - $(stat -c %Y "$status_cache_file" 2>/dev/null || echo 0))) -lt $status_cache_ttl ]]; then - lxc_status=$(cat "$status_cache_file" 2>/dev/null) - debug_log "lxc $vmid: using cached status: $lxc_status" - else - lxc_status=$(pct status "${vmid}" 2>/dev/null | awk '{print $2}') - echo "$lxc_status" > "$status_cache_file" 2>/dev/null - debug_log "lxc $vmid: fetched fresh status: $lxc_status" - fi + debug_log "lxc $vmid: starting IP detection" + # Check if LXC is running + local lxc_status=$(pct status "${vmid}" 2>/dev/null | awk '{print $2}') if [[ "$lxc_status" != "running" ]]; then debug_log "lxc $vmid: not running (status: $lxc_status)" return fi local ips="" - local method_used="" - # EXTREME Method 1: Direct Proxmox config inspection (super fast) - debug_log "lxc $vmid: trying direct Proxmox config inspection" + # Method 1: Check Proxmox config for static IP 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) - debug_log "lxc $vmid: [CONFIG] static_ip='$static_ip' (from $pve_lxc_config)" 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 Proxmox config" + debug_log "lxc $vmid: found static IP $static_ip in config" ips="$static_ip" - method_used="proxmox_config" fi - else - debug_log "lxc $vmid: [CONFIG] config file not found: $pve_lxc_config" fi - # EXTREME Method 2: Direct network namespace inspection (fastest dynamic) + # Method 2: ARP table lookup if no static IP + 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 + fi + fi + + # Method 3: Direct container command if ARP failed if [[ -z "$ips" ]]; then - debug_log "lxc $vmid: trying optimized namespace inspection" - local ns_file="/var/lib/lxc/${vmid}/rootfs/proc/net/fib_trie" - if [[ -f "$ns_file" ]]; then - local ns_ip=$(timeout 1 grep -m1 -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' "$ns_file" 2>/dev/null | grep -v '127.0.0.1' | head -1) - debug_log "lxc $vmid: [NAMESPACE] ns_ip='$ns_ip'" - if [[ -n "$ns_ip" ]] && is_valid_ipv4 "$ns_ip"; then - debug_log "lxc $vmid: found IP $ns_ip via namespace inspection" - ips="$ns_ip" - method_used="namespace" - fi - else - debug_log "lxc $vmid: [NAMESPACE] ns_file not found: $ns_file" + 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" fi fi - # EXTREME Method 3: Batch ARP table lookup (if namespace failed) - if [[ -z "$ips" ]]; then - debug_log "lxc $vmid: trying batch ARP lookup" - local bridge_name=""; local mac_addr="" - if [[ -f "$pve_lxc_config" ]]; then - bridge_name=$(grep -Eo 'bridge=[^,]+' "$pve_lxc_config" | head -1 | cut -d'=' -f2) - mac_addr=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" | head -1 | cut -d'=' -f2) - debug_log "lxc $vmid: [ARP] bridge_name='$bridge_name' mac_addr='$mac_addr' (from $pve_lxc_config)" - fi - if [[ -z "$bridge_name" || -z "$mac_addr" ]]; then - local lxc_config="/var/lib/lxc/${vmid}/config" - if [[ -f "$lxc_config" ]]; then - [[ -z "$bridge_name" ]] && bridge_name=$(grep "lxc.net.0.link" "$lxc_config" 2>/dev/null | cut -d'=' -f2 | tr -d ' ') - [[ -z "$mac_addr" ]] && mac_addr=$(grep "lxc.net.0.hwaddr" "$lxc_config" 2>/dev/null | cut -d'=' -f2 | tr -d ' ') - debug_log "lxc $vmid: [ARP] bridge_name='$bridge_name' mac_addr='$mac_addr' (from $lxc_config)" - else - debug_log "lxc $vmid: [ARP] lxc config not found: $lxc_config" - fi - fi - if [[ -n "$bridge_name" && -n "$mac_addr" ]]; then - local bridge_ip=$(ip neighbor show dev "$bridge_name" 2>/dev/null | grep "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) - debug_log "lxc $vmid: [ARP] bridge_ip='$bridge_ip'" - if [[ -n "$bridge_ip" && "$bridge_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - debug_log "lxc $vmid: found IP $bridge_ip via ARP table" - ips="$bridge_ip" - method_used="arp_table" - fi - fi - fi - - # EXTREME Method 4: Fast process namespace (if ARP failed) - if [[ -z "$ips" ]] && [[ "${LXC_SKIP_SLOW_METHODS:-true}" != "true" ]]; then - debug_log "lxc $vmid: trying fast process namespace" - local pid_cache_file="/tmp/iptag_lxc_pid_${vmid}_cache" - local container_pid="" - if [[ -f "$pid_cache_file" ]] && [[ $(($(date +%s) - $(stat -c %Y "$pid_cache_file" 2>/dev/null || echo 0))) -lt 60 ]]; then - container_pid=$(cat "$pid_cache_file" 2>/dev/null) - else - container_pid=$(pct list 2>/dev/null | grep "^$vmid" | awk '{print $3}') - [[ -n "$container_pid" && "$container_pid" != "-" ]] && echo "$container_pid" > "$pid_cache_file" - fi - debug_log "lxc $vmid: [PROCESS_NS] container_pid='$container_pid'" - if [[ -n "$container_pid" && "$container_pid" != "-" ]]; then - local ns_ip=$(timeout 1 nsenter -t "$container_pid" -n 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) - debug_log "lxc $vmid: [PROCESS_NS] ns_ip='$ns_ip'" - if [[ -n "$ns_ip" ]] && is_valid_ipv4 "$ns_ip"; then - debug_log "lxc $vmid: found IP $ns_ip via process namespace" - ips="$ns_ip" - method_used="process_ns" - fi - fi - fi - - # Fallback: always do lxc-attach/pct exec with timeout if nothing found - if [[ -z "$ips" && "${LXC_ALLOW_FORCED_COMMANDS:-true}" == "true" ]]; then - debug_log "lxc $vmid: trying fallback lxc-attach (forced)" - local attach_ip="" - attach_ip=$(timeout 7s lxc-attach -n "$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) - local attach_status=$? - debug_log "lxc $vmid: [LXC_ATTACH] attach_ip='$attach_ip' status=$attach_status" - if [[ $attach_status -eq 124 ]]; then - debug_log "lxc $vmid: lxc-attach timed out after 7s" - fi - if [[ -n "$attach_ip" ]] && is_valid_ipv4 "$attach_ip"; then - debug_log "lxc $vmid: found IP $attach_ip via lxc-attach (forced)" - ips="$attach_ip" - method_used="lxc_attach_forced" - fi - fi - if [[ -z "$ips" && "${LXC_ALLOW_FORCED_COMMANDS:-true}" == "true" ]]; then - debug_log "lxc $vmid: trying fallback pct exec (forced)" - local pct_ip="" - pct_ip=$(timeout 7s 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) - local pct_status=$? - debug_log "lxc $vmid: [PCT_EXEC] pct_ip='$pct_ip' status=$pct_status" - if [[ $pct_status -eq 124 ]]; then - debug_log "lxc $vmid: pct exec timed out after 7s" - fi - if [[ -n "$pct_ip" ]] && is_valid_ipv4 "$pct_ip"; then - debug_log "lxc $vmid: found IP $pct_ip via pct exec (forced)" - ips="$pct_ip" - method_used="pct_exec_forced" - fi - fi - - debug_log "lxc $vmid: [RESULT] ips='$ips' method='$method_used'" + debug_log "lxc $vmid: final IPs: '$ips'" echo "$ips" } @@ -1239,28 +957,44 @@ main EOF } -# Main installation process -if check_service_exists; then - while true; do - read -p "IP-Tag service is already installed. Do you want to update it? (y/n): " yn - case $yn in - [Yy]*) +# Choose installation/update mode +echo -e "\n${YW}Choose action:${CL}" +echo -e "${GN}1)${CL} Install with automatic service (recommended)" +echo -e "${GN}2)${CL} Install command only (manual execution)" +echo -e "${GN}3)${CL} Update existing installation" +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 ;; - [Nn]*) - msg_error "Installation cancelled." + 4) + msg_error "Action cancelled." exit 0 ;; *) - msg_error "Please answer yes or no." + msg_error "Please enter 1, 2, 3, or 4." ;; - esac - done -fi + esac +done +echo -e "\n${YW}This will install ${APP} on ${hostname} in $INSTALL_MODE mode.${CL}" while true; do - read -p "This will install ${APP} on ${hostname}. Proceed? (y/n): " yn + read -p "Proceed? (y/n): " yn case $yn in [Yy]*) break @@ -1277,83 +1011,97 @@ done if ! pveversion | grep -Eq "pve-manager/(8\.[0-4]|9\.[0-9]+)(\.[0-9]+)*"; then msg_error "This version of Proxmox Virtual Environment is not supported" - msg_error "⚠️ Requires Proxmox Virtual Environment Version 8.0–8.4 or 9.x." + msg_error "⚠ Requires Proxmox Virtual Environment Version 8.0–8.4 or 9.x." msg_error "Exiting..." sleep 2 exit fi -FILE_PATH="/usr/local/bin/iptag" -if [[ -f "$FILE_PATH" ]]; then - msg_info "The file already exists: '$FILE_PATH'. Skipping installation." - exit 0 -fi - msg_info "Installing Dependencies" apt-get update &>/dev/null apt-get install -y ipcalc net-tools &>/dev/null msg_ok "Installed Dependencies" -msg_info "Setting up IP-Tag Scripts" -mkdir -p /opt/iptag -msg_ok "Setup IP-Tag Scripts" +# Execute installation based on selected mode +if [[ "$INSTALL_MODE" == "service" ]]; then + # Full service installation + msg_info "Setting up IP-Tag Scripts" + mkdir -p /opt/iptag + msg_ok "Setup IP-Tag Scripts" -# Migrate config if needed -migrate_config + # Migrate config if needed + migrate_config -msg_info "Setup Default Config" -if [[ ! -f /opt/iptag/iptag.conf ]]; then - generate_config >/opt/iptag/iptag.conf - msg_ok "Setup default config" -else - msg_ok "Default config already exists" -fi + # Interactive configuration setup + if [[ ! -f /opt/iptag/iptag.conf ]]; then + interactive_config_setup + msg_info "Setup Default Config" + generate_config >/opt/iptag/iptag.conf + msg_ok "Setup default config" + else + stop_spinner + 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" + ;; + esac + fi -msg_info "Setup Main Function" -if [[ ! -f /opt/iptag/iptag ]]; then + msg_info "Setup Main Function" generate_main_script >/opt/iptag/iptag chmod +x /opt/iptag/iptag msg_ok "Setup Main Function" -else - msg_ok "Main Function already exists" -fi -msg_info "Creating Service" -if [[ ! -f /lib/systemd/system/iptag.service ]]; then + msg_info "Creating Service" generate_service >/lib/systemd/system/iptag.service msg_ok "Created Service" -else - msg_ok "Service already exists." -fi -msg_ok "Setup IP-Tag Scripts" + msg_info "Starting Service" + systemctl daemon-reload &>/dev/null + systemctl enable -q --now iptag.service &>/dev/null + msg_ok "Started Service" -msg_info "Starting Service" -systemctl daemon-reload &>/dev/null -systemctl enable -q --now iptag.service &>/dev/null -msg_ok "Started Service" - -msg_info "Restarting Service with optimizations" -systemctl restart iptag.service &>/dev/null -msg_ok "Service restarted with CPU optimizations" - -msg_info "Creating manual run command" -cat <<'EOF' >/usr/local/bin/iptag-run + msg_info "Creating manual run command" + cat <<'EOF' >/usr/local/bin/iptag-run #!/usr/bin/env bash CONFIG_FILE="/opt/iptag/iptag.conf" SCRIPT_FILE="/opt/iptag/iptag" if [[ ! -f "$SCRIPT_FILE" ]]; then - echo "❌ Main script not found: $SCRIPT_FILE" + echo "✗ Main script not found: $SCRIPT_FILE" exit 1 fi export FORCE_SINGLE_RUN=true exec "$SCRIPT_FILE" EOF -chmod +x /usr/local/bin/iptag-run -msg_ok "Created iptag-run executable - You can execute this manually by entering “iptag-run” in the Proxmox host, so the script is executed by hand." + 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 -SPINNER_PID="" -echo -e "\n${APP} installation completed successfully! ${CL}\n" - -# Proper script termination +# Clean up any running spinner and exit +stop_spinner exit 0