2024-12-16 12:47:16 +00:00
#!/usr/bin/env bash
2025-01-01 12:37:29 +00:00
# Copyright (c) 2021-2025 community-scripts ORG
2024-12-16 12:47:16 +00:00
# Author: MickLesk (Canbiz)
# License: MIT
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/gitsang/lxc-iptag
function header_info {
2024-12-17 14:30:51 +00:00
clear
cat <<"EOF"
2024-12-16 12:47:16 +00:00
__ _ ________ ________ ______
/ / | | / / ____/ / _/ __ \ /_ __/___ _____ _
/ / | / / / // /_/ /_____/ / / __ ` / __ ` /
/ /___/ / /___ _/ // ____/_____/ / / /_/ / /_/ /
/_____/_/| _\_ ___/ /___/_/ /_/ \_ _,_/\_ _, /
/____/
EOF
}
clear
header_info
APP = "LXC IP-Tag"
hostname = $( hostname)
# Farbvariablen
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 } "
# This function enables error handling in the script by setting options and defining a trap for the ERR signal.
catch_errors( ) {
set -Eeuo pipefail
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
}
# This function is called when an error occurs. It receives the exit code, line number, and command that caused the error, and displays an error message.
error_handler( ) {
2024-12-17 14:30:51 +00:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
2024-12-16 12:47:16 +00:00
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 "
}
2024-12-17 14:30:51 +00:00
# This function displays a spinner.
2024-12-16 12:47:16 +00:00
spinner( ) {
2024-12-17 14:30:51 +00:00
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
2024-12-16 12:47:16 +00:00
}
# This function displays an informational message with a yellow color.
msg_info( ) {
2024-12-17 14:30:51 +00:00
local msg = " $1 "
echo -ne " ${ TAB } ${ YW } ${ HOLD } ${ msg } ${ HOLD } "
spinner &
SPINNER_PID = $!
2024-12-16 12:47:16 +00:00
}
# This function displays a success message with a green color.
msg_ok( ) {
2024-12-17 14:30:51 +00:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
2024-12-16 12:47:16 +00:00
printf "\e[?25h"
local msg = " $1 "
2024-12-17 14:30:51 +00:00
echo -e " ${ BFR } ${ CM } ${ GN } ${ msg } ${ CL } "
2024-12-16 12:47:16 +00:00
}
# This function displays a error message with a red color.
msg_error( ) {
2024-12-17 14:30:51 +00:00
if [ -n " $SPINNER_PID " ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
2024-12-16 12:47:16 +00:00
printf "\e[?25h"
local msg = " $1 "
2024-12-17 14:30:51 +00:00
echo -e " ${ BFR } ${ CROSS } ${ RD } ${ msg } ${ CL } "
2024-12-16 12:47:16 +00:00
}
while true; do
2024-12-17 14:30:51 +00:00
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
2024-12-16 12:47:16 +00:00
done
if ! pveversion | grep -Eq "pve-manager/8.[0-3]" ; 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/lxc-iptag
2024-12-17 14:30:51 +00:00
msg_ok "Setup IP-Tag Scripts"
2024-12-16 12:47:16 +00:00
msg_info "Setup Default Config"
if [ [ ! -f /opt/lxc-iptag/iptag.conf ] ] ; then
2024-12-17 14:30:51 +00:00
cat <<EOF >/opt/lxc-iptag/iptag.conf
2024-12-16 12:47:16 +00:00
# Configuration file for LXC IP tagging
# List of allowed CIDRs
CIDR_LIST = (
2024-12-17 14:30:51 +00:00
192.168.0.0/16
2024-12-26 09:36:52 +00:00
172.16.0.0/12
2024-12-17 14:30:51 +00:00
10.0.0.0/8
2024-12-26 09:36:52 +00:00
100.64.0.0/10
2024-12-16 12:47:16 +00:00
)
# Interval settings (in seconds)
LOOP_INTERVAL = 60
FW_NET_INTERFACE_CHECK_INTERVAL = 60
LXC_STATUS_CHECK_INTERVAL = -1
FORCE_UPDATE_INTERVAL = 1800
EOF
2024-12-17 14:30:51 +00:00
msg_ok "Setup default config"
2024-12-16 12:47:16 +00:00
else
2024-12-17 14:30:51 +00:00
msg_ok "Default config already exists"
2024-12-16 12:47:16 +00:00
fi
msg_info "Setup Main Function"
if [ [ ! -f /opt/lxc-iptag/iptag ] ] ; then
2024-12-17 14:30:51 +00:00
cat <<'EOF' >/opt/lxc-iptag/iptag
2024-12-16 12:47:16 +00:00
#!/bin/bash
# =============== CONFIGURATION =============== #
CONFIG_FILE = "/opt/lxc-iptag/iptag.conf"
# Load the configuration file if it exists
if [ -f " $CONFIG_FILE " ] ; then
2024-12-17 14:30:51 +00:00
# shellcheck source=./lxc-iptag.conf
source " $CONFIG_FILE "
2024-12-16 12:47:16 +00:00
fi
# Convert IP to integer for comparison
ip_to_int( ) {
2024-12-17 14:30:51 +00:00
local ip = " ${ 1 } "
local a b c d
2024-12-16 12:47:16 +00:00
2024-12-17 14:30:51 +00:00
IFS = . read -r a b c d <<< " ${ ip } "
echo " $(( a << 24 | b << 16 | c << 8 | d)) "
2024-12-16 12:47:16 +00:00
}
# Check if IP is in CIDR
ip_in_cidr( ) {
2024-12-17 14:30:51 +00:00
local ip = " ${ 1 } "
local cidr = " ${ 2 } "
2024-12-16 12:47:16 +00:00
2024-12-17 14:30:51 +00:00
ip_int = $( ip_to_int " ${ ip } " )
netmask_int = $( ip_to_int " $( ipcalc -b " ${ cidr } " | grep Broadcast | awk '{print $2}' ) " )
masked_ip_int = $(( " ${ ip_int } " & " ${ netmask_int } " ))
[ [ ${ ip_int } -eq ${ masked_ip_int } ] ] && return 0 || return 1
2024-12-16 12:47:16 +00:00
}
# Check if IP is in any CIDRs
ip_in_cidrs( ) {
2024-12-17 14:30:51 +00:00
local ip = " ${ 1 } "
local cidrs = ( )
2024-12-16 12:47:16 +00:00
2024-12-17 14:30:51 +00:00
mapfile -t cidrs < <( echo " ${ 2 } " | tr ' ' '\n' )
for cidr in " ${ cidrs [@] } " ; do
ip_in_cidr " ${ ip } " " ${ cidr } " && return 0
done
2024-12-16 12:47:16 +00:00
2024-12-17 14:30:51 +00:00
return 1
2024-12-16 12:47:16 +00:00
}
# Check if IP is valid
is_valid_ipv4( ) {
2024-12-17 14:30:51 +00:00
local ip = $1
local regex = " ^([0-9]{1,3}\.){3}[0-9]{1,3} $"
if [ [ $ip = ~ $regex ] ] ; then
IFS = '.' read -r -a parts <<< " $ip "
for part in " ${ parts [@] } " ; do
if ! [ [ $part = ~ ^[ 0-9] +$ ] ] || ( ( part < 0 || part > 255) ) ; then
2024-12-16 12:47:16 +00:00
return 1
2024-12-17 14:30:51 +00:00
fi
done
return 0
else
return 1
fi
2024-12-16 12:47:16 +00:00
}
lxc_status_changed( ) {
2024-12-17 14:30:51 +00:00
current_lxc_status = $( pct list 2>/dev/null)
if [ " ${ last_lxc_status } " = = " ${ current_lxc_status } " ] ; then
return 1
else
last_lxc_status = " ${ current_lxc_status } "
return 0
fi
2024-12-16 12:47:16 +00:00
}
fw_net_interface_changed( ) {
2024-12-17 14:30:51 +00:00
current_net_interface = $( ifconfig | grep "^fw" )
if [ " ${ last_net_interface } " = = " ${ current_net_interface } " ] ; then
return 1
else
last_net_interface = " ${ current_net_interface } "
return 0
fi
2024-12-16 12:47:16 +00:00
}
# =============== MAIN =============== #
update_lxc_iptags( ) {
2024-12-17 14:30:51 +00:00
vmid_list = $( pct list 2>/dev/null | grep -v VMID | awk '{print $1}' )
for vmid in ${ vmid_list } ; do
last_tagged_ips = ( )
current_valid_ips = ( )
next_tags = ( )
# Parse current tags
mapfile -t current_tags < <( pct config " ${ vmid } " | grep tags | awk '{print $2}' | sed 's/;/\n/g' )
for current_tag in " ${ current_tags [@] } " ; do
if is_valid_ipv4 " ${ current_tag } " ; then
last_tagged_ips += ( " ${ current_tag } " )
continue
fi
next_tags += ( " ${ current_tag } " )
2024-12-16 12:47:16 +00:00
done
2024-12-17 14:30:51 +00:00
# Get current IPs
current_ips_full = $( lxc-info -n " ${ vmid } " -i | awk '{print $2}' )
for ip in ${ current_ips_full } ; do
if is_valid_ipv4 " ${ ip } " && ip_in_cidrs " ${ ip } " " ${ CIDR_LIST [*] } " ; then
current_valid_ips += ( " ${ ip } " )
next_tags += ( " ${ ip } " )
fi
done
2024-12-16 12:47:16 +00:00
2024-12-17 14:30:51 +00:00
# Skip if no ip change
if [ [ " $( echo " ${ last_tagged_ips [@] } " | tr ' ' '\n' | sort -u) " = = " $( echo " ${ current_valid_ips [@] } " | tr ' ' '\n' | sort -u) " ] ] ; then
echo " Skipping ${ vmid } cause ip no changes "
continue
2024-12-16 12:47:16 +00:00
fi
2024-12-17 14:30:51 +00:00
# Set tags
echo " Setting ${ vmid } tags from ${ current_tags [*] } to ${ next_tags [*] } "
pct set " ${ vmid } " -tags " $( IFS = ';' ; echo " ${ next_tags [*] } " ) "
done
}
check( ) {
current_time = $( date +%s)
time_since_last_lxc_status_check = $(( current_time - last_lxc_status_check_time))
if [ [ " ${ LXC_STATUS_CHECK_INTERVAL } " -gt 0 ] ] \
&& [ [ " ${ time_since_last_lxc_status_check } " -ge " ${ STATUS_CHECK_INTERVAL } " ] ] ; then
echo "Checking lxc status..."
last_lxc_status_check_time = ${ current_time }
if lxc_status_changed; then
update_lxc_iptags
last_update_time = ${ current_time }
return
2024-12-16 12:47:16 +00:00
fi
2024-12-17 14:30:51 +00:00
fi
time_since_last_fw_net_interface_check = $(( current_time - last_fw_net_interface_check_time))
if [ [ " ${ FW_NET_INTERFACE_CHECK_INTERVAL } " -gt 0 ] ] \
&& [ [ " ${ time_since_last_fw_net_interface_check } " -ge " ${ FW_NET_INTERFACE_CHECK_INTERVAL } " ] ] ; then
echo "Checking fw net interface..."
last_fw_net_interface_check_time = ${ current_time }
if fw_net_interface_changed; then
update_lxc_iptags
last_update_time = ${ current_time }
return
fi
fi
time_since_last_update = $(( current_time - last_update_time))
if [ ${ time_since_last_update } -ge ${ FORCE_UPDATE_INTERVAL } ] ; then
echo "Force updating lxc iptags..."
update_lxc_iptags
last_update_time = ${ current_time }
return
fi
2024-12-16 12:47:16 +00:00
}
# main: Set the IP tags for all LXC containers
main( ) {
2024-12-17 14:30:51 +00:00
while true; do
check
sleep " ${ LOOP_INTERVAL } "
done
2024-12-16 12:47:16 +00:00
}
main
EOF
2024-12-17 14:30:51 +00:00
msg_ok "Setup Main Function"
2024-12-16 12:47:16 +00:00
else
2024-12-17 14:30:51 +00:00
msg_ok "Main Function already exists"
2024-12-16 12:47:16 +00:00
fi
2024-12-17 14:30:51 +00:00
chmod +x /opt/lxc-iptag/iptag
2024-12-16 12:47:16 +00:00
msg_info "Creating Service"
if [ [ ! -f /lib/systemd/system/iptag.service ] ] ; then
2024-12-17 14:30:51 +00:00
cat <<EOF >/lib/systemd/system/iptag.service
2024-12-16 12:47:16 +00:00
[ Unit]
Description = LXC IP-Tag service
After = network.target
[ Service]
Type = simple
ExecStart = /opt/lxc-iptag/iptag
Restart = always
[ Install]
WantedBy = multi-user.target
EOF
2024-12-17 14:30:51 +00:00
msg_ok "Created Service"
2024-12-16 12:47:16 +00:00
else
2024-12-17 14:30:51 +00:00
msg_ok "Service already exists."
2024-12-16 12:47:16 +00:00
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"
2024-12-17 14:30:51 +00:00
SPINNER_PID = ""
2024-12-16 12:47:16 +00:00
echo -e " \n ${ APP } installation completed successfully! ${ CL } \n "