feat: add dependency-check
This commit is contained in:
parent
327d270294
commit
3f102060c0
48
frontend/public/json/dependency-check.json
Normal file
48
frontend/public/json/dependency-check.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "Proxmox VE VM Startup Dependency Check",
|
||||||
|
"slug": "dependency-check",
|
||||||
|
"categories": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"date_created": "2025-08-12",
|
||||||
|
"type": "pve",
|
||||||
|
"updateable": false,
|
||||||
|
"privileged": false,
|
||||||
|
"interface_port": null,
|
||||||
|
"documentation": null,
|
||||||
|
"website": null,
|
||||||
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/proxmox.webp",
|
||||||
|
"config_path": "/etc/default/pve-auto-hook",
|
||||||
|
"description": "This script checks for the presence of required dependencies before starting a VM or LXC container in Proxmox. It ensures that all referenced storages are available and, additionally, supports the usage of tags to check for specific dependencies. If any required dependency is missing, the VM or container will not start until the issue is resolved. This script is designed to be used as a Proxmox hookscript, which can be applied to both QEMU VMs and LXC containers.",
|
||||||
|
"install_methods": [
|
||||||
|
{
|
||||||
|
"type": "default",
|
||||||
|
"script": "tools/pve/dependency-check.sh",
|
||||||
|
"resources": {
|
||||||
|
"cpu": null,
|
||||||
|
"ram": null,
|
||||||
|
"hdd": null,
|
||||||
|
"os": null,
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_credentials": {
|
||||||
|
"username": null,
|
||||||
|
"password": null
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"text": "Execute within the Proxmox shell",
|
||||||
|
"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.",
|
||||||
|
"type": "info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "To wait until a certain TCP port is open, tag the VM or container with `dep_tcp_<hostname>_<port>` where `<hostname>` is the name or IP of the host and `<port>` is the TCP port number. The script will wait until the port is open before proceeding with the startup.",
|
||||||
|
"type": "info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
363
tools/pve/dependency-check.sh
Normal file
363
tools/pve/dependency-check.sh
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
#!/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
|
Loading…
x
Reference in New Issue
Block a user