Use pvesh API for cluster node discovery in install-sensor-proxy.sh

Replace brittle pvecm nodes CLI parsing with pvesh API calls. The old
approach used awk field positions ($4) which breaks across Proxmox
versions, locales, or output format changes.

Added get_cluster_node_names() helper that:
- Prefers pvesh get /cluster/status --output-format json (structured)
- Falls back to pvecm nodes CLI parsing if pvesh unavailable
- Uses python3 for JSON parsing (always available on Proxmox)

Related to #738
This commit is contained in:
rcourtman 2025-11-29 18:33:27 +00:00
parent e5db4f01c3
commit 2de2dc36c2

View file

@ -785,6 +785,44 @@ REQUESTED_VERSION=""
INSTALLER_CACHE_REASON="" INSTALLER_CACHE_REASON=""
DEFER_SOCKET_VERIFICATION=false DEFER_SOCKET_VERIFICATION=false
# Get cluster node names via Proxmox API (pvesh) - returns one hostname per line
# Falls back to pvecm CLI parsing if pvesh unavailable
get_cluster_node_names() {
# Try pvesh API first (structured JSON, version-stable)
if command -v pvesh >/dev/null 2>&1; then
local json_output
if json_output=$(pvesh get /cluster/status --output-format json 2>/dev/null); then
# Parse JSON: extract "name" from entries where "type" is "node"
# Using python3 for reliable JSON parsing (always available on Proxmox)
if command -v python3 >/dev/null 2>&1; then
local names
names=$(echo "$json_output" | python3 -c '
import sys, json
try:
data = json.load(sys.stdin)
for item in data:
if item.get("type") == "node" and item.get("name"):
print(item["name"])
except:
pass
' 2>/dev/null)
if [[ -n "$names" ]]; then
echo "$names"
return 0
fi
fi
fi
fi
# Fallback: parse pvecm nodes CLI output (fragile, position-dependent)
if command -v pvecm >/dev/null 2>&1; then
pvecm nodes 2>/dev/null | awk '/^[[:space:]]+[0-9]/ && !/Qdevice/ {print $4}' || true
return 0
fi
return 1
}
cleanup_local_authorized_keys() { cleanup_local_authorized_keys() {
local auth_keys_file="/root/.ssh/authorized_keys" local auth_keys_file="/root/.ssh/authorized_keys"
if [[ ! -f "$auth_keys_file" ]]; then if [[ ! -f "$auth_keys_file" ]]; then
@ -802,21 +840,19 @@ cleanup_local_authorized_keys() {
cleanup_cluster_authorized_keys_manual() { cleanup_cluster_authorized_keys_manual() {
local nodes=() local nodes=()
if command -v pvecm >/dev/null 2>&1; then # Use get_cluster_node_names helper (prefers pvesh API, falls back to pvecm CLI)
# Prefer hostname resolution for multi-network clusters
while IFS= read -r nodename; do while IFS= read -r nodename; do
[[ -z "$nodename" ]] && continue [[ -z "$nodename" ]] && continue
local resolved_ip local resolved_ip
resolved_ip=$(getent hosts "$nodename" 2>/dev/null | awk '{print $1; exit}') resolved_ip=$(getent hosts "$nodename" 2>/dev/null | awk '{print $1; exit}')
[[ -n "$resolved_ip" ]] && nodes+=("$resolved_ip") [[ -n "$resolved_ip" ]] && nodes+=("$resolved_ip")
done < <(pvecm nodes 2>/dev/null | awk '/^[[:space:]]+[0-9]/ && !/Qdevice/ {print $4}' || true) done < <(get_cluster_node_names 2>/dev/null || true)
# Fallback to pvecm status if hostname resolution didn't work # Fallback to pvecm status if hostname resolution didn't work
if [[ ${#nodes[@]} -eq 0 ]]; then if [[ ${#nodes[@]} -eq 0 ]] && command -v pvecm >/dev/null 2>&1; then
while IFS= read -r node_ip; do while IFS= read -r node_ip; do
[[ -n "$node_ip" ]] && nodes+=("$node_ip") [[ -n "$node_ip" ]] && nodes+=("$node_ip")
done < <(pvecm status 2>/dev/null | grep -vEi "qdevice" | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true) done < <(pvecm status 2>/dev/null | grep -vEi "qdevice" | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true)
fi fi
fi
if [[ ${#nodes[@]} -eq 0 ]]; then if [[ ${#nodes[@]} -eq 0 ]]; then
cleanup_local_authorized_keys cleanup_local_authorized_keys
@ -2662,7 +2698,7 @@ if [[ -z "$HOST" ]]; then
log_info "No specific host provided - cleaning up all cluster nodes" log_info "No specific host provided - cleaning up all cluster nodes"
# Discover cluster nodes (prefer hostname resolution for multi-network clusters) # Discover cluster nodes (prefer hostname resolution for multi-network clusters)
if command -v pvecm >/dev/null 2>&1; then # Use get_cluster_node_names helper (prefers pvesh API, falls back to pvecm CLI)
CLUSTER_NODES="" CLUSTER_NODES=""
while IFS= read -r nodename; do while IFS= read -r nodename; do
[[ -z "$nodename" ]] && continue [[ -z "$nodename" ]] && continue
@ -2670,9 +2706,9 @@ if [[ -z "$HOST" ]]; then
if [[ -n "$resolved_ip" ]]; then if [[ -n "$resolved_ip" ]]; then
CLUSTER_NODES="${CLUSTER_NODES:+$CLUSTER_NODES }$resolved_ip" CLUSTER_NODES="${CLUSTER_NODES:+$CLUSTER_NODES }$resolved_ip"
fi fi
done < <(pvecm nodes 2>/dev/null | awk '/^[[:space:]]+[0-9]/ && !/Qdevice/ {print $4}' || true) done < <(get_cluster_node_names 2>/dev/null || true)
# Fallback to pvecm status if hostname resolution didn't work # Fallback to pvecm status if hostname resolution didn't work
if [[ -z "$CLUSTER_NODES" ]]; then if [[ -z "$CLUSTER_NODES" ]] && command -v pvecm >/dev/null 2>&1; then
CLUSTER_NODES=$(pvecm status 2>/dev/null | grep -vEi "qdevice" | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true) CLUSTER_NODES=$(pvecm status 2>/dev/null | grep -vEi "qdevice" | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true)
fi fi
@ -2980,9 +3016,7 @@ fi
PROXY_PUBLIC_KEY=$(cat "$PROXY_KEY_FILE") PROXY_PUBLIC_KEY=$(cat "$PROXY_KEY_FILE")
print_info "Proxy public key: ${PROXY_PUBLIC_KEY:0:50}..." print_info "Proxy public key: ${PROXY_PUBLIC_KEY:0:50}..."
# Discover cluster nodes # Discover cluster nodes using get_cluster_node_names helper (prefers pvesh API, falls back to pvecm CLI)
if command -v pvecm >/dev/null 2>&1; then
# Extract node hostnames from pvecm nodes and resolve to IPs via /etc/hosts
# This prefers management IPs over corosync ring IPs for multi-network clusters # This prefers management IPs over corosync ring IPs for multi-network clusters
CLUSTER_NODES="" CLUSTER_NODES=""
while IFS= read -r nodename; do while IFS= read -r nodename; do
@ -2993,15 +3027,17 @@ if command -v pvecm >/dev/null 2>&1; then
CLUSTER_NODES="${CLUSTER_NODES:+$CLUSTER_NODES }$resolved_ip" CLUSTER_NODES="${CLUSTER_NODES:+$CLUSTER_NODES }$resolved_ip"
else else
# Fallback: try extracting IP directly from pvecm status for this node # Fallback: try extracting IP directly from pvecm status for this node
if command -v pvecm >/dev/null 2>&1; then
status_ip=$(pvecm status 2>/dev/null | grep -E "0x[0-9a-f]+.*$nodename" | awk '{for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) {print $i; exit}}') status_ip=$(pvecm status 2>/dev/null | grep -E "0x[0-9a-f]+.*$nodename" | awk '{for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) {print $i; exit}}')
if [[ -n "$status_ip" ]]; then if [[ -n "$status_ip" ]]; then
CLUSTER_NODES="${CLUSTER_NODES:+$CLUSTER_NODES }$status_ip" CLUSTER_NODES="${CLUSTER_NODES:+$CLUSTER_NODES }$status_ip"
fi fi
fi fi
done < <(pvecm nodes 2>/dev/null | awk '/^[[:space:]]+[0-9]/ && !/Qdevice/ {print $4}' || true) fi
done < <(get_cluster_node_names 2>/dev/null || true)
# Fallback to original method if pvecm nodes didn't yield results # Fallback to pvecm status if get_cluster_node_names didn't yield results
if [[ -z "$CLUSTER_NODES" ]]; then if [[ -z "$CLUSTER_NODES" ]] && command -v pvecm >/dev/null 2>&1; then
CLUSTER_NODES=$(pvecm status 2>/dev/null | grep -vEi "qdevice" | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true) CLUSTER_NODES=$(pvecm status 2>/dev/null | grep -vEi "qdevice" | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true)
fi fi
@ -3137,36 +3173,6 @@ if command -v pvecm >/dev/null 2>&1; then
exit 1 exit 1
fi fi
fi fi
else
# Proxmox host but pvecm not available (shouldn't happen, but handle it)
print_warn "pvecm command not available"
print_info "Configuring SSH key for localhost..."
# Configure localhost as fallback
FORCED_CMD='command="/opt/pulse/sensor-proxy/bin/pulse-sensor-wrapper.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty'
AUTH_LINE="${FORCED_CMD} ${PROXY_PUBLIC_KEY} # pulse-managed-key"
configure_local_authorized_key "$AUTH_LINE"
# Add localhost to config file for allowlist validation
print_info "Updating proxy configuration for localhost fallback..."
LOCAL_IPS=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -v '^$' || echo "127.0.0.1")
# Collect all local IPs and localhost variants into array
all_nodes=()
for local_ip in $LOCAL_IPS; do
all_nodes+=("$local_ip")
done
# Always include localhost variants
all_nodes+=("127.0.0.1" "localhost")
if [[ ${#CONTROL_PLANE_ALLOWED_NODE_LIST[@]} -gt 0 ]]; then
all_nodes+=("${CONTROL_PLANE_ALLOWED_NODE_LIST[@]}")
fi
# Use helper function to safely update allowed_nodes (prevents duplicates on re-run)
if ! update_allowed_nodes "Localhost fallback configuration (pvecm unavailable)" "${all_nodes[@]}"; then
print_error "Failed to update allowed_nodes list"
exit 1
fi
fi
cleanup_inline_allowed_nodes cleanup_inline_allowed_nodes