|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# Copyright (c) 2021-2024 community-scripts ORG |
| 4 | +# Author: MickLesk (Canbiz) |
| 5 | +# License: MIT |
| 6 | +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE |
| 7 | +# Source: https://github.com/gitsang/lxc-iptag |
| 8 | + |
| 9 | +function header_info { |
| 10 | + clear |
| 11 | + cat <<"EOF" |
| 12 | + __ _ ________ ________ ______ |
| 13 | + / / | |/ / ____/ / _/ __ \ /_ __/___ _____ _ |
| 14 | + / / | / / / // /_/ /_____/ / / __ `/ __ `/ |
| 15 | + / /___/ / /___ _/ // ____/_____/ / / /_/ / /_/ / |
| 16 | +/_____/_/|_\____/ /___/_/ /_/ \__,_/\__, / |
| 17 | + /____/ |
| 18 | +EOF |
| 19 | +} |
| 20 | + |
| 21 | +clear |
| 22 | +header_info |
| 23 | +APP="LXC IP-Tag" |
| 24 | +hostname=$(hostname) |
| 25 | + |
| 26 | +# Farbvariablen |
| 27 | +YW=$(echo "\033[33m") |
| 28 | +GN=$(echo "\033[1;92m") |
| 29 | +RD=$(echo "\033[01;31m") |
| 30 | +CL=$(echo "\033[m") |
| 31 | +BFR="\\r\\033[K" |
| 32 | +HOLD=" " |
| 33 | +CM=" ✔️ ${CL}" |
| 34 | +CROSS=" ✖️ ${CL}" |
| 35 | + |
| 36 | +# This function enables error handling in the script by setting options and defining a trap for the ERR signal. |
| 37 | +catch_errors() { |
| 38 | + set -Eeuo pipefail |
| 39 | + trap 'error_handler $LINENO "$BASH_COMMAND"' ERR |
| 40 | +} |
| 41 | + |
| 42 | +# 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. |
| 43 | +error_handler() { |
| 44 | + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then kill $SPINNER_PID > /dev/null; fi |
| 45 | + printf "\e[?25h" |
| 46 | + local exit_code="$?" |
| 47 | + local line_number="$1" |
| 48 | + local command="$2" |
| 49 | + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" |
| 50 | + echo -e "\n$error_message\n" |
| 51 | +} |
| 52 | + |
| 53 | +spinner() { |
| 54 | + local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') |
| 55 | + local spin_i=0 |
| 56 | + local interval=0.1 |
| 57 | + printf "\e[?25l" |
| 58 | + local orange="\e[38;5;214m" |
| 59 | + |
| 60 | + while true; do |
| 61 | + printf "\r ${orange}%s\e[0m " "${frames[spin_i]}" |
| 62 | + spin_i=$(( (spin_i + 1) % ${#frames[@]} )) |
| 63 | + sleep "$interval" |
| 64 | + done |
| 65 | +} |
| 66 | + |
| 67 | +# This function displays an informational message with a yellow color. |
| 68 | +msg_info() { |
| 69 | + local msg="$1" |
| 70 | + echo -ne " ${HOLD} ${YW}${msg} " |
| 71 | + spinner & |
| 72 | + SPINNER_PID=$! |
| 73 | +} |
| 74 | + |
| 75 | +# This function displays a success message with a green color. |
| 76 | +msg_ok() { |
| 77 | + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then kill $SPINNER_PID > /dev/null; fi |
| 78 | + printf "\e[?25h" |
| 79 | + local msg="$1" |
| 80 | + echo -e "${BFR}${CM} ${GN}${msg}${CL}" |
| 81 | +} |
| 82 | + |
| 83 | +# This function displays a error message with a red color. |
| 84 | +msg_error() { |
| 85 | + if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID > /dev/null; then kill $SPINNER_PID > /dev/null; fi |
| 86 | + printf "\e[?25h" |
| 87 | + local msg="$1" |
| 88 | + echo -e "${BFR}${CROSS} ${RD}${msg}${CL}" |
| 89 | +} |
| 90 | + |
| 91 | +while true; do |
| 92 | + read -p "This will install ${APP} on ${hostname}. Proceed? (y/n): " yn |
| 93 | + case $yn in |
| 94 | + [Yy]*) break ;; |
| 95 | + [Nn]*) msg_info "Installation cancelled."; exit ;; |
| 96 | + *) msg_info "Please answer yes or no." ;; |
| 97 | + esac |
| 98 | +done |
| 99 | + |
| 100 | +if ! pveversion | grep -Eq "pve-manager/8.[0-3]"; then |
| 101 | + msg_error "This version of Proxmox Virtual Environment is not supported" |
| 102 | + msg_error "⚠️ Requires Proxmox Virtual Environment Version 8.0 or later." |
| 103 | + msg_error "Exiting..." |
| 104 | + sleep 2 |
| 105 | + exit |
| 106 | +fi |
| 107 | + |
| 108 | +FILE_PATH="/usr/local/bin/iptag" |
| 109 | +if [[ -f "$FILE_PATH" ]]; then |
| 110 | + msg_info "The file already exists: '$FILE_PATH'. Skipping installation." |
| 111 | + exit 0 |
| 112 | +fi |
| 113 | + |
| 114 | +msg_info "Installing Dependencies" |
| 115 | +apt-get update &>/dev/null |
| 116 | +apt-get install -y ipcalc net-tools &>/dev/null |
| 117 | +msg_ok "Installed Dependencies" |
| 118 | + |
| 119 | +msg_info "Setting up IP-Tag Scripts" |
| 120 | +mkdir -p /opt/lxc-iptag |
| 121 | + |
| 122 | +msg_info "Setup Default Config" |
| 123 | +if [[ ! -f /opt/lxc-iptag/iptag.conf ]]; then |
| 124 | + cat <<EOF > /opt/lxc-iptag/iptag.conf |
| 125 | +# Configuration file for LXC IP tagging |
| 126 | +
|
| 127 | +# List of allowed CIDRs |
| 128 | +CIDR_LIST=( |
| 129 | + 192.168.0.0/16 |
| 130 | + 100.64.0.0/10 |
| 131 | + 10.0.0.0/8 |
| 132 | +) |
| 133 | +
|
| 134 | +# Interval settings (in seconds) |
| 135 | +LOOP_INTERVAL=60 |
| 136 | +FW_NET_INTERFACE_CHECK_INTERVAL=60 |
| 137 | +LXC_STATUS_CHECK_INTERVAL=-1 |
| 138 | +FORCE_UPDATE_INTERVAL=1800 |
| 139 | +EOF |
| 140 | + msg_ok "Setup default config" |
| 141 | +else |
| 142 | + msg_ok "Default config already exists" |
| 143 | +fi |
| 144 | + |
| 145 | +msg_info "Setup Main Function" |
| 146 | +if [[ ! -f /opt/lxc-iptag/iptag ]]; then |
| 147 | + cat <<'EOF' > /opt/lxc-iptag/iptag |
| 148 | +#!/bin/bash |
| 149 | +
|
| 150 | +# =============== CONFIGURATION =============== # |
| 151 | +
|
| 152 | +CONFIG_FILE="/opt/lxc-iptag/iptag.conf" |
| 153 | +
|
| 154 | +# Load the configuration file if it exists |
| 155 | +if [ -f "$CONFIG_FILE" ]; then |
| 156 | + # shellcheck source=./lxc-iptag.conf |
| 157 | + source "$CONFIG_FILE" |
| 158 | +fi |
| 159 | +
|
| 160 | +# Convert IP to integer for comparison |
| 161 | +ip_to_int() { |
| 162 | + local ip="${1}" |
| 163 | + local a b c d |
| 164 | +
|
| 165 | + IFS=. read -r a b c d <<< "${ip}" |
| 166 | + echo "$((a << 24 | b << 16 | c << 8 | d))" |
| 167 | +} |
| 168 | +
|
| 169 | +# Check if IP is in CIDR |
| 170 | +ip_in_cidr() { |
| 171 | + local ip="${1}" |
| 172 | + local cidr="${2}" |
| 173 | +
|
| 174 | + ip_int=$(ip_to_int "${ip}") |
| 175 | + netmask_int=$(ip_to_int "$(ipcalc -b "${cidr}" | grep Broadcast | awk '{print $2}')") |
| 176 | + masked_ip_int=$(( "${ip_int}" & "${netmask_int}" )) |
| 177 | + [[ ${ip_int} -eq ${masked_ip_int} ]] && return 0 || return 1 |
| 178 | +} |
| 179 | +
|
| 180 | +# Check if IP is in any CIDRs |
| 181 | +ip_in_cidrs() { |
| 182 | + local ip="${1}" |
| 183 | + local cidrs=() |
| 184 | +
|
| 185 | + mapfile -t cidrs < <(echo "${2}" | tr ' ' '\n') |
| 186 | + for cidr in "${cidrs[@]}"; do |
| 187 | + ip_in_cidr "${ip}" "${cidr}" && return 0 |
| 188 | + done |
| 189 | +
|
| 190 | + return 1 |
| 191 | +} |
| 192 | +
|
| 193 | +# Check if IP is valid |
| 194 | +is_valid_ipv4() { |
| 195 | + local ip=$1 |
| 196 | + local regex="^([0-9]{1,3}\.){3}[0-9]{1,3}$" |
| 197 | +
|
| 198 | + if [[ $ip =~ $regex ]]; then |
| 199 | + IFS='.' read -r -a parts <<< "$ip" |
| 200 | + for part in "${parts[@]}"; do |
| 201 | + if ! [[ $part =~ ^[0-9]+$ ]] || ((part < 0 || part > 255)); then |
| 202 | + return 1 |
| 203 | + fi |
| 204 | + done |
| 205 | + return 0 |
| 206 | + else |
| 207 | + return 1 |
| 208 | + fi |
| 209 | +} |
| 210 | +
|
| 211 | +lxc_status_changed() { |
| 212 | + current_lxc_status=$(pct list 2>/dev/null) |
| 213 | + if [ "${last_lxc_status}" == "${current_lxc_status}" ]; then |
| 214 | + return 1 |
| 215 | + else |
| 216 | + last_lxc_status="${current_lxc_status}" |
| 217 | + return 0 |
| 218 | + fi |
| 219 | +} |
| 220 | +
|
| 221 | +fw_net_interface_changed() { |
| 222 | + current_net_interface=$(ifconfig | grep "^fw") |
| 223 | + if [ "${last_net_interface}" == "${current_net_interface}" ]; then |
| 224 | + return 1 |
| 225 | + else |
| 226 | + last_net_interface="${current_net_interface}" |
| 227 | + return 0 |
| 228 | + fi |
| 229 | +} |
| 230 | +
|
| 231 | +# =============== MAIN =============== # |
| 232 | +
|
| 233 | +update_lxc_iptags() { |
| 234 | + vmid_list=$(pct list 2>/dev/null | grep -v VMID | awk '{print $1}') |
| 235 | + for vmid in ${vmid_list}; do |
| 236 | + last_tagged_ips=() |
| 237 | + current_valid_ips=() |
| 238 | + next_tags=() |
| 239 | +
|
| 240 | + # Parse current tags |
| 241 | + mapfile -t current_tags < <(pct config "${vmid}" | grep tags | awk '{print $2}' | sed 's/;/\n/g') |
| 242 | + for current_tag in "${current_tags[@]}"; do |
| 243 | + if is_valid_ipv4 "${current_tag}"; then |
| 244 | + last_tagged_ips+=("${current_tag}") |
| 245 | + continue |
| 246 | + fi |
| 247 | + next_tags+=("${current_tag}") |
| 248 | + done |
| 249 | +
|
| 250 | + # Get current IPs |
| 251 | + current_ips_full=$(lxc-info -n "${vmid}" -i | awk '{print $2}') |
| 252 | + for ip in ${current_ips_full}; do |
| 253 | + if is_valid_ipv4 "${ip}" && ip_in_cidrs "${ip}" "${CIDR_LIST[*]}"; then |
| 254 | + current_valid_ips+=("${ip}") |
| 255 | + next_tags+=("${ip}") |
| 256 | + fi |
| 257 | + done |
| 258 | +
|
| 259 | + # Skip if no ip change |
| 260 | + if [[ "$(echo "${last_tagged_ips[@]}" | tr ' ' '\n' | sort -u)" == "$(echo "${current_valid_ips[@]}" | tr ' ' '\n' | sort -u)" ]]; then |
| 261 | + echo "Skipping ${vmid} cause ip no changes" |
| 262 | + continue |
| 263 | + fi |
| 264 | +
|
| 265 | + # Set tags |
| 266 | + echo "Setting ${vmid} tags from ${current_tags[*]} to ${next_tags[*]}" |
| 267 | + pct set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" |
| 268 | + done |
| 269 | +} |
| 270 | +
|
| 271 | +check() { |
| 272 | + current_time=$(date +%s) |
| 273 | +
|
| 274 | + time_since_last_lxc_status_check=$((current_time - last_lxc_status_check_time)) |
| 275 | + if [[ "${LXC_STATUS_CHECK_INTERVAL}" -gt 0 ]] \ |
| 276 | + && [[ "${time_since_last_lxc_status_check}" -ge "${STATUS_CHECK_INTERVAL}" ]]; then |
| 277 | + echo "Checking lxc status..." |
| 278 | + last_lxc_status_check_time=${current_time} |
| 279 | + if lxc_status_changed; then |
| 280 | + update_lxc_iptags |
| 281 | + last_update_time=${current_time} |
| 282 | + return |
| 283 | + fi |
| 284 | + fi |
| 285 | +
|
| 286 | + time_since_last_fw_net_interface_check=$((current_time - last_fw_net_interface_check_time)) |
| 287 | + if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL}" -gt 0 ]] \ |
| 288 | + && [[ "${time_since_last_fw_net_interface_check}" -ge "${FW_NET_INTERFACE_CHECK_INTERVAL}" ]]; then |
| 289 | + echo "Checking fw net interface..." |
| 290 | + last_fw_net_interface_check_time=${current_time} |
| 291 | + if fw_net_interface_changed; then |
| 292 | + update_lxc_iptags |
| 293 | + last_update_time=${current_time} |
| 294 | + return |
| 295 | + fi |
| 296 | + fi |
| 297 | +
|
| 298 | + time_since_last_update=$((current_time - last_update_time)) |
| 299 | + if [ ${time_since_last_update} -ge ${FORCE_UPDATE_INTERVAL} ]; then |
| 300 | + echo "Force updating lxc iptags..." |
| 301 | + update_lxc_iptags |
| 302 | + last_update_time=${current_time} |
| 303 | + return |
| 304 | + fi |
| 305 | +} |
| 306 | +
|
| 307 | +# main: Set the IP tags for all LXC containers |
| 308 | +main() { |
| 309 | + while true; do |
| 310 | + check |
| 311 | + sleep "${LOOP_INTERVAL}" |
| 312 | + done |
| 313 | +} |
| 314 | +
|
| 315 | +main |
| 316 | +EOF |
| 317 | + msg_ok "Setup Main Function" |
| 318 | +else |
| 319 | + msg_ok "Main Function already exists" |
| 320 | +fi |
| 321 | +chmod +x /opt/lxc-iptag/iptag |
| 322 | + |
| 323 | +msg_info "Creating Service" |
| 324 | +if [[ ! -f /lib/systemd/system/iptag.service ]]; then |
| 325 | + echo "Systemd service file not found. Creating it now..." |
| 326 | + cat <<EOF > /lib/systemd/system/iptag.service |
| 327 | +[Unit] |
| 328 | +Description=LXC IP-Tag service |
| 329 | +After=network.target |
| 330 | +
|
| 331 | +[Service] |
| 332 | +Type=simple |
| 333 | +ExecStart=/opt/lxc-iptag/iptag |
| 334 | +Restart=always |
| 335 | +
|
| 336 | +[Install] |
| 337 | +WantedBy=multi-user.target |
| 338 | +EOF |
| 339 | + msg_ok "Created Service" |
| 340 | +else |
| 341 | + msg_ok "Service already exists." |
| 342 | +fi |
| 343 | + |
| 344 | +msg_ok "Setup IP-Tag Scripts" |
| 345 | + |
| 346 | +msg_info "Starting Service" |
| 347 | +systemctl daemon-reload &>/dev/null |
| 348 | +systemctl enable -q --now iptag.service &>/dev/null |
| 349 | +msg_ok "Started Service" |
| 350 | + |
| 351 | +echo -e "\n${APP} installation completed successfully! ${CL}\n" |
0 commit comments