Enhance Proxmox dependency-check scripts
Update frontend docs and significantly refactor dependency-check tooling. - frontend: set supported version to "PVE 8.x / 9.x" and add info about --install/--status/--uninstall flags. - Add new tools/pve/dependency-check copy.sh (installer wrapper). - Rework tools/pve/dependency-check.sh: add CLI (install/status/uninstall), PVE version detection/validation, improved logging/colors, safer config parsing, more robust storage checks, validated tag handling (dep_ping/dep_tcp), portable TCP/ping checks, and wait/timeout helper. - Improve applicator script handling (ignore list, avoid overwriting other hookscripts), update systemd units (PathExistsGlob, unit binding), and implement uninstall to remove assignments and installed files. These changes harden lifecycle management and make installation/cleanup and runtime checks more robust and observable.
This commit is contained in:
parent
3eaa0ecf10
commit
6c43b624c1
@ -23,7 +23,7 @@
|
|||||||
"ram": null,
|
"ram": null,
|
||||||
"hdd": null,
|
"hdd": null,
|
||||||
"os": null,
|
"os": null,
|
||||||
"version": null
|
"version": "PVE 8.x / 9.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -36,6 +36,10 @@
|
|||||||
"text": "Execute within the Proxmox shell",
|
"text": "Execute within the Proxmox shell",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"text": "The script supports --install (default), --status and --uninstall for clean lifecycle management.",
|
||||||
|
"type": "info"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"text": "To wait until a certain host is available, tag the VM or container with `dep_ping_<hostname>` where `<hostname>` is the name or IP of the host to ping. The script will wait until the host is reachable before proceeding with the startup.",
|
"text": "To wait until a certain host is available, tag the VM or container with `dep_ping_<hostname>` where `<hostname>` is the name or IP of the host to ping. The script will wait until the host is reachable before proceeding with the startup.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
|
|||||||
361
tools/pve/dependency-check copy.sh
Normal file
361
tools/pve/dependency-check copy.sh
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2023 community-scripts ORG
|
||||||
|
# This script is designed to install the Proxmox Dependency Check Hookscript.
|
||||||
|
# It sets up a dependency-checking hookscript and automates its
|
||||||
|
# application to all new and existing guests using a systemd watcher.
|
||||||
|
# License: MIT
|
||||||
|
|
||||||
|
function header_info {
|
||||||
|
clear
|
||||||
|
cat <<"EOF"
|
||||||
|
____ _ ____ _ _
|
||||||
|
| _ \ ___ _ __ ___ _ __ __| | ___ _ __ ___ _ _ / ___| |__ ___ ___| | __
|
||||||
|
| | | |/ _ \ '_ \ / _ \ '_ \ / _` |/ _ \ '_ \ / __| | | | | | '_ \ / _ \/ __| |/ /
|
||||||
|
| |_| | __/ |_) | __/ | | | (_| | __/ | | | (__| |_| | |___| | | | __/ (__| <
|
||||||
|
|____/ \___| .__/ \___|_| |_|\__,_|\___|_| |_|\___|\__, |\____|_| |_|\___|\___|_|\_\
|
||||||
|
|_| |___/
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Color variables
|
||||||
|
YW=$(echo "\033[33m")
|
||||||
|
GN=$(echo "\033[1;92m")
|
||||||
|
RD=$(echo "\033[01;31m")
|
||||||
|
CL=$(echo "\033[m")
|
||||||
|
BFR="\\r\\033[K"
|
||||||
|
HOLD=" "
|
||||||
|
CM="${GN}✓${CL}"
|
||||||
|
CROSS="${RD}✗${CL}"
|
||||||
|
|
||||||
|
# Spinner for progress indication (simplified)
|
||||||
|
spinner() {
|
||||||
|
local pid=$!
|
||||||
|
local delay=0.1
|
||||||
|
local spinstr='|/-\'
|
||||||
|
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
|
||||||
|
local temp=${spinstr#?}
|
||||||
|
printf " [%c] " "$spinstr"
|
||||||
|
local spinstr=$temp${spinstr%"$temp"}
|
||||||
|
sleep $delay
|
||||||
|
printf "\b\b\b\b\b\b"
|
||||||
|
done
|
||||||
|
printf " \b\b\b\b"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Message functions
|
||||||
|
msg_info() {
|
||||||
|
echo -ne " ${YW}›${CL} $1..."
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_ok() {
|
||||||
|
echo -e "${BFR} ${CM} $1${CL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_error() {
|
||||||
|
echo -e "${BFR} ${CROSS} $1${CL}"
|
||||||
|
}
|
||||||
|
# --- End of base script functions ---
|
||||||
|
|
||||||
|
# --- Installation Functions ---
|
||||||
|
|
||||||
|
# Function to create the actual hookscript that runs before guest startup
|
||||||
|
create_dependency_hookscript() {
|
||||||
|
msg_info "Creating dependency-check hookscript"
|
||||||
|
mkdir -p /var/lib/vz/snippets
|
||||||
|
cat <<'EOF' >/var/lib/vz/snippets/dependency-check.sh
|
||||||
|
#!/bin/bash
|
||||||
|
# Proxmox Hookscript for Pre-Start Dependency Checking
|
||||||
|
# Works for both QEMU VMs and LXC Containers
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
POLL_INTERVAL=5 # Seconds to wait between checks
|
||||||
|
MAX_ATTEMPTS=60 # Max number of attempts before failing (60 * 5s = 5 minutes)
|
||||||
|
# --- End Configuration ---
|
||||||
|
|
||||||
|
VMID=$1
|
||||||
|
PHASE=$2
|
||||||
|
|
||||||
|
# Function for logging to syslog with a consistent format
|
||||||
|
log() {
|
||||||
|
echo "[hookscript-dep-check] VMID $VMID: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This script only runs in the 'pre-start' phase
|
||||||
|
if [ "$PHASE" != "pre-start" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "--- Starting Pre-Start Dependency Check ---"
|
||||||
|
|
||||||
|
# --- Determine Guest Type (QEMU or LXC) ---
|
||||||
|
GUEST_TYPE=""
|
||||||
|
CONFIG_CMD=""
|
||||||
|
if qm config "$VMID" >/dev/null 2>&1; then
|
||||||
|
GUEST_TYPE="qemu"
|
||||||
|
CONFIG_CMD="qm config"
|
||||||
|
log "Guest type is QEMU (VM)."
|
||||||
|
elif pct config "$VMID" >/dev/null 2>&1; then
|
||||||
|
GUEST_TYPE="lxc"
|
||||||
|
CONFIG_CMD="pct config"
|
||||||
|
log "Guest type is LXC (Container)."
|
||||||
|
else
|
||||||
|
log "ERROR: Could not determine guest type for $VMID. Aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GUEST_CONFIG=$($CONFIG_CMD "$VMID")
|
||||||
|
|
||||||
|
# --- 1. Storage Availability Check ---
|
||||||
|
log "Checking storage availability..."
|
||||||
|
# Grep for all disk definitions (scsi, sata, virtio, ide, rootfs, mp)
|
||||||
|
# and extract the storage identifier (the field between the colons).
|
||||||
|
# Sort -u gets the unique list of storage pools.
|
||||||
|
STORAGE_IDS=$(echo "$GUEST_CONFIG" | grep -E '^(scsi|sata|virtio|ide|rootfs|mp)[0-9]*:' | awk -F'[:]' '{print $2}' | awk '{print$1}' | sort -u)
|
||||||
|
|
||||||
|
if [ -z "$STORAGE_IDS" ]; then
|
||||||
|
log "No storage dependencies found to check."
|
||||||
|
else
|
||||||
|
for STORAGE_ID in $STORAGE_IDS; do
|
||||||
|
log "Checking status of storage: '$STORAGE_ID'"
|
||||||
|
ATTEMPTS=0
|
||||||
|
while true; do
|
||||||
|
# Grep for the storage ID line in pvesm status and check the 'Active' column (3rd column)
|
||||||
|
STATUS=$(pvesm status | grep "^\s*$STORAGE_ID\s" | awk '{print $3}')
|
||||||
|
if [ "$STATUS" == "active" ]; then
|
||||||
|
log "Storage '$STORAGE_ID' is active."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTEMPTS=$((ATTEMPTS + 1))
|
||||||
|
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
||||||
|
log "ERROR: Timeout waiting for storage '$STORAGE_ID' to become active. Aborting start."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Storage '$STORAGE_ID' is not active (current status: '${STATUS:-inactive/unknown}'). Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
log "All storage dependencies are met."
|
||||||
|
|
||||||
|
|
||||||
|
# --- 2. Custom Tag-Based Dependency Check ---
|
||||||
|
log "Checking for custom tag-based dependencies..."
|
||||||
|
TAGS=$(echo "$GUEST_CONFIG" | grep '^tags:' | awk '{print $2}')
|
||||||
|
|
||||||
|
if [ -z "$TAGS" ]; then
|
||||||
|
log "No tags found. Skipping custom dependency check."
|
||||||
|
else
|
||||||
|
# Replace colons with spaces to loop through tags
|
||||||
|
for TAG in ${TAGS//;/ }; do
|
||||||
|
# Check if the tag matches our dependency format 'dep_*'
|
||||||
|
if [[ $TAG == dep_* ]]; then
|
||||||
|
log "Found dependency tag: '$TAG'"
|
||||||
|
|
||||||
|
# Split tag into parts using underscore as delimiter
|
||||||
|
IFS='_' read -ra PARTS <<< "$TAG"
|
||||||
|
DEP_TYPE="${PARTS[1]}"
|
||||||
|
|
||||||
|
ATTEMPTS=0
|
||||||
|
while true; do
|
||||||
|
CHECK_PASSED=false
|
||||||
|
case "$DEP_TYPE" in
|
||||||
|
"tcp")
|
||||||
|
HOST="${PARTS[2]}"
|
||||||
|
PORT="${PARTS[3]}"
|
||||||
|
if [ -z "$HOST" ] || [ -z "$PORT" ]; then
|
||||||
|
log "ERROR: Malformed TCP dependency tag '$TAG'. Skipping."
|
||||||
|
CHECK_PASSED=true # Skip to avoid infinite loop
|
||||||
|
# nc -z is great for this. -w sets a timeout.
|
||||||
|
elif nc -z -w 2 "$HOST" "$PORT"; then
|
||||||
|
log "TCP dependency met: Host $HOST port $PORT is open."
|
||||||
|
CHECK_PASSED=true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
"ping")
|
||||||
|
HOST="${PARTS[2]}"
|
||||||
|
if [ -z "$HOST" ]; then
|
||||||
|
log "ERROR: Malformed PING dependency tag '$TAG'. Skipping."
|
||||||
|
CHECK_PASSED=true # Skip to avoid infinite loop
|
||||||
|
# ping -c 1 (one packet) -W 2 (2-second timeout)
|
||||||
|
elif ping -c 1 -W 2 "$HOST" >/dev/null 2>&1; then
|
||||||
|
log "Ping dependency met: Host $HOST is reachable."
|
||||||
|
CHECK_PASSED=true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log "WARNING: Unknown dependency type '$DEP_TYPE' in tag '$TAG'. Ignoring."
|
||||||
|
CHECK_PASSED=true # Mark as passed to avoid getting stuck
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if $CHECK_PASSED; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTEMPTS=$((ATTEMPTS + 1))
|
||||||
|
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
||||||
|
log "ERROR: Timeout waiting for dependency '$TAG'. Aborting start."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Dependency '$TAG' not met. Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "All custom dependencies are met."
|
||||||
|
log "--- Dependency Check Complete. Proceeding with start. ---"
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x /var/lib/vz/snippets/dependency-check.sh
|
||||||
|
msg_ok "Created dependency-check hookscript"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create the config file for exclusions
|
||||||
|
create_exclusion_config() {
|
||||||
|
msg_info "Creating exclusion configuration file"
|
||||||
|
if [ -f /etc/default/pve-auto-hook ]; then
|
||||||
|
msg_ok "Exclusion file already exists, skipping."
|
||||||
|
else
|
||||||
|
cat <<'EOF' >/etc/default/pve-auto-hook
|
||||||
|
#
|
||||||
|
# Configuration for the Proxmox Automatic Hookscript Applicator
|
||||||
|
#
|
||||||
|
# Add VM or LXC IDs here to prevent the hookscript from being added.
|
||||||
|
# Separate IDs with spaces.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# IGNORE_IDS="9000 9001 105"
|
||||||
|
#
|
||||||
|
|
||||||
|
IGNORE_IDS=""
|
||||||
|
EOF
|
||||||
|
msg_ok "Created exclusion configuration file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create the script that applies the hook
|
||||||
|
create_applicator_script() {
|
||||||
|
msg_info "Creating the hookscript applicator script"
|
||||||
|
cat <<'EOF' >/usr/local/bin/pve-apply-hookscript.sh
|
||||||
|
#!/bin/bash
|
||||||
|
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
||||||
|
CONFIG_FILE="/etc/default/pve-auto-hook"
|
||||||
|
LOG_TAG="pve-auto-hook-list"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
systemd-cat -t "$LOG_TAG" <<< "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process QEMU VMs
|
||||||
|
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
is_ignored=false
|
||||||
|
for id_to_ignore in $IGNORE_IDS; do
|
||||||
|
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
||||||
|
done
|
||||||
|
if $is_ignored; then continue; fi
|
||||||
|
if qm config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
||||||
|
log "Hookscript not found for VM $VMID. Applying..."
|
||||||
|
qm set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Process LXC Containers
|
||||||
|
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
is_ignored=false
|
||||||
|
for id_to_ignore in $IGNORE_IDS; do
|
||||||
|
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
||||||
|
done
|
||||||
|
if $is_ignored; then continue; fi
|
||||||
|
if pct config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
||||||
|
log "Hookscript not found for LXC $VMID. Applying..."
|
||||||
|
pct set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
||||||
|
done
|
||||||
|
EOF
|
||||||
|
chmod +x /usr/local/bin/pve-apply-hookscript.sh
|
||||||
|
msg_ok "Created applicator script"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to set up the systemd watcher and service
|
||||||
|
create_systemd_units() {
|
||||||
|
msg_info "Creating systemd watcher and service units"
|
||||||
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.path
|
||||||
|
[Unit]
|
||||||
|
Description=Watch for new Proxmox guest configs to apply hookscript
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathModified=/etc/pve/qemu-server/
|
||||||
|
PathModified=/etc/pve/lxc/
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.service
|
||||||
|
[Unit]
|
||||||
|
Description=Automatically add hookscript to new Proxmox guests
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
||||||
|
EOF
|
||||||
|
msg_ok "Created systemd units"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main Execution ---
|
||||||
|
header_info
|
||||||
|
|
||||||
|
if ! command -v pveversion >/dev/null 2>&1; then
|
||||||
|
msg_error "This script must be run on a Proxmox VE host."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\nThis script will install a service to automatically apply a"
|
||||||
|
echo -e "dependency-checking hookscript to all new and existing Proxmox guests."
|
||||||
|
echo -e "${YW}This includes creating files in:${CL}"
|
||||||
|
echo -e " - /var/lib/vz/snippets/"
|
||||||
|
echo -e " - /usr/local/bin/"
|
||||||
|
echo -e " - /etc/default/"
|
||||||
|
echo -e " - /etc/systemd/system/\n"
|
||||||
|
|
||||||
|
read -p "Do you want to proceed with the installation? (y/n): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
msg_error "Installation cancelled."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n"
|
||||||
|
create_dependency_hookscript
|
||||||
|
create_exclusion_config
|
||||||
|
create_applicator_script
|
||||||
|
create_systemd_units
|
||||||
|
|
||||||
|
msg_info "Reloading systemd and enabling the watcher"
|
||||||
|
(systemctl daemon-reload && systemctl enable --now pve-auto-hook.path) >/dev/null 2>&1 &
|
||||||
|
spinner
|
||||||
|
msg_ok "Systemd watcher enabled and running"
|
||||||
|
|
||||||
|
msg_info "Performing initial run to update existing guests"
|
||||||
|
/usr/local/bin/pve-apply-hookscript.sh >/dev/null 2>&1 &
|
||||||
|
spinner
|
||||||
|
msg_ok "Initial run complete"
|
||||||
|
|
||||||
|
echo -e "\n\n${GN}Installation successful!${CL}"
|
||||||
|
echo -e "The service is now active and will monitor for new guests."
|
||||||
|
echo -e "To ${YW}exclude${CL} a VM or LXC, add its ID to the ${YW}IGNORE_IDS${CL} variable in:"
|
||||||
|
echo -e " ${YW}/etc/default/pve-auto-hook${CL}"
|
||||||
|
echo -e "\nYou can monitor the service's activity with:"
|
||||||
|
echo -e " ${YW}journalctl -fu pve-auto-hook.service${CL}\n"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@ -1,10 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Copyright (c) 2023 community-scripts ORG
|
# Copyright (c) 2023-2026 community-scripts ORG
|
||||||
# This script is designed to install the Proxmox Dependency Check Hookscript.
|
# Author: MickLesk | Maintainer: community-scripts
|
||||||
# It sets up a dependency-checking hookscript and automates its
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
# application to all new and existing guests using a systemd watcher.
|
# Source: https://www.proxmox.com/
|
||||||
# License: MIT
|
|
||||||
|
|
||||||
function header_info {
|
function header_info {
|
||||||
clear
|
clear
|
||||||
@ -18,195 +17,220 @@ function header_info {
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Color variables
|
|
||||||
YW=$(echo "\033[33m")
|
YW=$(echo "\033[33m")
|
||||||
GN=$(echo "\033[1;92m")
|
GN=$(echo "\033[1;92m")
|
||||||
RD=$(echo "\033[01;31m")
|
RD=$(echo "\033[01;31m")
|
||||||
|
BL=$(echo "\033[36m")
|
||||||
CL=$(echo "\033[m")
|
CL=$(echo "\033[m")
|
||||||
BFR="\\r\\033[K"
|
BFR="\\r\\033[K"
|
||||||
HOLD=" "
|
CM="${GN}✔️${CL}"
|
||||||
CM="${GN}✓${CL}"
|
CROSS="${RD}✖️${CL}"
|
||||||
CROSS="${RD}✗${CL}"
|
INFO="${BL}ℹ️${CL}"
|
||||||
|
|
||||||
# Spinner for progress indication (simplified)
|
|
||||||
spinner() {
|
|
||||||
local pid=$!
|
|
||||||
local delay=0.1
|
|
||||||
local spinstr='|/-\'
|
|
||||||
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
|
|
||||||
local temp=${spinstr#?}
|
|
||||||
printf " [%c] " "$spinstr"
|
|
||||||
local spinstr=$temp${spinstr%"$temp"}
|
|
||||||
sleep $delay
|
|
||||||
printf "\b\b\b\b\b\b"
|
|
||||||
done
|
|
||||||
printf " \b\b\b\b"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Message functions
|
|
||||||
msg_info() {
|
msg_info() {
|
||||||
echo -ne " ${YW}›${CL} $1..."
|
local msg="$1"
|
||||||
|
echo -e "${INFO} ${YW}${msg}...${CL}"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_ok() {
|
msg_ok() {
|
||||||
echo -e "${BFR} ${CM} $1${CL}"
|
local msg="$1"
|
||||||
|
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_error() {
|
msg_error() {
|
||||||
echo -e "${BFR} ${CROSS} $1${CL}"
|
local msg="$1"
|
||||||
|
echo -e "${BFR} ${CROSS} ${RD}${msg}${CL}"
|
||||||
}
|
}
|
||||||
# --- End of base script functions ---
|
|
||||||
|
|
||||||
|
SCRIPT_NAME="$(basename "$0")"
|
||||||
|
HOOKSCRIPT_FILE="/var/lib/vz/snippets/dependency-check.sh"
|
||||||
|
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
||||||
|
CONFIG_FILE="/etc/default/pve-auto-hook"
|
||||||
|
APPLICATOR_FILE="/usr/local/bin/pve-apply-hookscript.sh"
|
||||||
|
PATH_UNIT_FILE="/etc/systemd/system/pve-auto-hook.path"
|
||||||
|
SERVICE_UNIT_FILE="/etc/systemd/system/pve-auto-hook.service"
|
||||||
|
|
||||||
# --- Installation Functions ---
|
function print_usage {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: ${SCRIPT_NAME} [OPTIONS]
|
||||||
|
|
||||||
|
Install or remove the Proxmox startup dependency-check hook system.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--install Install/update hookscript automation (default)
|
||||||
|
--uninstall Remove automation and cleanup hookscript assignments
|
||||||
|
--status Show current installation state
|
||||||
|
--help, -h Show this help message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_supported_pve {
|
||||||
|
if ! command -v pveversion >/dev/null 2>&1; then
|
||||||
|
msg_error "This script must be run on a Proxmox VE host"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pve_version major
|
||||||
|
pve_version=$(pveversion | grep -oE 'pve-manager/[0-9.]+' | cut -d'/' -f2)
|
||||||
|
major=$(echo "$pve_version" | cut -d'.' -f1)
|
||||||
|
|
||||||
|
if [[ -z "$major" ]] || ! [[ "$major" =~ ^[0-9]+$ ]]; then
|
||||||
|
msg_error "Unable to detect a supported Proxmox version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$major" -lt 8 ]] || [[ "$major" -gt 9 ]]; then
|
||||||
|
msg_error "Supported on Proxmox VE 8.x and 9.x (detected: $pve_version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "Proxmox VE $pve_version detected"
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm_action {
|
||||||
|
local prompt="$1"
|
||||||
|
read -r -p "$prompt (y/n): " -n 1 REPLY
|
||||||
|
echo
|
||||||
|
[[ "$REPLY" =~ ^[Yy]$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
# Function to create the actual hookscript that runs before guest startup
|
|
||||||
create_dependency_hookscript() {
|
create_dependency_hookscript() {
|
||||||
msg_info "Creating dependency-check hookscript"
|
msg_info "Creating dependency-check hookscript"
|
||||||
mkdir -p /var/lib/vz/snippets
|
mkdir -p /var/lib/vz/snippets
|
||||||
cat <<'EOF' > /var/lib/vz/snippets/dependency-check.sh
|
cat <<'EOF' >/var/lib/vz/snippets/dependency-check.sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Proxmox Hookscript for Pre-Start Dependency Checking
|
# Proxmox Hookscript for Pre-Start Dependency Checking
|
||||||
# Works for both QEMU VMs and LXC Containers
|
# Works for both QEMU VMs and LXC Containers
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
POLL_INTERVAL=5 # Seconds to wait between checks
|
POLL_INTERVAL=5 # Seconds to wait between checks
|
||||||
MAX_ATTEMPTS=60 # Max number of attempts before failing (60 * 5s = 5 minutes)
|
MAX_ATTEMPTS=60 # Max number of attempts before failing (60 * 5s = 5 minutes)
|
||||||
# --- End Configuration ---
|
|
||||||
|
|
||||||
VMID=$1
|
VMID=$1
|
||||||
PHASE=$2
|
PHASE=$2
|
||||||
|
|
||||||
# Function for logging to syslog with a consistent format
|
|
||||||
log() {
|
log() {
|
||||||
echo "[hookscript-dep-check] VMID $VMID: $1"
|
logger -t hookscript-dep-check "VMID $VMID: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
has_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_tcp() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
|
||||||
|
if has_cmd nc; then
|
||||||
|
nc -z -w 2 "$host" "$port" >/dev/null 2>&1
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
timeout 2 bash -c "</dev/tcp/${host}/${port}" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_until() {
|
||||||
|
local description="$1"
|
||||||
|
local check_cmd="$2"
|
||||||
|
|
||||||
|
local attempts=0
|
||||||
|
while true; do
|
||||||
|
if eval "$check_cmd"; then
|
||||||
|
log "$description"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
attempts=$((attempts + 1))
|
||||||
|
if [ "$attempts" -ge "$MAX_ATTEMPTS" ]; then
|
||||||
|
log "ERROR: Timeout waiting for condition: $description"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Waiting ${POLL_INTERVAL}s for condition: $description (Attempt ${attempts}/${MAX_ATTEMPTS})"
|
||||||
|
sleep "$POLL_INTERVAL"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# This script only runs in the 'pre-start' phase
|
|
||||||
if [ "$PHASE" != "pre-start" ]; then
|
if [ "$PHASE" != "pre-start" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "--- Starting Pre-Start Dependency Check ---"
|
log "--- Starting Pre-Start Dependency Check ---"
|
||||||
|
|
||||||
# --- Determine Guest Type (QEMU or LXC) ---
|
|
||||||
GUEST_TYPE=""
|
|
||||||
CONFIG_CMD=""
|
|
||||||
if qm config "$VMID" >/dev/null 2>&1; then
|
if qm config "$VMID" >/dev/null 2>&1; then
|
||||||
GUEST_TYPE="qemu"
|
CONFIG_CMD=(qm config "$VMID")
|
||||||
CONFIG_CMD="qm config"
|
|
||||||
log "Guest type is QEMU (VM)."
|
log "Guest type is QEMU (VM)."
|
||||||
elif pct config "$VMID" >/dev/null 2>&1; then
|
elif pct config "$VMID" >/dev/null 2>&1; then
|
||||||
GUEST_TYPE="lxc"
|
CONFIG_CMD=(pct config "$VMID")
|
||||||
CONFIG_CMD="pct config"
|
|
||||||
log "Guest type is LXC (Container)."
|
log "Guest type is LXC (Container)."
|
||||||
else
|
else
|
||||||
log "ERROR: Could not determine guest type for $VMID. Aborting."
|
log "ERROR: Could not determine guest type for $VMID. Aborting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
GUEST_CONFIG=$($CONFIG_CMD "$VMID")
|
GUEST_CONFIG=$("${CONFIG_CMD[@]}")
|
||||||
|
|
||||||
# --- 1. Storage Availability Check ---
|
|
||||||
log "Checking storage availability..."
|
log "Checking storage availability..."
|
||||||
# Grep for all disk definitions (scsi, sata, virtio, ide, rootfs, mp)
|
STORAGE_IDS=$(echo "$GUEST_CONFIG" | awk -F':' '
|
||||||
# and extract the storage identifier (the field between the colons).
|
/^(scsi|sata|virtio|ide|efidisk|tpmstate|unused|rootfs|mp)[0-9]*:/ {
|
||||||
# Sort -u gets the unique list of storage pools.
|
val=$2
|
||||||
STORAGE_IDS=$(echo "$GUEST_CONFIG" | grep -E '^(scsi|sata|virtio|ide|rootfs|mp)[0-9]*:' | awk -F'[:]' '{print $2}' | awk '{print$1}' | sort -u)
|
gsub(/^[[:space:]]+/, "", val)
|
||||||
|
split(val, parts, ",")
|
||||||
|
storage=parts[1]
|
||||||
|
|
||||||
|
# Skip bind-mount style paths and empty values
|
||||||
|
if (storage == "" || storage ~ /^\//) next
|
||||||
|
|
||||||
|
print storage
|
||||||
|
}
|
||||||
|
' | sort -u)
|
||||||
|
|
||||||
if [ -z "$STORAGE_IDS" ]; then
|
if [ -z "$STORAGE_IDS" ]; then
|
||||||
log "No storage dependencies found to check."
|
log "No storage dependencies found to check."
|
||||||
else
|
else
|
||||||
for STORAGE_ID in $STORAGE_IDS; do
|
for STORAGE_ID in $STORAGE_IDS; do
|
||||||
log "Checking status of storage: '$STORAGE_ID'"
|
STATUS=$(pvesm status 2>/dev/null | awk -v id="$STORAGE_ID" '$1 == id { print $3; exit }')
|
||||||
ATTEMPTS=0
|
|
||||||
while true; do
|
|
||||||
# Grep for the storage ID line in pvesm status and check the 'Active' column (3rd column)
|
|
||||||
STATUS=$(pvesm status | grep "^\s*$STORAGE_ID\s" | awk '{print $3}')
|
|
||||||
if [ "$STATUS" == "active" ]; then
|
|
||||||
log "Storage '$STORAGE_ID' is active."
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
ATTEMPTS=$((ATTEMPTS + 1))
|
if [ -z "$STATUS" ]; then
|
||||||
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
log "WARNING: Storage '$STORAGE_ID' not found in 'pvesm status'. Skipping this dependency."
|
||||||
log "ERROR: Timeout waiting for storage '$STORAGE_ID' to become active. Aborting start."
|
continue
|
||||||
exit 1
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
log "Storage '$STORAGE_ID' is not active (current status: '${STATUS:-inactive/unknown}'). Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
wait_until "Storage '$STORAGE_ID' is active." "[ \"\$(pvesm status 2>/dev/null | awk -v id=\"$STORAGE_ID\" '\$1 == id { print \$3; exit }')\" = \"active\" ]" || exit 1
|
||||||
sleep $POLL_INTERVAL
|
|
||||||
done
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
log "All storage dependencies are met."
|
log "All storage dependencies are met."
|
||||||
|
|
||||||
|
|
||||||
# --- 2. Custom Tag-Based Dependency Check ---
|
|
||||||
log "Checking for custom tag-based dependencies..."
|
log "Checking for custom tag-based dependencies..."
|
||||||
TAGS=$(echo "$GUEST_CONFIG" | grep '^tags:' | awk '{print $2}')
|
TAGS=$(echo "$GUEST_CONFIG" | awk -F': ' '/^tags:/ {print $2}')
|
||||||
|
|
||||||
if [ -z "$TAGS" ]; then
|
if [ -z "$TAGS" ]; then
|
||||||
log "No tags found. Skipping custom dependency check."
|
log "No tags found. Skipping custom dependency check."
|
||||||
else
|
else
|
||||||
# Replace colons with spaces to loop through tags
|
|
||||||
for TAG in ${TAGS//;/ }; do
|
for TAG in ${TAGS//;/ }; do
|
||||||
# Check if the tag matches our dependency format 'dep_*'
|
|
||||||
if [[ $TAG == dep_* ]]; then
|
if [[ $TAG == dep_* ]]; then
|
||||||
log "Found dependency tag: '$TAG'"
|
log "Found dependency tag: '$TAG'"
|
||||||
|
|
||||||
# Split tag into parts using underscore as delimiter
|
IFS='_' read -r _ DEP_TYPE HOST PORT EXTRA <<< "$TAG"
|
||||||
IFS='_' read -ra PARTS <<< "$TAG"
|
|
||||||
DEP_TYPE="${PARTS[1]}"
|
|
||||||
|
|
||||||
ATTEMPTS=0
|
case "$DEP_TYPE" in
|
||||||
while true; do
|
ping)
|
||||||
CHECK_PASSED=false
|
if [ -z "$HOST" ]; then
|
||||||
case "$DEP_TYPE" in
|
log "WARNING: Malformed ping dependency tag '$TAG'. Ignoring."
|
||||||
"tcp")
|
continue
|
||||||
HOST="${PARTS[2]}"
|
fi
|
||||||
PORT="${PARTS[3]}"
|
wait_until "Ping dependency met: Host $HOST is reachable." "ping -c 1 -W 2 \"$HOST\" >/dev/null 2>&1" || exit 1
|
||||||
if [ -z "$HOST" ] || [ -z "$PORT" ]; then
|
;;
|
||||||
log "ERROR: Malformed TCP dependency tag '$TAG'. Skipping."
|
tcp)
|
||||||
CHECK_PASSED=true # Skip to avoid infinite loop
|
if [ -z "$HOST" ] || [ -z "$PORT" ] || ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
|
||||||
# nc -z is great for this. -w sets a timeout.
|
log "WARNING: Malformed TCP dependency tag '$TAG'. Expected dep_tcp_<host>_<port>. Ignoring."
|
||||||
elif nc -z -w 2 "$HOST" "$PORT"; then
|
continue
|
||||||
log "TCP dependency met: Host $HOST port $PORT is open."
|
fi
|
||||||
CHECK_PASSED=true
|
wait_until "TCP dependency met: Host $HOST port $PORT is open." "check_tcp \"$HOST\" \"$PORT\"" || exit 1
|
||||||
fi
|
;;
|
||||||
;;
|
*)
|
||||||
|
log "WARNING: Unknown dependency type '$DEP_TYPE' in tag '$TAG'. Ignoring."
|
||||||
"ping")
|
;;
|
||||||
HOST="${PARTS[2]}"
|
esac
|
||||||
if [ -z "$HOST" ]; then
|
|
||||||
log "ERROR: Malformed PING dependency tag '$TAG'. Skipping."
|
|
||||||
CHECK_PASSED=true # Skip to avoid infinite loop
|
|
||||||
# ping -c 1 (one packet) -W 2 (2-second timeout)
|
|
||||||
elif ping -c 1 -W 2 "$HOST" >/dev/null 2>&1; then
|
|
||||||
log "Ping dependency met: Host $HOST is reachable."
|
|
||||||
CHECK_PASSED=true
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
log "WARNING: Unknown dependency type '$DEP_TYPE' in tag '$TAG'. Ignoring."
|
|
||||||
CHECK_PASSED=true # Mark as passed to avoid getting stuck
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if $CHECK_PASSED; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
ATTEMPTS=$((ATTEMPTS + 1))
|
|
||||||
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
|
||||||
log "ERROR: Timeout waiting for dependency '$TAG'. Aborting start."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Dependency '$TAG' not met. Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
|
||||||
sleep $POLL_INTERVAL
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@ -215,17 +239,16 @@ log "All custom dependencies are met."
|
|||||||
log "--- Dependency Check Complete. Proceeding with start. ---"
|
log "--- Dependency Check Complete. Proceeding with start. ---"
|
||||||
exit 0
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
chmod +x /var/lib/vz/snippets/dependency-check.sh
|
chmod +x "$HOOKSCRIPT_FILE"
|
||||||
msg_ok "Created dependency-check hookscript"
|
msg_ok "Created dependency-check hookscript"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the config file for exclusions
|
|
||||||
create_exclusion_config() {
|
create_exclusion_config() {
|
||||||
msg_info "Creating exclusion configuration file"
|
msg_info "Creating exclusion configuration file"
|
||||||
if [ -f /etc/default/pve-auto-hook ]; then
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
msg_ok "Exclusion file already exists, skipping."
|
msg_ok "Exclusion file already exists, skipping."
|
||||||
else
|
else
|
||||||
cat <<'EOF' > /etc/default/pve-auto-hook
|
cat <<'EOF' >/etc/default/pve-auto-hook
|
||||||
#
|
#
|
||||||
# Configuration for the Proxmox Automatic Hookscript Applicator
|
# Configuration for the Proxmox Automatic Hookscript Applicator
|
||||||
#
|
#
|
||||||
@ -238,71 +261,99 @@ create_exclusion_config() {
|
|||||||
|
|
||||||
IGNORE_IDS=""
|
IGNORE_IDS=""
|
||||||
EOF
|
EOF
|
||||||
msg_ok "Created exclusion configuration file"
|
chmod 0644 "$CONFIG_FILE"
|
||||||
fi
|
msg_ok "Created exclusion configuration file"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the script that applies the hook
|
|
||||||
create_applicator_script() {
|
create_applicator_script() {
|
||||||
msg_info "Creating the hookscript applicator script"
|
msg_info "Creating the hookscript applicator script"
|
||||||
cat <<'EOF' > /usr/local/bin/pve-apply-hookscript.sh
|
cat <<'EOF' >/usr/local/bin/pve-apply-hookscript.sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
||||||
CONFIG_FILE="/etc/default/pve-auto-hook"
|
CONFIG_FILE="/etc/default/pve-auto-hook"
|
||||||
LOG_TAG="pve-auto-hook-list"
|
LOG_TAG="pve-auto-hook"
|
||||||
|
IGNORE_IDS=""
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
systemd-cat -t "$LOG_TAG" <<< "$1"
|
systemd-cat -t "$LOG_TAG" <<< "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
source "$CONFIG_FILE"
|
IGNORE_IDS=$(grep -E '^IGNORE_IDS=' "$CONFIG_FILE" | head -n1 | cut -d'=' -f2- | tr -d '"')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Process QEMU VMs
|
is_ignored() {
|
||||||
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
local vmid="$1"
|
||||||
is_ignored=false
|
|
||||||
for id_to_ignore in $IGNORE_IDS; do
|
for id_to_ignore in $IGNORE_IDS; do
|
||||||
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
if [ "$id_to_ignore" = "$vmid" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
if $is_ignored; then continue; fi
|
return 1
|
||||||
if qm config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
}
|
||||||
log "Hookscript not found for VM $VMID. Applying..."
|
|
||||||
qm set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
ensure_hookscript() {
|
||||||
done
|
local guest_type="$1"
|
||||||
|
local vmid="$2"
|
||||||
# Process LXC Containers
|
local current_hook=""
|
||||||
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
|
||||||
is_ignored=false
|
if [ "$guest_type" = "qemu" ]; then
|
||||||
for id_to_ignore in $IGNORE_IDS; do
|
current_hook=$(qm config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
else
|
||||||
done
|
current_hook=$(pct config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
if $is_ignored; then continue; fi
|
fi
|
||||||
if pct config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
|
||||||
log "Hookscript not found for LXC $VMID. Applying..."
|
if [ -n "$current_hook" ]; then
|
||||||
pct set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
if [ "$current_hook" = "$HOOKSCRIPT_VOLUME_ID" ]; then
|
||||||
done
|
return 0
|
||||||
EOF
|
fi
|
||||||
chmod +x /usr/local/bin/pve-apply-hookscript.sh
|
log "Guest $guest_type/$vmid already has another hookscript ($current_hook). Leaving unchanged."
|
||||||
msg_ok "Created applicator script"
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Applying hookscript to $guest_type/$vmid"
|
||||||
|
if [ "$guest_type" = "qemu" ]; then
|
||||||
|
qm set "$vmid" --hookscript "$HOOKSCRIPT_VOLUME_ID" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
pct set "$vmid" --hookscript "$HOOKSCRIPT_VOLUME_ID" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
if is_ignored "$VMID"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ensure_hookscript "qemu" "$VMID"
|
||||||
|
done
|
||||||
|
|
||||||
|
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
if is_ignored "$VMID"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ensure_hookscript "lxc" "$VMID"
|
||||||
|
done
|
||||||
|
EOF
|
||||||
|
chmod +x "$APPLICATOR_FILE"
|
||||||
|
msg_ok "Created applicator script"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to set up the systemd watcher and service
|
|
||||||
create_systemd_units() {
|
create_systemd_units() {
|
||||||
msg_info "Creating systemd watcher and service units"
|
msg_info "Creating systemd watcher and service units"
|
||||||
cat <<'EOF' > /etc/systemd/system/pve-auto-hook.path
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.path
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Watch for new Proxmox guest configs to apply hookscript
|
Description=Watch for new Proxmox guest configs to apply hookscript
|
||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
PathModified=/etc/pve/qemu-server/
|
PathExistsGlob=/etc/pve/qemu-server/*.conf
|
||||||
PathModified=/etc/pve/lxc/
|
PathExistsGlob=/etc/pve/lxc/*.conf
|
||||||
|
Unit=pve-auto-hook.service
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<'EOF' > /etc/systemd/system/pve-auto-hook.service
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.service
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Automatically add hookscript to new Proxmox guests
|
Description=Automatically add hookscript to new Proxmox guests
|
||||||
|
|
||||||
@ -310,54 +361,149 @@ Description=Automatically add hookscript to new Proxmox guests
|
|||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
||||||
EOF
|
EOF
|
||||||
msg_ok "Created systemd units"
|
chmod 0644 "$PATH_UNIT_FILE" "$SERVICE_UNIT_FILE"
|
||||||
|
msg_ok "Created systemd units"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remove_hookscript_assignments() {
|
||||||
|
msg_info "Removing hookscript assignment from guests using dependency-check"
|
||||||
|
|
||||||
|
qm list | awk 'NR>1 {print $1}' | while read -r vmid; do
|
||||||
|
current_hook=$(qm config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
|
if [ "$current_hook" = "$HOOKSCRIPT_VOLUME_ID" ]; then
|
||||||
|
qm set "$vmid" --delete hookscript >/dev/null 2>&1 && msg_ok "Removed hookscript from VM $vmid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
pct list | awk 'NR>1 {print $1}' | while read -r vmid; do
|
||||||
|
current_hook=$(pct config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
|
if [ "$current_hook" = "$HOOKSCRIPT_VOLUME_ID" ]; then
|
||||||
|
pct set "$vmid" --delete hookscript >/dev/null 2>&1 && msg_ok "Removed hookscript from LXC $vmid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
install_stack() {
|
||||||
|
create_dependency_hookscript
|
||||||
|
create_exclusion_config
|
||||||
|
create_applicator_script
|
||||||
|
create_systemd_units
|
||||||
|
|
||||||
|
msg_info "Reloading systemd and enabling watcher"
|
||||||
|
if systemctl daemon-reload && systemctl enable --now pve-auto-hook.path >/dev/null 2>&1; then
|
||||||
|
msg_ok "Systemd watcher enabled and running"
|
||||||
|
else
|
||||||
|
msg_error "Could not enable pve-auto-hook.path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_info "Performing initial run to update existing guests"
|
||||||
|
if "$APPLICATOR_FILE" >/dev/null 2>&1; then
|
||||||
|
msg_ok "Initial run complete"
|
||||||
|
else
|
||||||
|
msg_error "Initial run failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall_stack() {
|
||||||
|
remove_hookscript_assignments
|
||||||
|
|
||||||
|
msg_info "Stopping and disabling systemd units"
|
||||||
|
systemctl disable --now pve-auto-hook.path >/dev/null 2>&1 || true
|
||||||
|
systemctl disable --now pve-auto-hook.service >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
msg_info "Removing installed files"
|
||||||
|
rm -f "$HOOKSCRIPT_FILE" "$APPLICATOR_FILE" "$PATH_UNIT_FILE" "$SERVICE_UNIT_FILE" "$CONFIG_FILE"
|
||||||
|
|
||||||
|
if systemctl daemon-reload >/dev/null 2>&1; then
|
||||||
|
msg_ok "systemd daemon reloaded"
|
||||||
|
else
|
||||||
|
msg_error "Failed to reload systemd daemon"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "Dependency-check stack successfully removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
echo -e "\n${BL}Dependency-check status${CL}"
|
||||||
|
echo -e "--------------------------------"
|
||||||
|
[ -f "$HOOKSCRIPT_FILE" ] && echo -e "Hookscript file: ${GN}present${CL}" || echo -e "Hookscript file: ${RD}missing${CL}"
|
||||||
|
[ -f "$APPLICATOR_FILE" ] && echo -e "Applicator script: ${GN}present${CL}" || echo -e "Applicator script: ${RD}missing${CL}"
|
||||||
|
[ -f "$CONFIG_FILE" ] && echo -e "Config file: ${GN}present${CL}" || echo -e "Config file: ${RD}missing${CL}"
|
||||||
|
[ -f "$PATH_UNIT_FILE" ] && echo -e "Path unit: ${GN}present${CL}" || echo -e "Path unit: ${RD}missing${CL}"
|
||||||
|
[ -f "$SERVICE_UNIT_FILE" ] && echo -e "Service unit: ${GN}present${CL}" || echo -e "Service unit: ${RD}missing${CL}"
|
||||||
|
|
||||||
|
if systemctl is-enabled pve-auto-hook.path >/dev/null 2>&1; then
|
||||||
|
echo -e "Watcher enabled: ${GN}yes${CL}"
|
||||||
|
else
|
||||||
|
echo -e "Watcher enabled: ${YW}no${CL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if systemctl is-active pve-auto-hook.path >/dev/null 2>&1; then
|
||||||
|
echo -e "Watcher active: ${GN}yes${CL}"
|
||||||
|
else
|
||||||
|
echo -e "Watcher active: ${YW}no${CL}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# --- Main Execution ---
|
|
||||||
header_info
|
header_info
|
||||||
|
ensure_supported_pve
|
||||||
|
|
||||||
if ! command -v pveversion >/dev/null 2>&1; then
|
case "${1:---install}" in
|
||||||
msg_error "This script must be run on a Proxmox VE host."
|
--help | -h)
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--status)
|
||||||
|
show_status
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--install)
|
||||||
|
echo -e "\nThis script will install a service to automatically apply a"
|
||||||
|
echo -e "dependency-checking hookscript to all new and existing Proxmox guests."
|
||||||
|
echo -e "${YW}This includes creating files in:${CL}"
|
||||||
|
echo -e " - /var/lib/vz/snippets/"
|
||||||
|
echo -e " - /usr/local/bin/"
|
||||||
|
echo -e " - /etc/default/"
|
||||||
|
echo -e " - /etc/systemd/system/\n"
|
||||||
|
|
||||||
|
if ! confirm_action "Do you want to proceed with the installation?"; then
|
||||||
|
msg_error "Installation cancelled"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\nThis script will install a service to automatically apply a"
|
echo ""
|
||||||
echo -e "dependency-checking hookscript to all new and existing Proxmox guests."
|
install_stack
|
||||||
echo -e "${YW}This includes creating files in:${CL}"
|
|
||||||
echo -e " - /var/lib/vz/snippets/"
|
|
||||||
echo -e " - /usr/local/bin/"
|
|
||||||
echo -e " - /etc/default/"
|
|
||||||
echo -e " - /etc/systemd/system/\n"
|
|
||||||
|
|
||||||
read -p "Do you want to proceed with the installation? (y/n): " -n 1 -r
|
echo -e "\n${GN}Installation successful!${CL}"
|
||||||
echo
|
echo -e "The service is now active and will monitor for new guests."
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
echo -e "To ${YW}exclude${CL} a VM or LXC, add its ID to ${YW}IGNORE_IDS${CL} in:"
|
||||||
msg_error "Installation cancelled."
|
echo -e " ${YW}${CONFIG_FILE}${CL}"
|
||||||
|
echo -e "\nMonitor activity with:"
|
||||||
|
echo -e " ${YW}journalctl -fu pve-auto-hook.service${CL}\n"
|
||||||
|
;;
|
||||||
|
--uninstall)
|
||||||
|
echo -e "\nThis will completely remove the dependency-check stack:"
|
||||||
|
echo -e " - hookscript and applicator"
|
||||||
|
echo -e " - systemd path/service units"
|
||||||
|
echo -e " - exclusion config"
|
||||||
|
echo -e " - hookscript assignment from guests using ${HOOKSCRIPT_VOLUME_ID}\n"
|
||||||
|
|
||||||
|
if ! confirm_action "Do you want to proceed with uninstall?"; then
|
||||||
|
msg_error "Uninstall cancelled"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n"
|
echo ""
|
||||||
create_dependency_hookscript
|
uninstall_stack
|
||||||
create_exclusion_config
|
;;
|
||||||
create_applicator_script
|
*)
|
||||||
create_systemd_units
|
msg_error "Unknown option: $1"
|
||||||
|
print_usage
|
||||||
msg_info "Reloading systemd and enabling the watcher"
|
exit 1
|
||||||
(systemctl daemon-reload && systemctl enable --now pve-auto-hook.path) >/dev/null 2>&1 &
|
;;
|
||||||
spinner
|
esac
|
||||||
msg_ok "Systemd watcher enabled and running"
|
|
||||||
|
|
||||||
msg_info "Performing initial run to update existing guests"
|
|
||||||
/usr/local/bin/pve-apply-hookscript.sh >/dev/null 2>&1 &
|
|
||||||
spinner
|
|
||||||
msg_ok "Initial run complete"
|
|
||||||
|
|
||||||
echo -e "\n\n${GN}Installation successful!${CL}"
|
|
||||||
echo -e "The service is now active and will monitor for new guests."
|
|
||||||
echo -e "To ${YW}exclude${CL} a VM or LXC, add its ID to the ${YW}IGNORE_IDS${CL} variable in:"
|
|
||||||
echo -e " ${YW}/etc/default/pve-auto-hook${CL}"
|
|
||||||
echo -e "\nYou can monitor the service's activity with:"
|
|
||||||
echo -e " ${YW}journalctl -fu pve-auto-hook.service${CL}\n"
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user