Skip to content

Commit a00f7af

Browse files
MickLeskhavardthom
andauthored
New Script: LXC IP-Tag (#536)
* New Script: LXC IP-Tag * add comma in json * Update misc/add-lxc-iptag.sh Co-authored-by: Håvard Gjøby Thom <[email protected]> * Update json/add-lxc-iptag.json Co-authored-by: Håvard Gjøby Thom <[email protected]> * Update json/add-lxc-iptag.json Co-authored-by: Håvard Gjøby Thom <[email protected]> * remove files * Full-Update to Single-File * Finalo * Update add-lxc-iptag.json --------- Co-authored-by: Håvard Gjøby Thom <[email protected]>
1 parent 284238d commit a00f7af

File tree

2 files changed

+394
-0
lines changed

2 files changed

+394
-0
lines changed

json/add-lxc-iptag.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "Proxmox VE LXC IP-Tag",
3+
"slug": "add-lxc-iptag",
4+
"categories": [
5+
1
6+
],
7+
"date_created": "2024-11-27",
8+
"type": "misc",
9+
"updateable": false,
10+
"privileged": false,
11+
"interface_port": null,
12+
"documentation": null,
13+
"website": null,
14+
"logo": "https://raw.githubusercontent.com/home-assistant/brands/master/core_integrations/proxmoxve/icon.png",
15+
"description": "This script automatically adds IP address as tags to LXC containers using a Systemd service. The service also updates the tags if a LXC IP address is changed.",
16+
"install_methods": [
17+
{
18+
"type": "default",
19+
"script": "misc/add-lxc-iptag.sh",
20+
"resources": {
21+
"cpu": null,
22+
"ram": null,
23+
"hdd": null,
24+
"os": null,
25+
"version": null
26+
}
27+
}
28+
],
29+
"default_credentials": {
30+
"username": null,
31+
"password": null
32+
},
33+
"notes": [
34+
{
35+
"text": "Execute within the Proxmox shell",
36+
"type": "Info"
37+
},
38+
{
39+
"text": "Configuration: `nano /opt/lxc-iptag/iptag.conf`. iptag.service must be restarted after change.",
40+
"type": "Info"
41+
}
42+
]
43+
}

misc/add-lxc-iptag.sh

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
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

Comments
 (0)