update huntarr helper-script

This commit is contained in:
bilulib
2025-05-05 18:39:07 +02:00
parent 5af25f0f32
commit 845d22b71e
8 changed files with 3790 additions and 44 deletions

View File

@ -1,2 +1,40 @@
# proxmox-ve-install-scripts
huntarr: `bash -c "$(curl -fsSL https://git.bila.li/Proxmox/proxmox-ve-install-scripts/raw/branch/dev/lxc/huntarr.sh)"`
# Proxmox VE Install Scripts
A collection of installation scripts for Proxmox VE containers.
## Installation Commands
### Huntarr
Create a new Huntarr LXC container:
```bash
bash -c "$(curl -fsSL https://git.bila.li/Proxmox/proxmox-ve-install-scripts/raw/branch/dev/lxc/huntarr.sh)"
```
Install Huntarr in an existing container:
```bash
bash -c "$(curl -fsSL https://git.bila.li/Proxmox/proxmox-ve-install-scripts/raw/branch/dev/install/huntarr-install.sh)"
```
### Custom Options
You can customize the LXC creation with environment variables:
```bash
var_cpu=4 var_ram=4096 var_disk=16 HUNTARR_VERSION=6.2 bash -c "$(curl -fsSL https://git.bila.li/Proxmox/proxmox-ve-install-scripts/raw/branch/dev/lxc/huntarr.sh)"
```
#### Available Options:
- var_cpu: Number of CPU cores
- var_ram: RAM in MB
- var_disk: Disk size in GB
- var_os: Operating system (debian, ubuntu)
- var_version: OS version (12, 11, 22.04, etc.)
- HUNTARR_VERSION: Version of Huntarr to install

View File

@ -13,6 +13,9 @@ setting_up_container
network_check
update_os
APP="Huntarr"
HUNTARR_PORT="3000"
msg_info "Installing Dependencies"
$STD apt-get install -y curl wget gnupg git openjdk-17-jdk nodejs npm
msg_ok "Installed Dependencies"
@ -56,3 +59,6 @@ msg_info "Cleaning up"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleaned"
echo -e "\n${APP} installation completed successfully!"
echo -e "\nYou can access the ${APP} web interface at http://${HOSTNAME}:${HUNTARR_PORT}"

View File

@ -9,7 +9,7 @@ APP="Huntarr"
var_tags="${var_tags:-arr}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-4}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-12}"
var_unprivileged="${var_unprivileged:-1}"
@ -30,27 +30,18 @@ function update_script() {
fi
msg_info "Updating $APP LXC"
temp_file="$(mktemp)"
rm -rf /opt/huntarr
# If Huntarr publishes releases with version tags:
RELEASE=$(curl -fsSL https://api.github.com/repos/huntarr/huntarr/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
curl -fsSL "https://github.com/huntarr/huntarr/archive/refs/tags/v${RELEASE}.tar.gz" -o "$temp_file"
$STD tar -xvzf "$temp_file"
$STD mv huntarr-${RELEASE} /opt/huntarr
# Build process still needed for source-based installation
cd /opt/huntarr
HUNTARR_VERSION="${HUNTARR_VERSION:-6.2}"
$STD git fetch --all
$STD git checkout "v${HUNTARR_VERSION}"
$STD npm install
$STD npm run build
$STD chown -R huntarr:huntarr /opt/huntarr
$STD chmod 775 /opt/huntarr
$STD systemctl restart huntarr
msg_ok "Updated $APP to version ${RELEASE}"
systemctl restart huntarr
msg_ok "Updated $APP LXC"
msg_info "Cleaning up"
rm -rf "$temp_file"
$STD apt-get -y autoremove
$STD apt-get -y autoclean
msg_ok "Cleaned up"
exit
}
@ -61,27 +52,19 @@ description
msg_info "Installing Dependencies"
$STD apt-get update
$STD apt-get -y install curl wget gnupg git openjdk-17-jdk nodejs npm
$STD apt-get install -y curl wget gnupg git openjdk-17-jdk nodejs npm
msg_ok "Installed Dependencies"
msg_info "Creating Huntarr user"
$STD useradd -r -d /var/lib/huntarr -s /bin/bash huntarr
msg_ok "Created Huntarr user"
msg_info "Setting up directories"
$STD mkdir -p /opt/huntarr
$STD mkdir -p /var/lib/huntarr
$STD chown -R huntarr:huntarr /var/lib/huntarr
msg_ok "Set up directories"
msg_info "Installing $APP"
msg_info "Installing Huntarr"
mkdir -p /var/lib/huntarr/
chmod 775 /var/lib/huntarr/
$STD git clone https://github.com/huntarr/huntarr.git /opt/huntarr
cd /opt/huntarr
$STD git checkout "v${var_huntarr_version}"
HUNTARR_VERSION="${HUNTARR_VERSION:-6.2}"
$STD git checkout "v${HUNTARR_VERSION}"
$STD npm install
$STD npm run build
$STD chown -R huntarr:huntarr /opt/huntarr
msg_ok "Installed $APP"
msg_ok "Installed Huntarr"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/huntarr.service
@ -90,26 +73,24 @@ Description=Huntarr Service
After=network.target
[Service]
UMask=0002
Type=simple
WorkingDirectory=/opt/huntarr
ExecStart=/usr/bin/npm start
Restart=on-failure
User=huntarr
Group=huntarr
Environment=HOME=/var/lib/huntarr
TimeoutStopSec=20
KillMode=process
[Install]
WantedBy=multi-user.target
EOF
$STD systemctl daemon-reload
$STD systemctl enable huntarr
$STD systemctl start huntarr
systemctl enable -q --now huntarr
msg_ok "Created Service"
# Determine the port Huntarr is using (assuming default 3000)
HUNTARR_PORT=3000
motd_ssh
customize
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:${HUNTARR_PORT}${CL}"
echo -e "${INFO}${GN} http://${BL}$(hostname)${GN}:3000${CL}\n"

867
misc/add-iptag.sh Normal file
View File

@ -0,0 +1,867 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: MickLesk (Canbiz) && Desert_Gamer
# License: MIT
# Source: https://github.com/gitsang/iptag
function header_info {
clear
cat <<"EOF"
___ ____ _____
|_ _| _ \ _ |_ _|_ _ __ _
| || |_) (_) | |/ _` |/ _` |
| || __/ _ | | (_| | (_| |
|___|_| (_) |_|\__,_|\__, |
|___/
EOF
}
clear
header_info
APP="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() {
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"
}
# This function displays a spinner.
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
}
# This function displays an informational message with a yellow color.
msg_info() {
local msg="$1"
echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}"
spinner &
SPINNER_PID=$!
}
# This function displays a success message with a green color.
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}"
}
# This function displays a error message with a red color.
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
# Create directory if it doesn't exist
if [[ ! -d "/opt/iptag" ]]; then
mkdir -p /opt/iptag
fi
# Migrate config if needed
migrate_config
# Update main script
cat <<'EOF' >/opt/iptag/iptag
#!/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
# Convert IP to integer for comparison
ip_to_int() {
local ip="$1"
local a b c d
IFS=. read -r a b c d <<< "${ip}"
echo "$((a << 24 | b << 16 | c << 8 | d))"
}
# Check if IP is in CIDR
ip_in_cidr() {
local ip="$1" cidr="$2"
ipcalc -c "$ip" "$cidr" >/dev/null 2>&1 || return 1
local network prefix ip_parts net_parts
network=$(echo "$cidr" | cut -d/ -f1)
prefix=$(echo "$cidr" | cut -d/ -f2)
IFS=. read -r -a ip_parts <<< "$ip"
IFS=. read -r -a net_parts <<< "$network"
case $prefix in
8) [[ "${ip_parts[0]}" == "${net_parts[0]}" ]] ;;
16) [[ "${ip_parts[0]}.${ip_parts[1]}" == "${net_parts[0]}.${net_parts[1]}" ]] ;;
24) [[ "${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}" == "${net_parts[0]}.${net_parts[1]}.${net_parts[2]}" ]] ;;
32) [[ "$ip" == "$network" ]] ;;
*) return 1 ;;
esac
}
# Format IP address according to the configuration
format_ip_tag() {
local ip="$1"
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=' '
for cidr in $cidrs; do ip_in_cidr "$ip" "$cidr" && return 0; done
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
}
lxc_status_changed() {
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
}
vm_status_changed() {
current_vm_status=$(qm list 2>/dev/null)
if [ "${last_vm_status}" == "${current_vm_status}" ]; then
return 1
else
last_vm_status="${current_vm_status}"
return 0
fi
}
fw_net_interface_changed() {
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
}
# Get VM IPs using MAC addresses and ARP table
get_vm_ips() {
local vmid=$1 ips="" macs found_ip=false
qm status "$vmid" 2>/dev/null | grep -q "status: running" || return
macs=$(qm config "$vmid" 2>/dev/null | grep -E 'net[0-9]+' | grep -oE '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
[[ -z "$macs" ]] && return
for mac in $macs; do
local ip
ip=$(arp -an 2>/dev/null | grep -i "$mac" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}')
[[ -n "$ip" ]] && { ips+="$ip "; found_ip=true; }
done
if ! $found_ip; then
local agent_ip
agent_ip=$(qm agent "$vmid" network-get-interfaces 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' || true)
[[ -n "$agent_ip" ]] && ips+="$agent_ip "
fi
echo "${ips% }"
}
# Update tags
update_tags() {
local type="$1" vmid="$2" config_cmd="pct"
[[ "$type" == "vm" ]] && config_cmd="qm"
local current_ips_full
if [[ "$type" == "lxc" ]]; then
current_ips_full=$(lxc-info -n "${vmid}" -i 2>/dev/null | grep -E "^IP:" | awk '{print $2}')
else
current_ips_full=$(get_vm_ips "${vmid}")
fi
[[ -z "$current_ips_full" ]] && return
local current_tags=() next_tags=() current_ip_tags=()
mapfile -t current_tags < <($config_cmd config "${vmid}" 2>/dev/null | grep tags | awk '{print $2}' | sed 's/;/\n/g')
# Separate IP and non-IP tags
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
local formatted_ips=() needs_update=false added_ips=()
for ip in ${current_ips_full}; do
if is_valid_ipv4 "$ip" && ip_in_cidrs "$ip" "${CIDR_LIST[*]}"; then
local formatted_ip=$(format_ip_tag "$ip")
formatted_ips+=("$formatted_ip")
if [[ ! " ${current_ip_tags[*]} " =~ " ${formatted_ip} " ]]; then
needs_update=true
added_ips+=("$formatted_ip")
next_tags+=("$formatted_ip")
fi
fi
done
[[ ${#formatted_ips[@]} -eq 0 ]] && return
# Add existing IP tags that are still valid
for tag in "${current_ip_tags[@]}"; do
if [[ " ${formatted_ips[*]} " =~ " ${tag} " ]]; then
if [[ ! " ${next_tags[*]} " =~ " ${tag} " ]]; then
next_tags+=("$tag")
fi
fi
done
if [[ "$needs_update" == true ]]; then
echo "${type^} ${vmid}: adding IP tags: ${added_ips[*]}"
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null
elif [[ ${#current_ip_tags[@]} -gt 0 ]]; then
echo "${type^} ${vmid}: IP tags already set: ${current_ip_tags[*]}"
else
echo "${type^} ${vmid}: setting initial IP tags: ${formatted_ips[*]}"
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${formatted_ips[*]}")" &>/dev/null
fi
}
# Check if status changed
check_status() {
local type="$1" current
case "$type" in
"lxc") current=$(pct list 2>/dev/null | grep -v VMID) ;;
"vm") current=$(qm list 2>/dev/null | grep -v VMID) ;;
"fw") current=$(ifconfig 2>/dev/null | grep "^fw") ;;
esac
local last_var="last_${type}_status"
[[ "${!last_var}" == "$current" ]] && return 1
eval "$last_var='$current'"
return 0
}
# Update all instances
update_all() {
local type="$1" list_cmd="pct" vmids count=0
[[ "$type" == "vm" ]] && list_cmd="qm"
vmids=$($list_cmd list 2>/dev/null | grep -v VMID | awk '{print $1}')
for vmid in $vmids; do ((count++)); done
echo "Found ${count} running ${type}s"
[[ $count -eq 0 ]] && return
for vmid in $vmids; do
update_tags "$type" "$vmid"
done
}
# Main check function
check() {
local current_time changes_detected=false
current_time=$(date +%s)
for type in "lxc" "vm"; do
local interval_var="${type^^}_STATUS_CHECK_INTERVAL"
local last_check_var="last_${type}_check_time"
local last_update_var="last_update_${type}_time"
if [[ "${!interval_var}" -gt 0 ]] && (( current_time - ${!last_check_var} >= ${!interval_var} )); then
echo "Checking ${type^^} status..."
eval "${last_check_var}=\$current_time"
if check_status "$type"; then
changes_detected=true
update_all "$type"
eval "${last_update_var}=\$current_time"
fi
fi
if (( current_time - ${!last_update_var} >= FORCE_UPDATE_INTERVAL )); then
echo "Force updating ${type} tags..."
changes_detected=true
update_all "$type"
eval "${last_update_var}=\$current_time"
fi
done
if [[ "${FW_NET_INTERFACE_CHECK_INTERVAL}" -gt 0 ]] && \
(( current_time - last_fw_check_time >= FW_NET_INTERFACE_CHECK_INTERVAL )); then
echo "Checking network interfaces..."
last_fw_check_time=$current_time
if check_status "fw"; then
changes_detected=true
update_all "lxc"
update_all "vm"
last_update_lxc_time=$current_time
last_update_vm_time=$current_time
fi
fi
$changes_detected || echo "No changes detected in system status"
}
# Initialize time variables
declare -g last_lxc_status="" last_vm_status="" last_fw_status=""
declare -g last_lxc_check_time=0 last_vm_check_time=0 last_fw_check_time=0
declare -g last_update_lxc_time=0 last_update_vm_time=0
# Main loop
main() {
while true; do
check
sleep "${LOOP_INTERVAL:-$DEFAULT_CHECK_INTERVAL}"
done
}
main
EOF
chmod +x /opt/iptag/iptag
# Update service file
cat <<EOF >/lib/systemd/system/iptag.service
[Unit]
Description=IP-Tag service
After=network.target
[Service]
Type=simple
ExecStart=/opt/iptag/iptag
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload &>/dev/null
systemctl enable -q --now iptag.service &>/dev/null
msg_ok "Updated IP-Tag Scripts"
}
# 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
cat <<EOF >/opt/iptag/iptag.conf
# Configuration file for LXC IP tagging
# List of allowed CIDRs
CIDR_LIST=(
192.168.0.0/16
172.16.0.0/12
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="full"
# Interval settings (in seconds)
LOOP_INTERVAL=60
VM_STATUS_CHECK_INTERVAL=60
FW_NET_INTERFACE_CHECK_INTERVAL=60
LXC_STATUS_CHECK_INTERVAL=60
FORCE_UPDATE_INTERVAL=1800
EOF
msg_ok "Setup default config"
else
msg_ok "Default config already exists"
fi
msg_info "Setup Main Function"
if [[ ! -f /opt/iptag/iptag ]]; then
cat <<'EOF' >/opt/iptag/iptag
#!/bin/bash
# =============== CONFIGURATION =============== #
CONFIG_FILE="/opt/iptag/iptag.conf"
# Load the configuration file if it exists
if [ -f "$CONFIG_FILE" ]; then
# shellcheck source=./iptag.conf
source "$CONFIG_FILE"
fi
# Convert IP to integer for comparison
ip_to_int() {
local ip="$1"
local a b c d
IFS=. read -r a b c d <<< "${ip}"
echo "$((a << 24 | b << 16 | c << 8 | d))"
}
# Check if IP is in CIDR
ip_in_cidr() {
local ip="$1"
local cidr="$2"
# Use ipcalc with the -c option (check), which returns 0 if the IP is in the network
if ipcalc -c "$ip" "$cidr" >/dev/null 2>&1; then
# Get network address and mask from CIDR
local network prefix
network=$(echo "$cidr" | cut -d/ -f1)
prefix=$(echo "$cidr" | cut -d/ -f2)
# Check if IP is in the network
local ip_a ip_b ip_c ip_d net_a net_b net_c net_d
IFS=. read -r ip_a ip_b ip_c ip_d <<< "$ip"
IFS=. read -r net_a net_b net_c net_d <<< "$network"
# Check octets match based on prefix length
local result=0
if (( prefix >= 8 )); then
[[ "$ip_a" != "$net_a" ]] && result=1
fi
if (( prefix >= 16 )); then
[[ "$ip_b" != "$net_b" ]] && result=1
fi
if (( prefix >= 24 )); then
[[ "$ip_c" != "$net_c" ]] && result=1
fi
return $result
fi
return 1
}
# Format IP address according to the configuration
format_ip_tag() {
local ip="$1"
local format="${TAG_FORMAT:-full}"
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"
local cidrs="$2"
# Check that cidrs is not empty
[[ -z "$cidrs" ]] && return 1
local IFS=' '
for cidr in $cidrs; do
ip_in_cidr "$ip" "$cidr" && return 0
done
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='.'
read -ra parts <<< "$ip"
for part in "${parts[@]}"; do
[[ "$part" =~ ^[0-9]+$ ]] && ((part >= 0 && part <= 255)) || return 1
done
return 0
}
lxc_status_changed() {
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
}
vm_status_changed() {
current_vm_status=$(qm list 2>/dev/null)
if [ "${last_vm_status}" == "${current_vm_status}" ]; then
return 1
else
last_vm_status="${current_vm_status}"
return 0
fi
}
fw_net_interface_changed() {
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
}
# Get VM IPs using MAC addresses and ARP table
get_vm_ips() {
local vmid=$1
local ips=""
# Check if VM is running
qm status "$vmid" 2>/dev/null | grep -q "status: running" || return
# Get MAC addresses from VM configuration
local macs
macs=$(qm config "$vmid" 2>/dev/null | grep -E 'net[0-9]+' | grep -o -E '[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}')
# Look up IPs from ARP table using MAC addresses
for mac in $macs; do
local ip
ip=$(arp -an 2>/dev/null | grep -i "$mac" | grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}')
if [ -n "$ip" ]; then
ips+="$ip "
fi
done
echo "$ips"
}
# Update tags for container or VM
update_tags() {
local type="$1"
local vmid="$2"
local config_cmd="pct"
[[ "$type" == "vm" ]] && config_cmd="qm"
# Get current IPs
local current_ips_full
if [[ "$type" == "lxc" ]]; then
# Redirect error output to suppress AppArmor warnings
current_ips_full=$(lxc-info -n "${vmid}" -i 2>/dev/null | grep -E "^IP:" | awk '{print $2}')
else
current_ips_full=$(get_vm_ips "${vmid}")
fi
# Parse current tags and get valid IPs
local current_tags=()
local next_tags=()
mapfile -t current_tags < <($config_cmd config "${vmid}" 2>/dev/null | grep tags | awk '{print $2}' | sed 's/;/\n/g')
for tag in "${current_tags[@]}"; do
# Skip tag if it looks like an IP (full or partial)
if ! is_valid_ipv4 "${tag}" && ! [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
next_tags+=("${tag}")
fi
done
# Add valid IPs to tags
local added_ips=()
local skipped_ips=()
for ip in ${current_ips_full}; do
if is_valid_ipv4 "${ip}"; then
if ip_in_cidrs "${ip}" "${CIDR_LIST[*]}"; then
local formatted_ip=$(format_ip_tag "$ip")
next_tags+=("${formatted_ip}")
added_ips+=("${formatted_ip}")
else
skipped_ips+=("${ip}")
fi
fi
done
# Log only if there are changes
if [ ${#added_ips[@]} -gt 0 ]; then
echo "${type^} ${vmid}: added IP tags: ${added_ips[*]}"
fi
# Update if changed
if [[ "$(IFS=';'; echo "${current_tags[*]}")" != "$(IFS=';'; echo "${next_tags[*]}")" ]]; then
$config_cmd set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null
fi
}
# Check if status changed
check_status_changed() {
local type="$1"
local current_status
case "$type" in
"lxc")
current_status=$(pct list 2>/dev/null | grep -v VMID)
[[ "${last_lxc_status}" == "${current_status}" ]] && return 1
last_lxc_status="${current_status}"
;;
"vm")
current_status=$(qm list 2>/dev/null | grep -v VMID)
[[ "${last_vm_status}" == "${current_status}" ]] && return 1
last_vm_status="${current_status}"
;;
"fw")
current_status=$(ifconfig 2>/dev/null | grep "^fw")
[[ "${last_net_interface}" == "${current_status}" ]] && return 1
last_net_interface="${current_status}"
;;
esac
return 0
}
check() {
current_time=$(date +%s)
# Check LXC status
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 "${LXC_STATUS_CHECK_INTERVAL}" ]]; then
echo "Checking LXC status..."
last_lxc_status_check_time=${current_time}
if check_status_changed "lxc"; then
update_all_tags "lxc"
last_update_lxc_time=${current_time}
fi
fi
# Check VM status
time_since_last_vm_status_check=$((current_time - last_vm_status_check_time))
if [[ "${VM_STATUS_CHECK_INTERVAL}" -gt 0 ]] \
&& [[ "${time_since_last_vm_status_check}" -ge "${VM_STATUS_CHECK_INTERVAL}" ]]; then
echo "Checking VM status..."
last_vm_status_check_time=${current_time}
if check_status_changed "vm"; then
update_all_tags "vm"
last_update_vm_time=${current_time}
fi
fi
# Check network interface changes
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 network interfaces..."
last_fw_net_interface_check_time=${current_time}
if check_status_changed "fw"; then
update_all_tags "lxc"
update_all_tags "vm"
last_update_lxc_time=${current_time}
last_update_vm_time=${current_time}
fi
fi
# Force update if needed
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} ]; then
echo "Force updating ${type} tags..."
update_all_tags "$type"
eval "${last_update_var}=${current_time}"
fi
done
}
# Initialize time variables
last_lxc_status_check_time=0
last_vm_status_check_time=0
last_fw_net_interface_check_time=0
last_update_lxc_time=0
last_update_vm_time=0
# main: Set the IP tags for all LXC containers and VMs
main() {
while true; do
check
sleep "${LOOP_INTERVAL}"
done
}
main
EOF
msg_ok "Setup Main Function"
else
msg_ok "Main Function already exists"
fi
chmod +x /opt/iptag/iptag
msg_info "Creating Service"
if [[ ! -f /lib/systemd/system/iptag.service ]]; then
cat <<EOF >/lib/systemd/system/iptag.service
[Unit]
Description=IP-Tag service
After=network.target
[Service]
Type=simple
ExecStart=/opt/iptag/iptag
Restart=always
[Install]
WantedBy=multi-user.target
EOF
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"
SPINNER_PID=""
echo -e "\n${APP} installation completed successfully! ${CL}\n"

123
misc/api.func Normal file
View File

@ -0,0 +1,123 @@
# Copyright (c) 2021-2025 community-scripts ORG
# Author: michelroegl-brunner
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
post_to_api() {
if ! command -v curl &> /dev/null; then
return
fi
if [ "$DIAGNOSTICS" = "no" ]; then
return
fi
if [ -z "$RANDOM_UUID" ]; then
return
fi
local API_URL="http://api.community-scripts.org/upload"
local pve_version="not found"
pve_version=$(pveversion | awk -F'[/ ]' '{print $2}')
JSON_PAYLOAD=$(cat <<EOF
{
"ct_type": $CT_TYPE,
"type":"lxc",
"disk_size": $DISK_SIZE,
"core_count": $CORE_COUNT,
"ram_size": $RAM_SIZE,
"os_type": "$var_os",
"os_version": "$var_version",
"disableip6": "$DISABLEIP6",
"nsapp": "$NSAPP",
"method": "$METHOD",
"pve_version": "$pve_version",
"status": "installing",
"random_id": "$RANDOM_UUID"
}
EOF
)
RESPONSE=$(curl -fsSL -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
}
post_to_api_vm() {
if [[ ! -f /usr/local/community-scripts/diagnostics ]]; then
return
fi
DIAGNOSTICS=$(grep -i "^DIAGNOSTICS=" /usr/local/community-scripts/diagnostics | awk -F'=' '{print $2}')
if ! command -v curl &> /dev/null; then
return
fi
if [ "$DIAGNOSTICS" = "no" ]; then
return
fi
if [ -z "$RANDOM_UUID" ]; then
return
fi
local API_URL="http://api.community-scripts.org/upload"
local pve_version="not found"
pve_version=$(pveversion | awk -F'[/ ]' '{print $2}')
DISK_SIZE_API=${DISK_SIZE%G}
JSON_PAYLOAD=$(cat <<EOF
{
"ct_type": 2,
"type":"vm",
"disk_size": $DISK_SIZE_API,
"core_count": $CORE_COUNT,
"ram_size": $RAM_SIZE,
"os_type": "$var_os",
"os_version": "$var_version",
"disableip6": "",
"nsapp": "$NSAPP",
"method": "$METHOD",
"pve_version": "$pve_version",
"status": "installing",
"random_id": "$RANDOM_UUID"
}
EOF
)
RESPONSE=$(curl -fsSL -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
}
POST_UPDATE_DONE=false
post_update_to_api() {
if ! command -v curl &> /dev/null; then
return
fi
if [ "$POST_UPDATE_DONE" = true ]; then
return 0
fi
local API_URL="http://api.community-scripts.org/upload/updatestatus"
local status="${1:-failed}"
local error="${2:-No error message}"
JSON_PAYLOAD=$(cat <<EOF
{
"status": "$status",
"error": "$error",
"random_id": "$RANDOM_UUID"
}
EOF
)
RESPONSE=$(curl -fsSL -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
POST_UPDATE_DONE=true
}

1471
misc/build.func Normal file

File diff suppressed because it is too large Load Diff

903
misc/tools.func Normal file
View File

@ -0,0 +1,903 @@
#!/bin/bash
install_node_and_modules() {
local NODE_VERSION="${NODE_VERSION:-22}"
local NODE_MODULE="${NODE_MODULE:-}"
local CURRENT_NODE_VERSION=""
local NEED_NODE_INSTALL=false
# Check if Node.js is already installed
if command -v node >/dev/null; then
CURRENT_NODE_VERSION="$(node -v | grep -oP '^v\K[0-9]+')"
if [[ "$CURRENT_NODE_VERSION" != "$NODE_VERSION" ]]; then
msg_info "Node.js version $CURRENT_NODE_VERSION found, replacing with $NODE_VERSION"
NEED_NODE_INSTALL=true
else
msg_ok "Node.js $NODE_VERSION already installed"
fi
else
msg_info "Node.js not found, installing version $NODE_VERSION"
NEED_NODE_INSTALL=true
fi
# Install Node.js if required
if [[ "$NEED_NODE_INSTALL" == true ]]; then
$STD apt-get purge -y nodejs
rm -f /etc/apt/sources.list.d/nodesource.list /etc/apt/keyrings/nodesource.gpg
mkdir -p /etc/apt/keyrings
if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key |
gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; then
msg_error "Failed to download or import NodeSource GPG key"
exit 1
fi
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" \
>/etc/apt/sources.list.d/nodesource.list
if ! apt-get update >/dev/null 2>&1; then
msg_error "Failed to update APT repositories after adding NodeSource"
exit 1
fi
if ! apt-get install -y nodejs >/dev/null 2>&1; then
msg_error "Failed to install Node.js ${NODE_VERSION} from NodeSource"
exit 1
fi
msg_ok "Installed Node.js ${NODE_VERSION}"
fi
export NODE_OPTIONS="--max-old-space-size=4096"
# Install global Node modules
if [[ -n "$NODE_MODULE" ]]; then
IFS=',' read -ra MODULES <<<"$NODE_MODULE"
for mod in "${MODULES[@]}"; do
local MODULE_NAME MODULE_REQ_VERSION MODULE_INSTALLED_VERSION
if [[ "$mod" == *"@"* ]]; then
MODULE_NAME="${mod%@*}"
MODULE_REQ_VERSION="${mod#*@}"
else
MODULE_NAME="$mod"
MODULE_REQ_VERSION="latest"
fi
# Check if the module is already installed
if npm list -g --depth=0 "$MODULE_NAME" >/dev/null 2>&1; then
MODULE_INSTALLED_VERSION="$(npm list -g --depth=0 "$MODULE_NAME" | grep "$MODULE_NAME@" | awk -F@ '{print $2}' | tr -d '[:space:]')"
if [[ "$MODULE_REQ_VERSION" != "latest" && "$MODULE_REQ_VERSION" != "$MODULE_INSTALLED_VERSION" ]]; then
msg_info "Updating $MODULE_NAME from v$MODULE_INSTALLED_VERSION to v$MODULE_REQ_VERSION"
if ! $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}"; then
msg_error "Failed to update $MODULE_NAME to version $MODULE_REQ_VERSION"
exit 1
fi
elif [[ "$MODULE_REQ_VERSION" == "latest" ]]; then
msg_info "Updating $MODULE_NAME to latest version"
if ! $STD npm install -g "${MODULE_NAME}@latest"; then
msg_error "Failed to update $MODULE_NAME to latest version"
exit 1
fi
else
msg_ok "$MODULE_NAME@$MODULE_INSTALLED_VERSION already installed"
fi
else
msg_info "Installing $MODULE_NAME@$MODULE_REQ_VERSION"
if ! $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}"; then
msg_error "Failed to install $MODULE_NAME@$MODULE_REQ_VERSION"
exit 1
fi
fi
done
msg_ok "All requested Node modules have been processed"
fi
}
install_postgresql() {
local PG_VERSION="${PG_VERSION:-16}"
local CURRENT_PG_VERSION=""
local DISTRO
local NEED_PG_INSTALL=false
DISTRO="$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)"
if command -v psql >/dev/null; then
CURRENT_PG_VERSION="$(psql -V | grep -oP '\s\K[0-9]+(?=\.)')"
if [[ "$CURRENT_PG_VERSION" != "$PG_VERSION" ]]; then
msg_info "PostgreSQL Version $CURRENT_PG_VERSION found, replacing with $PG_VERSION"
NEED_PG_INSTALL=true
fi
else
msg_info "PostgreSQL not found, installing version $PG_VERSION"
NEED_PG_INSTALL=true
fi
if [[ "$NEED_PG_INSTALL" == true ]]; then
msg_info "Stopping PostgreSQL if running"
systemctl stop postgresql >/dev/null 2>&1 || true
msg_info "Removing conflicting PostgreSQL packages"
$STD apt-get purge -y "postgresql*"
rm -f /etc/apt/sources.list.d/pgdg.list /etc/apt/trusted.gpg.d/postgresql.gpg
msg_info "Setting up PostgreSQL Repository"
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc |
gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
echo "deb https://apt.postgresql.org/pub/repos/apt ${DISTRO}-pgdg main" \
>/etc/apt/sources.list.d/pgdg.list
$STD apt-get update
$STD apt-get install -y "postgresql-${PG_VERSION}"
msg_ok "Installed PostgreSQL ${PG_VERSION}"
fi
}
install_mariadb() {
local MARIADB_VERSION="${MARIADB_VERSION:-latest}"
local DISTRO_CODENAME
DISTRO_CODENAME="$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)"
# grab dynamic latest LTS version
if [[ "$MARIADB_VERSION" == "latest" ]]; then
msg_info "Resolving latest MariaDB version"
MARIADB_VERSION=$(curl -fsSL https://mariadb.org | grep -oP 'MariaDB \K10\.[0-9]+' | head -n1)
if [[ -z "$MARIADB_VERSION" ]]; then
msg_error "Could not determine latest MariaDB version"
return 1
fi
msg_ok "Latest MariaDB version is $MARIADB_VERSION"
fi
local CURRENT_VERSION=""
if command -v mariadb >/dev/null; then
CURRENT_VERSION="$(mariadb --version | grep -oP 'Ver\s+\K[0-9]+\.[0-9]+')"
fi
if [[ "$CURRENT_VERSION" == "$MARIADB_VERSION" ]]; then
msg_info "MariaDB $MARIADB_VERSION already installed, checking for upgrade"
$STD apt-get update
$STD apt-get install --only-upgrade -y mariadb-server mariadb-client
msg_ok "MariaDB $MARIADB_VERSION upgraded if applicable"
return 0
fi
if [[ -n "$CURRENT_VERSION" ]]; then
msg_info "Replacing MariaDB $CURRENT_VERSION with $MARIADB_VERSION (data will be preserved)"
$STD systemctl stop mariadb >/dev/null 2>&1 || true
$STD apt-get purge -y 'mariadb*' || true
rm -f /etc/apt/sources.list.d/mariadb.list /etc/apt/trusted.gpg.d/mariadb.gpg
else
msg_info "Installing MariaDB $MARIADB_VERSION"
fi
msg_info "Setting up MariaDB Repository"
curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" |
gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mariadb.gpg] http://mirror.mariadb.org/repo/${MARIADB_VERSION}/debian ${DISTRO_CODENAME} main" \
>/etc/apt/sources.list.d/mariadb.list
$STD apt-get update
$STD apt-get install -y mariadb-server mariadb-client
msg_ok "Installed MariaDB $MARIADB_VERSION"
}
install_mysql() {
local MYSQL_VERSION="${MYSQL_VERSION:-8.0}"
local CURRENT_VERSION=""
local NEED_INSTALL=false
if command -v mysql >/dev/null; then
CURRENT_VERSION="$(mysql --version | grep -oP 'Distrib\s+\K[0-9]+\.[0-9]+')"
if [[ "$CURRENT_VERSION" != "$MYSQL_VERSION" ]]; then
msg_info "MySQL $CURRENT_VERSION found, replacing with $MYSQL_VERSION"
NEED_INSTALL=true
else
msg_ok "MySQL $MYSQL_VERSION already installed"
fi
else
msg_info "MySQL not found, installing version $MYSQL_VERSION"
NEED_INSTALL=true
fi
if [[ "$NEED_INSTALL" == true ]]; then
msg_info "Removing conflicting MySQL packages"
$STD systemctl stop mysql >/dev/null 2>&1 || true
$STD apt-get purge -y 'mysql*'
rm -f /etc/apt/sources.list.d/mysql.list /etc/apt/trusted.gpg.d/mysql.gpg
msg_info "Setting up MySQL APT Repository"
DISTRO_CODENAME="$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)"
curl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 | gpg --dearmor -o /etc/apt/trusted.gpg.d/mysql.gpg
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mysql.gpg] https://repo.mysql.com/apt/debian/ ${DISTRO_CODENAME} mysql-${MYSQL_VERSION}" \
>/etc/apt/sources.list.d/mysql.list
$STD apt-get update
$STD apt-get install -y mysql-server
msg_ok "Installed MySQL $MYSQL_VERSION"
fi
}
install_php() {
local PHP_VERSION="${PHP_VERSION:-8.4}"
local PHP_MODULE="${PHP_MODULE:-}"
local PHP_APACHE="${PHP_APACHE:-NO}"
local PHP_FPM="${PHP_FPM:-NO}"
local DEFAULT_MODULES="bcmath,cli,curl,gd,intl,mbstring,opcache,readline,xml,zip"
local COMBINED_MODULES
local PHP_MEMORY_LIMIT="${PHP_MEMORY_LIMIT:-512M}"
local PHP_UPLOAD_MAX_FILESIZE="${PHP_UPLOAD_MAX_FILESIZE:-128M}"
local PHP_POST_MAX_SIZE="${PHP_POST_MAX_SIZE:-128M}"
local PHP_MAX_EXECUTION_TIME="${PHP_MAX_EXECUTION_TIME:-300}"
# Merge default + user-defined modules
if [[ -n "$PHP_MODULE" ]]; then
COMBINED_MODULES="${DEFAULT_MODULES},${PHP_MODULE}"
else
COMBINED_MODULES="${DEFAULT_MODULES}"
fi
# Deduplicate modules
COMBINED_MODULES=$(echo "$COMBINED_MODULES" | tr ',' '\n' | awk '!seen[$0]++' | paste -sd, -)
local CURRENT_PHP
CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)
if [[ "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
$STD echo "PHP $CURRENT_PHP detected, migrating to PHP $PHP_VERSION"
if [[ ! -f /etc/apt/sources.list.d/php.list ]]; then
$STD curl -fsSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
$STD dpkg -i /tmp/debsuryorg-archive-keyring.deb
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \
>/etc/apt/sources.list.d/php.list
$STD apt-get update
fi
$STD apt-get purge -y "php${CURRENT_PHP//./}"* || true
fi
local MODULE_LIST="php${PHP_VERSION}"
IFS=',' read -ra MODULES <<<"$COMBINED_MODULES"
for mod in "${MODULES[@]}"; do
MODULE_LIST+=" php${PHP_VERSION}-${mod}"
done
if [[ "$PHP_APACHE" == "YES" ]]; then
# Optionally disable old Apache PHP module
if [[ -f /etc/apache2/mods-enabled/php${CURRENT_PHP}.load ]]; then
$STD a2dismod php${CURRENT_PHP} || true
fi
fi
if [[ "$PHP_FPM" == "YES" ]]; then
$STD systemctl stop php${CURRENT_PHP}-fpm || true
$STD systemctl disable php${CURRENT_PHP}-fpm || true
fi
$STD apt-get install -y $MODULE_LIST
msg_ok "Installed PHP $PHP_VERSION with selected modules"
if [[ "$PHP_APACHE" == "YES" ]]; then
$STD systemctl restart apache2 || true
fi
if [[ "$PHP_FPM" == "YES" ]]; then
$STD systemctl enable php${PHP_VERSION}-fpm
$STD systemctl restart php${PHP_VERSION}-fpm
fi
# Patch all relevant php.ini files
local PHP_INI_PATHS=()
PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/cli/php.ini")
[[ "$PHP_FPM" == "YES" ]] && PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/fpm/php.ini")
[[ "$PHP_APACHE" == "YES" ]] && PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/apache2/php.ini")
for ini in "${PHP_INI_PATHS[@]}"; do
if [[ -f "$ini" ]]; then
msg_info "Patching $ini"
sed -i "s|^memory_limit = .*|memory_limit = ${PHP_MEMORY_LIMIT}|" "$ini"
sed -i "s|^upload_max_filesize = .*|upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE}|" "$ini"
sed -i "s|^post_max_size = .*|post_max_size = ${PHP_POST_MAX_SIZE}|" "$ini"
sed -i "s|^max_execution_time = .*|max_execution_time = ${PHP_MAX_EXECUTION_TIME}|" "$ini"
msg_ok "Patched $ini"
fi
done
}
install_composer() {
local COMPOSER_BIN="/usr/local/bin/composer"
export COMPOSER_ALLOW_SUPERUSER=1
# Check if composer is already installed
if [[ -x "$COMPOSER_BIN" ]]; then
local CURRENT_VERSION
CURRENT_VERSION=$("$COMPOSER_BIN" --version | awk '{print $3}')
msg_info "Composer $CURRENT_VERSION found, updating to latest"
else
msg_info "Composer not found, installing latest version"
fi
# Download and install latest composer
curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
msg_error "Failed to install Composer"
return 1
fi
chmod +x "$COMPOSER_BIN"
msg_ok "Installed Composer $($COMPOSER_BIN --version | awk '{print $3}')"
}
install_go() {
local ARCH
case "$(uname -m)" in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
*)
msg_error "Unsupported architecture: $(uname -m)"
return 1
;;
esac
# Determine version
if [[ -z "${GO_VERSION:-}" || "${GO_VERSION}" == "latest" ]]; then
GO_VERSION=$(curl -fsSL https://go.dev/VERSION?m=text | head -n1 | sed 's/^go//')
if [[ -z "$GO_VERSION" ]]; then
msg_error "Could not determine latest Go version"
return 1
fi
msg_info "Detected latest Go version: $GO_VERSION"
fi
local GO_BIN="/usr/local/bin/go"
local GO_INSTALL_DIR="/usr/local/go"
if [[ -x "$GO_BIN" ]]; then
local CURRENT_VERSION
CURRENT_VERSION=$("$GO_BIN" version | awk '{print $3}' | sed 's/go//')
if [[ "$CURRENT_VERSION" == "$GO_VERSION" ]]; then
msg_ok "Go $GO_VERSION already installed"
return 0
else
msg_info "Go $CURRENT_VERSION found, upgrading to $GO_VERSION"
rm -rf "$GO_INSTALL_DIR"
fi
else
msg_info "Installing Go $GO_VERSION"
fi
local TARBALL="go${GO_VERSION}.linux-${ARCH}.tar.gz"
local URL="https://go.dev/dl/${TARBALL}"
local TMP_TAR=$(mktemp)
curl -fsSL "$URL" -o "$TMP_TAR" || {
msg_error "Failed to download $TARBALL"
return 1
}
tar -C /usr/local -xzf "$TMP_TAR"
ln -sf /usr/local/go/bin/go /usr/local/bin/go
ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
rm -f "$TMP_TAR"
msg_ok "Installed Go $GO_VERSION"
}
install_java() {
local JAVA_VERSION="${JAVA_VERSION:-21}"
local DISTRO_CODENAME
DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)
local DESIRED_PACKAGE="temurin-${JAVA_VERSION}-jdk"
# Add Adoptium repo if missing
if [[ ! -f /etc/apt/sources.list.d/adoptium.list ]]; then
msg_info "Setting up Adoptium Repository"
mkdir -p /etc/apt/keyrings
curl -fsSL "https://packages.adoptium.net/artifactory/api/gpg/key/public" | gpg --dearmor -o /etc/apt/trusted.gpg.d/adoptium.gpg
echo "deb [signed-by=/etc/apt/trusted.gpg.d/adoptium.gpg] https://packages.adoptium.net/artifactory/deb ${DISTRO_CODENAME} main" \
>/etc/apt/sources.list.d/adoptium.list
$STD apt-get update
msg_ok "Set up Adoptium Repository"
fi
# Detect currently installed temurin version
local INSTALLED_VERSION=""
if dpkg -l | grep -q "temurin-.*-jdk"; then
INSTALLED_VERSION=$(dpkg -l | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+')
fi
if [[ "$INSTALLED_VERSION" == "$JAVA_VERSION" ]]; then
msg_info "Temurin JDK $JAVA_VERSION already installed, updating if needed"
$STD apt-get update
$STD apt-get install --only-upgrade -y "$DESIRED_PACKAGE"
msg_ok "Updated Temurin JDK $JAVA_VERSION (if applicable)"
else
if [[ -n "$INSTALLED_VERSION" ]]; then
msg_info "Removing Temurin JDK $INSTALLED_VERSION"
$STD apt-get purge -y "temurin-${INSTALLED_VERSION}-jdk"
fi
msg_info "Installing Temurin JDK $JAVA_VERSION"
$STD apt-get install -y "$DESIRED_PACKAGE"
msg_ok "Installed Temurin JDK $JAVA_VERSION"
fi
}
install_mongodb() {
local MONGO_VERSION="${MONGO_VERSION:-8.0}"
local DISTRO_CODENAME
DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)
local REPO_LIST="/etc/apt/sources.list.d/mongodb-org-${MONGO_VERSION}.list"
# Aktuell installierte Major-Version ermitteln
local INSTALLED_VERSION=""
if command -v mongod >/dev/null; then
INSTALLED_VERSION=$(mongod --version | awk '/db version/{print $3}' | cut -d. -f1,2)
fi
if [[ "$INSTALLED_VERSION" == "$MONGO_VERSION" ]]; then
msg_info "MongoDB $MONGO_VERSION already installed, checking for upgrade"
$STD apt-get update
$STD apt-get install --only-upgrade -y mongodb-org
msg_ok "MongoDB $MONGO_VERSION upgraded if needed"
return 0
fi
# Ältere Version entfernen (nur Packages, nicht Daten!)
if [[ -n "$INSTALLED_VERSION" ]]; then
msg_info "Replacing MongoDB $INSTALLED_VERSION with $MONGO_VERSION (data will be preserved)"
$STD systemctl stop mongod || true
$STD apt-get purge -y mongodb-org || true
rm -f /etc/apt/sources.list.d/mongodb-org-*.list
rm -f /etc/apt/trusted.gpg.d/mongodb-*.gpg
else
msg_info "Installing MongoDB $MONGO_VERSION"
fi
# MongoDB Repo hinzufügen
curl -fsSL "https://pgp.mongodb.com/server-${MONGO_VERSION}.asc" | gpg --dearmor -o "/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg"
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg] https://repo.mongodb.org/apt/debian ${DISTRO_CODENAME}/mongodb-org/${MONGO_VERSION} main" \
>"$REPO_LIST"
$STD apt-get update
$STD apt-get install -y mongodb-org
# Sicherstellen, dass Datenverzeichnis intakt bleibt
mkdir -p /var/lib/mongodb
chown -R mongodb:mongodb /var/lib/mongodb
$STD systemctl enable mongod
$STD systemctl start mongod
msg_ok "MongoDB $MONGO_VERSION installed and started"
}
fetch_and_deploy_gh_release() {
local repo="$1"
local app=$(echo ${APPLICATION,,} | tr -d ' ')
local api_url="https://api.github.com/repos/$repo/releases/latest"
local header=()
local attempt=0
local max_attempts=3
local api_response tag http_code
local current_version=""
local curl_timeout="--connect-timeout 10 --max-time 30"
# Check if the app directory exists and if there's a version file
if [[ -f "/opt/${app}_version.txt" ]]; then
current_version=$(cat "/opt/${app}_version.txt")
$STD msg_info "Current version: $current_version"
fi
# ensure that jq is installed
if ! command -v jq &>/dev/null; then
$STD msg_info "Installing jq..."
$STD apt-get update -qq &>/dev/null
$STD apt-get install -y jq &>/dev/null || {
msg_error "Failed to install jq"
return 1
}
fi
[[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
until [[ $attempt -ge $max_attempts ]]; do
((attempt++)) || true
$STD msg_info "[$attempt/$max_attempts] Fetching GitHub release for $repo...\n"
api_response=$(curl $curl_timeout -fsSL -w "%{http_code}" -o /tmp/gh_resp.json "${header[@]}" "$api_url")
http_code="${api_response:(-3)}"
if [[ "$http_code" == "404" ]]; then
msg_error "Repository $repo has no Release candidate (404)"
return 1
fi
if [[ "$http_code" != "200" ]]; then
$STD msg_info "Request failed with HTTP $http_code, retrying...\n"
sleep $((attempt * 2))
continue
fi
api_response=$(</tmp/gh_resp.json)
if echo "$api_response" | grep -q "API rate limit exceeded"; then
msg_error "GitHub API rate limit exceeded."
return 1
fi
if echo "$api_response" | jq -e '.message == "Not Found"' &>/dev/null; then
msg_error "Repository not found: $repo"
return 1
fi
tag=$(echo "$api_response" | jq -r '.tag_name // .name // empty')
[[ "$tag" =~ ^v[0-9] ]] && tag="${tag:1}"
if [[ -z "$tag" ]]; then
$STD msg_info "Empty tag received, retrying...\n"
sleep $((attempt * 2))
continue
fi
$STD msg_ok "Found release: $tag for $repo"
break
done
if [[ -z "$tag" ]]; then
msg_error "Failed to fetch release for $repo after $max_attempts attempts."
exit 1
fi
# Version comparison (if we already have this version, skip)
if [[ "$current_version" == "$tag" ]]; then
$STD msg_info "Already running the latest version ($tag). Skipping update."
return 0
fi
local version="$tag"
local base_url="https://github.com/$repo/releases/download/v$tag"
local tmpdir
tmpdir=$(mktemp -d) || return 1
# Extract list of assets from the Release API
local assets urls
assets=$(echo "$api_response" | jq -r '.assets[].browser_download_url') || true
# Detect current architecture
local arch
if command -v dpkg &>/dev/null; then
arch=$(dpkg --print-architecture)
elif command -v uname &>/dev/null; then
case "$(uname -m)" in
x86_64) arch="amd64" ;;
aarch64) arch="arm64" ;;
armv7l) arch="armv7" ;;
armv6l) arch="armv6" ;;
*) arch="unknown" ;;
esac
else
arch="unknown"
fi
$STD msg_info "Detected system architecture: $arch"
# Try to find a matching asset for our architecture
local url=""
for u in $assets; do
if [[ "$u" =~ $arch.*\.tar\.gz$ ]]; then
url="$u"
$STD msg_info "Found matching architecture asset: $url"
break
fi
done
# Fallback to other architectures if our specific one isn't found
if [[ -z "$url" ]]; then
for u in $assets; do
if [[ "$u" =~ (x86_64|amd64|arm64|armv7|armv6).*\.tar\.gz$ ]]; then
url="$u"
$STD msg_info "Architecture-specific asset not found, using: $url"
break
fi
done
fi
# Fallback to any tar.gz
if [[ -z "$url" ]]; then
for u in $assets; do
if [[ "$u" =~ \.tar\.gz$ ]]; then
url="$u"
$STD msg_info "Using generic tarball: $url"
break
fi
done
fi
# Final fallback to GitHub source tarball
if [[ -z "$url" ]]; then
url="https://github.com/$repo/archive/refs/tags/$version.tar.gz"
$STD msg_info "Trying GitHub source tarball fallback: $url"
fi
local filename="${url##*/}"
$STD msg_info "Downloading $url"
if ! curl $curl_timeout -fsSL -o "$tmpdir/$filename" "$url"; then
msg_error "Failed to download release asset from $url"
rm -rf "$tmpdir"
return 1
fi
mkdir -p "/opt/$app"
tar -xzf "$tmpdir/$filename" -C "$tmpdir"
local content_root
content_root=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d)
if [[ $(echo "$content_root" | wc -l) -eq 1 ]]; then
cp -r "$content_root"/* "/opt/$app/"
else
cp -r "$tmpdir"/* "/opt/$app/"
fi
echo "$version" >"/opt/${app}_version.txt"
$STD msg_ok "Deployed $app v$version to /opt/$app"
rm -rf "$tmpdir"
}
setup_local_ip_helper() {
local BASE_DIR="/usr/local/community-scripts/ip-management"
local SCRIPT_PATH="$BASE_DIR/update_local_ip.sh"
local IP_FILE="/run/local-ip.env"
local DISPATCHER_SCRIPT="/etc/networkd-dispatcher/routable.d/10-update-local-ip.sh"
mkdir -p "$BASE_DIR"
# Install networkd-dispatcher if not present
if ! dpkg -s networkd-dispatcher >/dev/null 2>&1; then
apt-get update -qq
apt-get install -yq networkd-dispatcher
fi
# Write update_local_ip.sh
cat <<'EOF' >"$SCRIPT_PATH"
#!/bin/bash
set -euo pipefail
IP_FILE="/run/local-ip.env"
mkdir -p "$(dirname "$IP_FILE")"
get_current_ip() {
local targets=("8.8.8.8" "1.1.1.1" "192.168.1.1" "10.0.0.1" "172.16.0.1" "default")
local ip
for target in "${targets[@]}"; do
if [[ "$target" == "default" ]]; then
ip=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
else
ip=$(ip route get "$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
fi
if [[ -n "$ip" ]]; then
echo "$ip"
return 0
fi
done
return 1
}
current_ip="$(get_current_ip)"
if [[ -z "$current_ip" ]]; then
echo "[ERROR] Could not detect local IP" >&2
exit 1
fi
if [[ -f "$IP_FILE" ]]; then
source "$IP_FILE"
[[ "$LOCAL_IP" == "$current_ip" ]] && exit 0
fi
echo "LOCAL_IP=$current_ip" > "$IP_FILE"
echo "[INFO] LOCAL_IP updated to $current_ip"
EOF
chmod +x "$SCRIPT_PATH"
# Install dispatcher hook
mkdir -p "$(dirname "$DISPATCHER_SCRIPT")"
cat <<EOF >"$DISPATCHER_SCRIPT"
#!/bin/bash
$SCRIPT_PATH
EOF
chmod +x "$DISPATCHER_SCRIPT"
systemctl enable --now networkd-dispatcher.service
$STD msg_ok "LOCAL_IP helper installed using networkd-dispatcher"
}
import_local_ip() {
local IP_FILE="/run/local-ip.env"
if [[ -f "$IP_FILE" ]]; then
# shellcheck disable=SC1090
source "$IP_FILE"
fi
if [[ -z "${LOCAL_IP:-}" ]]; then
get_current_ip() {
local targets=("8.8.8.8" "1.1.1.1" "192.168.1.1" "10.0.0.1" "172.16.0.1" "default")
local ip
for target in "${targets[@]}"; do
if [[ "$target" == "default" ]]; then
ip=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
else
ip=$(ip route get "$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
fi
if [[ -n "$ip" ]]; then
echo "$ip"
return 0
fi
done
return 1
}
LOCAL_IP="$(get_current_ip || true)"
if [[ -z "$LOCAL_IP" ]]; then
msg_error "Could not determine LOCAL_IP"
return 1
fi
fi
export LOCAL_IP
}
function download_with_progress() {
local url="$1"
local output="$2"
if [ -n "$SPINNER_PID" ] && ps -p "$SPINNER_PID" >/dev/null; then kill "$SPINNER_PID" >/dev/null; fi
if ! command -v pv &>/dev/null; then
$STD apt-get install -y pv
fi
set -o pipefail
# Content-Length aus HTTP-Header holen
local content_length
content_length=$(curl -fsSLI "$url" | awk '/Content-Length/ {print $2}' | tr -d '\r' || true)
if [[ -z "$content_length" ]]; then
#msg_warn "Content-Length not available, falling back to plain download"
if ! curl -fL# -o "$output" "$url"; then
msg_error "Download failed"
return 1
fi
else
if ! curl -fsSL "$url" | pv -s "$content_length" >"$output"; then
msg_error "Download failed"
return 1
fi
fi
}
function setup_uv() {
$STD msg_info "Checking uv installation..."
UV_BIN="/usr/local/bin/uv"
TMP_DIR=$(mktemp -d)
ARCH=$(uname -m)
if [[ "$ARCH" == "x86_64" ]]; then
UV_TAR="uv-x86_64-unknown-linux-gnu.tar.gz"
elif [[ "$ARCH" == "aarch64" ]]; then
UV_TAR="uv-aarch64-unknown-linux-gnu.tar.gz"
else
msg_error "Unsupported architecture: $ARCH"
rm -rf "$TMP_DIR"
return 1
fi
# get current github version
LATEST_VERSION=$(curl -s https://api.github.com/repos/astral-sh/uv/releases/latest | grep '"tag_name":' | cut -d '"' -f4 | sed 's/^v//')
if [[ -z "$LATEST_VERSION" ]]; then
msg_error "Could not fetch latest uv version from GitHub."
rm -rf "$TMP_DIR"
return 1
fi
# check if uv exists
if [[ -x "$UV_BIN" ]]; then
INSTALLED_VERSION=$($UV_BIN -V | awk '{print $2}')
if [[ "$INSTALLED_VERSION" == "$LATEST_VERSION" ]]; then
$STD msg_ok "uv is already at the latest version ($INSTALLED_VERSION)"
rm -rf "$TMP_DIR"
# set path
if [[ ":$PATH:" != *":/usr/local/bin:"* ]]; then
export PATH="/usr/local/bin:$PATH"
fi
return 0
else
$STD msg_info "Updating uv from $INSTALLED_VERSION to $LATEST_VERSION"
fi
else
$STD msg_info "uv not found. Installing version $LATEST_VERSION"
fi
# install or update uv
curl -fsSL "https://github.com/astral-sh/uv/releases/latest/download/${UV_TAR}" -o "$TMP_DIR/uv.tar.gz"
tar -xzf "$TMP_DIR/uv.tar.gz" -C "$TMP_DIR"
install -m 755 "$TMP_DIR"/*/uv "$UV_BIN"
rm -rf "$TMP_DIR"
# set path
ensure_usr_local_bin_persist
msg_ok "uv installed/updated to $LATEST_VERSION"
}
function ensure_usr_local_bin_persist() {
local PROFILE_FILE="/etc/profile.d/custom_path.sh"
if [[ ! -f "$PROFILE_FILE" ]] && ! command -v pveversion &>/dev/null; then
echo 'export PATH="/usr/local/bin:$PATH"' >"$PROFILE_FILE"
chmod +x "$PROFILE_FILE"
fi
}
function setup_gs() {
msg_info "Setup Ghostscript"
mkdir -p /tmp
TMP_DIR=$(mktemp -d)
CURRENT_VERSION=$(gs --version 2>/dev/null || echo "0")
RELEASE_JSON=$(curl -fsSL https://api.github.com/repos/ArtifexSoftware/ghostpdl-downloads/releases/latest)
LATEST_VERSION=$(echo "$RELEASE_JSON" | grep '"tag_name":' | head -n1 | cut -d '"' -f4 | sed 's/^gs//')
LATEST_VERSION_DOTTED=$(echo "$RELEASE_JSON" | grep '"name":' | head -n1 | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
if [[ -z "$LATEST_VERSION" ]]; then
msg_error "Could not determine latest Ghostscript version from GitHub."
rm -rf "$TMP_DIR"
return
fi
if dpkg --compare-versions "$CURRENT_VERSION" ge "$LATEST_VERSION_DOTTED"; then
msg_ok "Ghostscript is already at version $CURRENT_VERSION"
rm -rf "$TMP_DIR"
return
fi
msg_info "Installing/Updating Ghostscript to $LATEST_VERSION_DOTTED"
curl -fsSL "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs${LATEST_VERSION}/ghostscript-${LATEST_VERSION_DOTTED}.tar.gz" -o "$TMP_DIR/ghostscript.tar.gz"
if ! tar -xzf "$TMP_DIR/ghostscript.tar.gz" -C "$TMP_DIR"; then
msg_error "Failed to extract Ghostscript archive."
rm -rf "$TMP_DIR"
return
fi
cd "$TMP_DIR/ghostscript-${LATEST_VERSION_DOTTED}" || {
msg_error "Failed to enter Ghostscript source directory."
rm -rf "$TMP_DIR"
}
$STD apt-get install -y build-essential libpng-dev zlib1g-dev
./configure >/dev/null && make && sudo make install >/dev/null
local EXIT_CODE=$?
hash -r
if [[ ! -x "$(command -v gs)" ]]; then
if [[ -x /usr/local/bin/gs ]]; then
ln -sf /usr/local/bin/gs /usr/bin/gs
fi
fi
rm -rf "$TMP_DIR"
if [[ $EXIT_CODE -eq 0 ]]; then
msg_ok "Ghostscript installed/updated to version $LATEST_VERSION_DOTTED"
else
msg_error "Ghostscript installation failed"
fi
}

357
tools/pve/add-lxc-iptag.sh Normal file
View File

@ -0,0 +1,357 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: MickLesk (Canbiz)
# License: MIT
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/gitsang/lxc-iptag
function header_info {
clear
cat <<"EOF"
__ _ ________ ________ ______
/ / | |/ / ____/ / _/ __ \ /_ __/___ _____ _
/ / | / / / // /_/ /_____/ / / __ `/ __ `/
/ /___/ / /___ _/ // ____/_____/ / / /_/ / /_/ /
/_____/_/|_\____/ /___/_/ /_/ \__,_/\__, /
/____/
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() {
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"
}
# This function displays a spinner.
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
}
# This function displays an informational message with a yellow color.
msg_info() {
local msg="$1"
echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}"
spinner &
SPINNER_PID=$!
}
# This function displays a success message with a green color.
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}"
}
# This function displays a error message with a red color.
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}"
}
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/lxc-iptag
msg_ok "Setup IP-Tag Scripts"
msg_info "Setup Default Config"
if [[ ! -f /opt/lxc-iptag/iptag.conf ]]; then
cat <<EOF >/opt/lxc-iptag/iptag.conf
# Configuration file for LXC IP tagging
# List of allowed CIDRs
CIDR_LIST=(
192.168.0.0/16
172.16.0.0/12
10.0.0.0/8
100.64.0.0/10
)
# Interval settings (in seconds)
LOOP_INTERVAL=60
FW_NET_INTERFACE_CHECK_INTERVAL=60
LXC_STATUS_CHECK_INTERVAL=-1
FORCE_UPDATE_INTERVAL=1800
EOF
msg_ok "Setup default config"
else
msg_ok "Default config already exists"
fi
msg_info "Setup Main Function"
if [[ ! -f /opt/lxc-iptag/iptag ]]; then
cat <<'EOF' >/opt/lxc-iptag/iptag
#!/bin/bash
# =============== CONFIGURATION =============== #
CONFIG_FILE="/opt/lxc-iptag/iptag.conf"
# Load the configuration file if it exists
if [ -f "$CONFIG_FILE" ]; then
# shellcheck source=./lxc-iptag.conf
source "$CONFIG_FILE"
fi
# Convert IP to integer for comparison
ip_to_int() {
local ip="${1}"
local a b c d
IFS=. read -r a b c d <<< "${ip}"
echo "$((a << 24 | b << 16 | c << 8 | d))"
}
# Check if IP is in CIDR
ip_in_cidr() {
local ip="${1}"
local cidr="${2}"
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
}
# Check if IP is in any CIDRs
ip_in_cidrs() {
local ip="${1}"
local cidrs=()
mapfile -t cidrs < <(echo "${2}" | tr ' ' '\n')
for cidr in "${cidrs[@]}"; do
ip_in_cidr "${ip}" "${cidr}" && return 0
done
return 1
}
# Check if IP is valid
is_valid_ipv4() {
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
return 1
fi
done
return 0
else
return 1
fi
}
lxc_status_changed() {
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
}
fw_net_interface_changed() {
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
}
# =============== MAIN =============== #
update_lxc_iptags() {
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}")
done
# 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
# 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
fi
# 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
fi
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
}
# main: Set the IP tags for all LXC containers
main() {
while true; do
check
sleep "${LOOP_INTERVAL}"
done
}
main
EOF
msg_ok "Setup Main Function"
else
msg_ok "Main Function already exists"
fi
chmod +x /opt/lxc-iptag/iptag
msg_info "Creating Service"
if [[ ! -f /lib/systemd/system/iptag.service ]]; then
cat <<EOF >/lib/systemd/system/iptag.service
[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
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"
SPINNER_PID=""
echo -e "\n${APP} installation completed successfully! ${CL}\n"