2025-04-19 23:00:43 +05:00
#!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: MickLesk (Canbiz) && Desert_Gamer
# License: MIT
function header_info {
clear
cat <<"EOF"
___ ____ _____
| _ _| _ \ _ | _ _| _ _ __ _
| || | _) ( _) | | / _` | / _` |
| || __/ _ | | ( _| | ( _| |
| ___| _| ( _) | _| \_ _,_| \_ _, |
| ___/
EOF
}
clear
header_info
APP = "IP-Tag"
hostname = $( hostname)
2025-07-04 11:38:05 +03:00
# Color variables
2025-04-19 23:00:43 +05:00
YW = $( echo "\033[33m" )
GN = $( echo "\033[1;92m" )
RD = $( echo "\033[01;31m" )
CL = $( echo "\033[m" )
BFR = "\\r\\033[K"
HOLD = " "
CM = " ✔️ ${ CL } "
CROSS = " ✖️ ${ CL } "
2025-07-04 11:38:05 +03:00
# Error handler for displaying error messages
2025-04-19 23:00:43 +05:00
error_handler( ) {
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID >/dev/null; then
kill $SPINNER_PID >/dev/null
fi
printf "\e[?25h"
local exit_code = " $? "
local line_number = " $1 "
local command = " $2 "
local error_message = " ${ RD } [ERROR] ${ CL } in line ${ RD } $line_number ${ CL } : exit code ${ RD } $exit_code ${ CL } : while executing command ${ YW } $command ${ CL } "
echo -e " \n $error_message \n "
}
2025-07-04 11:38:05 +03:00
# Spinner for progress indication
2025-04-19 23:00:43 +05:00
spinner( ) {
local frames = ( '⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏' )
local spin_i = 0
local interval = 0.1
printf "\e[?25l"
local color = " ${ YWB } "
while true; do
printf " \r ${ color } %s ${ CL } " " ${ frames [spin_i] } "
spin_i = $(( ( spin_i + 1 ) % ${# frames [@] } ))
sleep " $interval "
done
}
2025-07-04 11:38:05 +03:00
# Info message
2025-04-19 23:00:43 +05:00
msg_info( ) {
local msg = " $1 "
echo -ne " ${ TAB } ${ YW } ${ HOLD } ${ msg } ${ HOLD } "
spinner &
SPINNER_PID = $!
}
2025-07-04 11:38:05 +03:00
# Success message
2025-04-19 23:00:43 +05:00
msg_ok( ) {
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID >/dev/null; then
kill $SPINNER_PID >/dev/null
fi
printf "\e[?25h"
local msg = " $1 "
echo -e " ${ BFR } ${ CM } ${ GN } ${ msg } ${ CL } "
}
2025-07-04 11:38:05 +03:00
# Error message
2025-04-19 23:00:43 +05:00
msg_error( ) {
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID >/dev/null; then
kill $SPINNER_PID >/dev/null
fi
printf "\e[?25h"
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"
local new_config = "/opt/iptag/iptag.conf"
if [ [ -f " $old_config " ] ] ; then
msg_info "Migrating configuration from old path"
if cp " $old_config " " $new_config " & >/dev/null; then
rm -rf " $old_config " & >/dev/null
msg_ok "Configuration migrated and old config removed"
else
msg_error "Failed to migrate configuration"
fi
fi
}
# Update existing installation
update_installation( ) {
msg_info "Updating IP-Tag Scripts"
systemctl stop iptag.service & >/dev/null
2025-07-04 11:38:05 +03:00
msg_ok "Stopped IP-Tag service"
2025-04-19 23:00:43 +05:00
# Create directory if it doesn't exist
if [ [ ! -d "/opt/iptag" ] ] ; then
mkdir -p /opt/iptag
fi
2025-07-04 11:38:05 +03:00
# 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 } "
while true; do
read -p "Do you want to replace it with defaults? (y/n): " yn
case $yn in
[ Yy] *)
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 } "
break
; ;
*)
echo -e " ${ RD } Please answer yes or no. ${ CL } "
; ;
esac
done
else
msg_info "Creating new configuration file"
generate_config >/opt/iptag/iptag.conf
msg_ok "Created new configuration file at /opt/iptag/iptag.conf"
fi
2025-04-19 23:00:43 +05:00
# Update main script
2025-07-04 11:38:05 +03:00
msg_info "Updating main script"
generate_main_script >/opt/iptag/iptag
chmod +x /opt/iptag/iptag
msg_ok "Updated main script"
# Update service file
msg_info "Updating service file"
generate_service >/lib/systemd/system/iptag.service
msg_ok "Updated service file"
2025-07-07 14:41:23 +02:00
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 "
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."
2025-07-04 11:38:05 +03:00
msg_info "Restarting service"
systemctl daemon-reload & >/dev/null
systemctl enable -q --now iptag.service & >/dev/null
msg_ok "Updated IP-Tag Scripts"
}
# Generate configuration file content
generate_config( ) {
cat <<EOF
# Configuration file for LXC IP tagging
# List of allowed CIDRs
CIDR_LIST = (
192.168.0.0/16
10.0.0.0/8
100.64.0.0/10
)
# Tag format options:
# - "full": full IP address (e.g., 192.168.0.100)
# - "last_octet": only the last octet (e.g., 100)
# - "last_two_octets": last two octets (e.g., 0.100)
TAG_FORMAT = "last_two_octets"
# Interval settings (in seconds) - optimized for lower CPU usage
LOOP_INTERVAL = 300
VM_STATUS_CHECK_INTERVAL = 600
FW_NET_INTERFACE_CHECK_INTERVAL = 900
LXC_STATUS_CHECK_INTERVAL = 300
FORCE_UPDATE_INTERVAL = 7200
# Performance optimizations
2025-07-07 14:26:07 +02:00
VM_IP_CACHE_TTL = 300
2025-07-07 14:41:23 +02:00
MAX_PARALLEL_VM_CHECKS = 1
2025-07-04 11:38:05 +03:00
# LXC performance optimizations
2025-07-07 14:26:07 +02:00
LXC_IP_CACHE_TTL = 300
MAX_PARALLEL_LXC_CHECKS = 2
2025-07-04 11:38:05 +03:00
# Extreme LXC optimizations
2025-07-07 14:26:07 +02:00
LXC_BATCH_SIZE = 3
LXC_STATUS_CACHE_TTL = 300
2025-07-04 11:38:05 +03:00
LXC_AGGRESSIVE_CACHING = true
LXC_SKIP_SLOW_METHODS = true
2025-07-07 14:41:23 +02:00
LXC_ALLOW_FORCED_COMMANDS = false
2025-07-04 11:38:05 +03:00
# Debug settings (set to true to enable debugging)
DEBUG = false
EOF
}
# Generate systemd service file content
generate_service( ) {
cat <<EOF
[ Unit]
Description = IP-Tag service
After = network.target
[ Service]
Type = simple
ExecStart = /opt/iptag/iptag
2025-07-07 14:26:07 +02:00
Restart = on-failure
RestartSec = 10
2025-07-04 11:38:05 +03:00
[ Install]
WantedBy = multi-user.target
EOF
}
# Generate main script content
generate_main_script( ) {
cat <<'EOF'
2025-04-19 23:00:43 +05:00
#!/bin/bash
# =============== CONFIGURATION =============== #
readonly CONFIG_FILE = "/opt/iptag/iptag.conf"
readonly DEFAULT_TAG_FORMAT = "full"
readonly DEFAULT_CHECK_INTERVAL = 60
# Load the configuration file if it exists
if [ -f " $CONFIG_FILE " ] ; then
# shellcheck source=./iptag.conf
source " $CONFIG_FILE "
fi
2025-07-04 11:38:05 +03:00
# Set default DEBUG value if not defined
DEBUG = ${ DEBUG :- false }
# Debug logging function
debug_log( ) {
if [ [ " $DEBUG " = = "true" || " $DEBUG " = = "1" ] ] ; then
echo " [DEBUG] $* " >& 2
fi
}
# Color constants
readonly RED = '\033[0;31m'
readonly GREEN = '\033[0;32m'
readonly YELLOW = '\033[0;33m'
readonly BLUE = '\033[0;34m'
readonly PURPLE = '\033[0;35m'
readonly CYAN = '\033[0;36m'
readonly WHITE = '\033[1;37m'
readonly GRAY = '\033[0;37m'
readonly NC = '\033[0m' # No Color
# Logging functions with colors
log_success( ) {
echo -e " ${ GREEN } ✓ ${ NC } $* "
}
log_info( ) {
echo -e " ${ BLUE } ℹ ${ NC } $* "
}
log_warning( ) {
echo -e " ${ YELLOW } ⚠ ${ NC } $* "
}
log_error( ) {
echo -e " ${ RED } ✗ ${ NC } $* "
}
log_change( ) {
echo -e " ${ CYAN } ~ ${ NC } $* "
}
log_unchanged( ) {
echo -e " ${ GRAY } = ${ NC } $* "
2025-04-19 23:00:43 +05:00
}
# Check if IP is in CIDR
ip_in_cidr( ) {
local ip = " $1 " cidr = " $2 "
2025-07-04 11:38:05 +03:00
debug_log " ip_in_cidr: checking ' $ip ' against ' $cidr ' "
# Manual CIDR check - более надёжный метод
debug_log "ip_in_cidr: using manual check (bypassing ipcalc)"
local network prefix
IFS = '/' read -r network prefix <<< " $cidr "
# Convert IP and network to integers for comparison
local ip_int net_int mask
IFS = '.' read -r a b c d <<< " $ip "
ip_int = $(( ( a << 24 ) + ( b << 16 ) + ( c << 8 ) + d ))
IFS = '.' read -r a b c d <<< " $network "
net_int = $(( ( a << 24 ) + ( b << 16 ) + ( c << 8 ) + d ))
# Create subnet mask
mask = $(( 0 xFFFFFFFF << ( 32 - prefix) ))
# Apply mask and compare
local ip_masked = $(( ip_int & mask))
local net_masked = $(( net_int & mask))
debug_log " ip_in_cidr: IP= $ip ( $ip_int ), Network= $network ( $net_int ), Prefix= $prefix "
debug_log " ip_in_cidr: Mask= $mask (hex: $( printf '0x%08x' $mask ) ) "
debug_log " ip_in_cidr: IP&Mask= $ip_masked ( $( printf '%d.%d.%d.%d' $(( ip_masked>>24& 255 )) $(( ip_masked>>16& 255 )) $(( ip_masked>>8& 255 )) $(( ip_masked& 255 )) ) ) "
debug_log " ip_in_cidr: Net&Mask= $net_masked ( $( printf '%d.%d.%d.%d' $(( net_masked>>24& 255 )) $(( net_masked>>16& 255 )) $(( net_masked>>8& 255 )) $(( net_masked& 255 )) ) ) "
if ( ( ip_masked = = net_masked ) ) ; then
debug_log "ip_in_cidr: manual check PASSED - IP is in CIDR"
return 0
else
debug_log "ip_in_cidr: manual check FAILED - IP is NOT in CIDR"
return 1
fi
2025-04-19 23:00:43 +05:00
}
# Format IP address according to the configuration
format_ip_tag( ) {
local ip = " $1 "
2025-07-04 11:38:05 +03:00
[ [ -z " $ip " ] ] && return
2025-04-19 23:00:43 +05:00
local format = " ${ TAG_FORMAT :- $DEFAULT_TAG_FORMAT } "
case " $format " in
"last_octet" ) echo " ${ ip ##*. } " ; ;
"last_two_octets" ) echo " ${ ip #*.*. } " ; ;
*) echo " $ip " ; ;
esac
}
# Check if IP is in any CIDRs
ip_in_cidrs( ) {
local ip = " $1 " cidrs = " $2 "
[ [ -z " $cidrs " ] ] && return 1
local IFS = ' '
2025-07-04 11:38:05 +03:00
debug_log " Checking IP ' $ip ' against CIDRs: ' $cidrs ' "
for cidr in $cidrs ; do
debug_log " Testing IP ' $ip ' against CIDR ' $cidr ' "
if ip_in_cidr " $ip " " $cidr " ; then
debug_log " IP ' $ip ' matches CIDR ' $cidr ' - PASSED "
return 0
else
debug_log " IP ' $ip ' does not match CIDR ' $cidr ' "
fi
done
debug_log " IP ' $ip ' failed all CIDR checks "
2025-04-19 23:00:43 +05:00
return 1
}
# Check if IP is valid
is_valid_ipv4( ) {
local ip = " $1 "
[ [ " $ip " = ~ ^( [ 0-9] { 1,3} \. ) { 3} [ 0-9] { 1,3} $ ] ] || return 1
local IFS = '.' parts
read -ra parts <<< " $ip "
for part in " ${ parts [@] } " ; do
( ( part >= 0 && part <= 255 ) ) || return 1
done
return 0
}
2025-07-04 11:38:05 +03:00
# Get VM IPs using multiple methods with performance optimizations
2025-04-19 23:00:43 +05:00
get_vm_ips( ) {
2025-07-04 11:38:05 +03:00
local vmid = $1 ips = ""
local vm_config = " /etc/pve/qemu-server/ ${ vmid } .conf "
[ [ ! -f " $vm_config " ] ] && return
debug_log " vm $vmid : starting optimized IP detection "
# Check if VM is running first (avoid expensive operations for stopped VMs)
local vm_status = ""
if command -v qm >/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 "
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)
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
2025-04-19 23:00:43 +05:00
done
2025-07-04 11:38:05 +03:00
# 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
2025-04-19 23:00:43 +05:00
fi
2025-07-04 11:38:05 +03:00
# Method 2: QM guest agent (fast if available)
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)
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 "
ips += " $qm_ip "
fi
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
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)
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
fi
# Remove duplicates and cache result
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 ' "
echo " $unique_ips "
2025-04-19 23:00:43 +05:00
}
2025-07-04 11:38:05 +03:00
# Update tags for container or VM
2025-04-19 23:00:43 +05:00
update_tags( ) {
2025-07-04 11:38:05 +03:00
local type = " $1 " vmid = " $2 "
2025-04-19 23:00:43 +05:00
local current_ips_full
2025-07-04 11:38:05 +03:00
2025-04-19 23:00:43 +05:00
if [ [ " $type " = = "lxc" ] ] ; then
2025-07-04 11:38:05 +03:00
current_ips_full = $( get_lxc_ips " ${ vmid } " )
2025-07-07 14:41:23 +02:00
while IFS = read -r line; do
[ [ " $line " = = tags:* ] ] && current_tags_raw = " ${ line #tags : } " && break
done < <( pct config " $vmid " 2>/dev/null)
2025-04-19 23:00:43 +05:00
else
current_ips_full = $( get_vm_ips " ${ vmid } " )
2025-07-04 11:38:05 +03:00
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:]]*//' )
fi
2025-04-19 23:00:43 +05:00
fi
local current_tags = ( ) next_tags = ( ) current_ip_tags = ( )
2025-07-04 11:38:05 +03:00
if [ [ -n " $current_tags_raw " ] ] ; then
mapfile -t current_tags < <( echo " $current_tags_raw " | sed 's/;/\n/g' )
fi
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# Separate IP/numeric and user tags
2025-04-19 23:00:43 +05:00
for tag in " ${ current_tags [@] } " ; do
if is_valid_ipv4 " ${ tag } " || [ [ " $tag " = ~ ^[ 0-9] +( \. [ 0-9] +) *$ ] ] ; then
current_ip_tags += ( " ${ tag } " )
else
next_tags += ( " ${ tag } " )
fi
done
2025-07-04 11:38:05 +03:00
# Generate new IP tags from current IPs
local formatted_ips = ( )
debug_log " $type $vmid current_ips_full: ' $current_ips_full ' "
debug_log " $type $vmid CIDR_LIST: ${ CIDR_LIST [*] } "
for ip in $current_ips_full ; do
[ [ -z " $ip " ] ] && continue
debug_log " $type $vmid processing IP: ' $ip ' "
if is_valid_ipv4 " $ip " ; then
debug_log " $type $vmid IP ' $ip ' is valid "
if ip_in_cidrs " $ip " " ${ CIDR_LIST [*] } " ; then
debug_log " $type $vmid IP ' $ip ' passed CIDR check "
local formatted_ip = $( format_ip_tag " $ip " )
debug_log " $type $vmid formatted ' $ip ' -> ' $formatted_ip ' "
[ [ -n " $formatted_ip " ] ] && formatted_ips += ( " $formatted_ip " )
else
debug_log " $type $vmid IP ' $ip ' failed CIDR check "
2025-04-19 23:00:43 +05:00
fi
2025-07-04 11:38:05 +03:00
else
debug_log " $type $vmid IP ' $ip ' is invalid "
2025-04-19 23:00:43 +05:00
fi
done
2025-07-04 11:38:05 +03:00
debug_log " $type $vmid final formatted_ips: ${ formatted_ips [*] } "
# If LXC and no IPs detected, do not touch tags at all
if [ [ " $type " = = "lxc" && ${# formatted_ips [@] } -eq 0 ] ] ; then
log_unchanged " LXC ${ GRAY } ${ vmid } ${ NC } : No IP detected, tags unchanged "
return
fi
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# Add new IP tags
for new_ip in " ${ formatted_ips [@] } " ; do
next_tags += ( " $new_ip " )
done
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# Update tags if there are changes
local old_tags_str = $( IFS = ';' ; echo " ${ current_tags [*] } " )
local new_tags_str = $( IFS = ';' ; echo " ${ next_tags [*] } " )
debug_log " $type $vmid old_tags: ' $old_tags_str ' "
debug_log " $type $vmid new_tags: ' $new_tags_str ' "
debug_log " $type $vmid tags_equal: $( [ [ " $old_tags_str " = = " $new_tags_str " ] ] && echo true || echo false ) "
if [ [ " $old_tags_str " != " $new_tags_str " ] ] ; then
# Determine what changed
local old_ip_tags_count = ${# current_ip_tags [@] }
local new_ip_tags_count = ${# formatted_ips [@] }
# Build detailed change message
local change_details = ""
if [ [ $old_ip_tags_count -eq 0 ] ] ; then
change_details = " added ${ new_ip_tags_count } IP tag(s): [ ${ GREEN } ${ formatted_ips [*] } ${ NC } ] "
else
# Compare old and new IP tags
local added_tags = ( ) removed_tags = ( ) common_tags = ( )
# Find removed tags
for old_tag in " ${ current_ip_tags [@] } " ; do
local found = false
for new_tag in " ${ formatted_ips [@] } " ; do
if [ [ " $old_tag " = = " $new_tag " ] ] ; then
found = true
break
fi
done
if [ [ " $found " = = false ] ] ; then
removed_tags += ( " $old_tag " )
else
common_tags += ( " $old_tag " )
fi
done
# Find added tags
for new_tag in " ${ formatted_ips [@] } " ; do
local found = false
for old_tag in " ${ current_ip_tags [@] } " ; do
if [ [ " $new_tag " = = " $old_tag " ] ] ; then
found = true
break
fi
done
if [ [ " $found " = = false ] ] ; then
added_tags += ( " $new_tag " )
fi
done
# Build change message
local change_parts = ( )
if [ [ ${# added_tags [@] } -gt 0 ] ] ; then
change_parts += ( " added [ ${ GREEN } ${ added_tags [*] } ${ NC } ] " )
fi
if [ [ ${# removed_tags [@] } -gt 0 ] ] ; then
change_parts += ( " removed [ ${ YELLOW } ${ removed_tags [*] } ${ NC } ] " )
fi
if [ [ ${# common_tags [@] } -gt 0 ] ] ; then
change_parts += ( " kept [ ${ GRAY } ${ common_tags [*] } ${ NC } ] " )
2025-04-19 23:00:43 +05:00
fi
2025-07-04 11:38:05 +03:00
change_details = $( IFS = ', ' ; echo " ${ change_parts [*] } " )
2025-04-19 23:00:43 +05:00
fi
2025-07-04 11:38:05 +03:00
log_change " ${ type ^^ } ${ CYAN } ${ vmid } ${ NC } : ${ change_details } "
if [ [ " $type " = = "lxc" ] ] ; then
pct set " ${ vmid } " -tags " $( IFS = ';' ; echo " ${ next_tags [*] } " ) " & >/dev/null
else
local vm_config = " /etc/pve/qemu-server/ ${ vmid } .conf "
if [ [ -f " $vm_config " ] ] ; then
sed -i '/^tags:/d' " $vm_config "
if [ [ ${# next_tags [@] } -gt 0 ] ] ; then
echo " tags: $( IFS = ';' ; echo " ${ next_tags [*] } " ) " >> " $vm_config "
fi
fi
fi
else
# Tags unchanged
local ip_count = ${# formatted_ips [@] }
local status_msg = ""
if [ [ $ip_count -eq 0 ] ] ; then
status_msg = "No IPs detected"
elif [ [ $ip_count -eq 1 ] ] ; then
status_msg = " IP tag [ ${ GRAY } ${ formatted_ips [0] } ${ NC } ] unchanged "
else
status_msg = " ${ ip_count } IP tags [ ${ GRAY } ${ formatted_ips [*] } ${ NC } ] unchanged "
fi
log_unchanged " ${ type ^^ } ${ GRAY } ${ vmid } ${ NC } : ${ status_msg } "
fi
}
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# Update all instances of specified type
update_all_tags( ) {
local type = " $1 " vmids count = 0
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 [@] } " )
fi
count = ${# vmids [@] }
[ [ $count -eq 0 ] ] && return
# 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 [@] } "
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 [@] } "
fi
# Add completion message
if [ [ " $type " = = "lxc" ] ] ; then
log_success "Completed processing LXC containers"
2025-04-19 23:00:43 +05:00
else
2025-07-04 11:38:05 +03:00
log_success "Completed processing virtual machines"
2025-04-19 23:00:43 +05:00
fi
}
# Check if status changed
2025-07-04 11:38:05 +03:00
check_status_changed( ) {
2025-04-19 23:00:43 +05:00
local type = " $1 " current
case " $type " in
"lxc" ) current = $( pct list 2>/dev/null | grep -v VMID) ; ;
2025-07-04 11:38:05 +03:00
"vm" ) current = $( ls -la /etc/pve/qemu-server/*.conf 2>/dev/null) ; ;
"fw" ) current = $( ip link show type bridge 2>/dev/null) ; ;
2025-04-19 23:00:43 +05:00
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 changes_detected = false
current_time = $( date +%s)
2025-07-07 14:41:23 +02:00
local update_lxc = false
local update_vm = false
2025-07-04 11:38:05 +03:00
# 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
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# Check LXC status
local time_since_last_lxc_check = $(( current_time - last_lxc_status_check_time))
if [ [ " ${ LXC_STATUS_CHECK_INTERVAL :- 60 } " -gt 0 ] ] && \
2025-07-07 14:41:23 +02:00
[ [ " $time_since_last_lxc_check " -ge " ${ LXC_STATUS_CHECK_INTERVAL :- 60 } " ] ] ; then
last_lxc_status_check_time = $current_time
2025-07-04 11:38:05 +03:00
if check_status_changed "lxc" ; then
2025-07-07 14:41:23 +02:00
update_lxc = true
log_warning "LXC status changes detected"
2025-04-19 23:00:43 +05:00
fi
2025-07-04 11:38:05 +03:00
fi
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# Check VM status
local time_since_last_vm_check = $(( current_time - last_vm_status_check_time))
if [ [ " ${ VM_STATUS_CHECK_INTERVAL :- 60 } " -gt 0 ] ] && \
2025-07-07 14:41:23 +02:00
[ [ " $time_since_last_vm_check " -ge " ${ VM_STATUS_CHECK_INTERVAL :- 60 } " ] ] ; then
last_vm_status_check_time = $current_time
2025-07-04 11:38:05 +03:00
if check_status_changed "vm" ; then
2025-07-07 14:41:23 +02:00
update_vm = true
log_warning "VM status changes detected"
2025-04-19 23:00:43 +05:00
fi
2025-07-04 11:38:05 +03:00
fi
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# 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 ] ] && \
2025-07-07 14:41:23 +02:00
[ [ " $time_since_last_fw_check " -ge " ${ FW_NET_INTERFACE_CHECK_INTERVAL :- 60 } " ] ] ; then
last_fw_net_interface_check_time = $current_time
2025-07-04 11:38:05 +03:00
if check_status_changed "fw" ; then
2025-07-07 14:41:23 +02:00
update_lxc = true
update_vm = true
log_warning "Network interface changes detected"
2025-04-19 23:00:43 +05:00
fi
fi
2025-07-07 14:41:23 +02:00
# Force update if interval exceeded
2025-07-04 11:38:05 +03:00
for type in "lxc" "vm" ; do
local last_update_var = " last_update_ ${ type } _time "
local time_since_last_update = $(( current_time - ${ !last_update_var } ))
2025-07-07 14:41:23 +02:00
if [ [ $time_since_last_update -ge ${ FORCE_UPDATE_INTERVAL :- 1800 } ] ] ; then
2025-07-04 11:38:05 +03:00
if [ [ " $type " = = "lxc" ] ] ; then
2025-07-07 14:41:23 +02:00
update_lxc = true
log_info " Scheduled LXC update (every $(( FORCE_UPDATE_INTERVAL / 60 )) minutes) "
2025-07-04 11:38:05 +03:00
else
2025-07-07 14:41:23 +02:00
update_vm = true
log_info " Scheduled VM update (every $(( FORCE_UPDATE_INTERVAL / 60 )) minutes) "
2025-07-04 11:38:05 +03:00
fi
eval " ${ last_update_var } = ${ current_time } "
fi
done
2025-07-07 14:41:23 +02:00
# Final execution
$update_lxc && update_all_tags "lxc"
$update_vm && update_all_tags "vm"
2025-04-19 23:00:43 +05:00
}
# Initialize time variables
declare -g last_lxc_status = "" last_vm_status = "" last_fw_status = ""
2025-07-04 11:38:05 +03:00
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
2025-04-19 23:00:43 +05:00
# Main loop
main( ) {
2025-07-04 11:38:05 +03:00
# 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 } Debug mode: ${ WHITE } ${ DEBUG :- false } ${ NC } "
echo -e " ${ BLUE } ℹ ${ NC } Tag format: ${ WHITE } ${ TAG_FORMAT :- $DEFAULT_TAG_FORMAT } ${ NC } "
echo -e " ${ BLUE } ℹ ${ NC } Allowed CIDRs: ${ WHITE } ${ CIDR_LIST [*] } ${ NC } "
echo -e " ${ PURPLE } ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ${ NC } \n "
2025-07-07 14:41:23 +02:00
if [ [ " $FORCE_SINGLE_RUN " = = "true" ] ] ; then
check
exit 0
fi
2025-07-07 14:26:07 +02:00
while true; do
check
sleep " ${ LOOP_INTERVAL :- 300 } "
done
2025-04-19 23:00:43 +05:00
}
2025-07-07 14:41:23 +02:00
2025-07-04 11:38:05 +03:00
# 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"
}
2025-04-19 23:00:43 +05:00
2025-07-04 11:38:05 +03:00
# 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 = ( " $@ " )
2025-07-07 14:41:23 +02:00
local max_parallel = ${ MAX_PARALLEL_LXC_CHECKS :- 2 }
2025-07-04 11:38:05 +03:00
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
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
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 "
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 "
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)
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 "
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
2025-07-07 14:41:23 +02:00
if [ [ -z " $ips " && " ${ LXC_ALLOW_FORCED_COMMANDS :- true } " = = "true" ] ] ; then
2025-07-04 11:38:05 +03:00
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
2025-07-07 14:41:23 +02:00
if [ [ -z " $ips " && " ${ LXC_ALLOW_FORCED_COMMANDS :- true } " = = "true" ] ] ; then
2025-07-04 11:38:05 +03:00
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 ' "
echo " $ips "
}
main
EOF
2025-04-19 23:00:43 +05:00
}
# 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] *)
update_installation
exit 0
; ;
[ Nn] *)
msg_error "Installation cancelled."
exit 0
; ;
*)
msg_error "Please answer yes or no."
; ;
esac
done
fi
while true; do
read -p " This will install ${ APP } on ${ hostname } . Proceed? (y/n): " yn
case $yn in
[ Yy] *)
break
; ;
[ Nn] *)
msg_error "Installation cancelled."
exit
; ;
*)
msg_error "Please answer yes or no."
; ;
esac
done
if ! pveversion | grep -Eq "pve-manager/8\.[0-4](\.[0-9]+)*" ; then
msg_error "This version of Proxmox Virtual Environment is not supported"
msg_error "⚠️ Requires Proxmox Virtual Environment Version 8.0 or later."
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"
# Migrate config if needed
migrate_config
msg_info "Setup Default Config"
if [ [ ! -f /opt/iptag/iptag.conf ] ] ; then
2025-07-04 11:38:05 +03:00
generate_config >/opt/iptag/iptag.conf
2025-04-19 23:00:43 +05:00
msg_ok "Setup default config"
else
msg_ok "Default config already exists"
fi
msg_info "Setup Main Function"
if [ [ ! -f /opt/iptag/iptag ] ] ; then
2025-07-04 11:38:05 +03:00
generate_main_script >/opt/iptag/iptag
chmod +x /opt/iptag/iptag
2025-04-19 23:00:43 +05:00
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
2025-07-04 11:38:05 +03:00
generate_service >/lib/systemd/system/iptag.service
2025-04-19 23:00:43 +05:00
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"
2025-07-04 11:38:05 +03:00
msg_info "Restarting Service with optimizations"
systemctl restart iptag.service & >/dev/null
msg_ok "Service restarted with CPU optimizations"
2025-07-07 14:26:07 +02:00
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 "
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."
2025-04-19 23:00:43 +05:00
SPINNER_PID = ""
echo -e " \n ${ APP } installation completed successfully! ${ CL } \n "
2025-07-04 11:38:05 +03:00
# Proper script termination
exit 0