WIP: Fix temperature proxy registration persistence (incomplete)

This commit contains multiple fixes for temperature proxy registration,
but the core issue remains unresolved.

## What's Fixed:
1. Added config pointer and reloadFunc to TemperatureProxyHandlers
2. Added SetConfig method to keep handler in sync with router config changes
3. Added config reload after registration to prevent monitor from overwriting
4. Fixed installer port conflict detection and duplicate YAML key issues
5. Added comprehensive debug logging throughout registration flow

## What's Still Broken:
The TemperatureProxyURL, TemperatureProxyToken, and TemperatureProxyControlToken
fields are NOT persisting to nodes.enc after SaveNodesConfig is called.

Debug logs confirm:
- HandleRegister correctly updates nodesConfig.PVEInstances[matchedIndex]
- The correct data is passed to SaveNodesConfig (verified in logs)
- SaveNodesConfig completes without errors
- Config reload executes successfully
- BUT after Pulse restart, the fields are empty when loaded from disk

The bug is in SaveNodesConfig serialization or file writing logic itself.

Related files:
- internal/api/temperature_proxy.go: Registration handler
- internal/config/persistence.go: SaveNodesConfig implementation
- internal/config/config.go: PVEInstance struct definition
This commit is contained in:
rcourtman 2025-11-19 20:12:19 +00:00
parent 320ef6da71
commit 844c895521
5 changed files with 160 additions and 70 deletions

View file

@ -3418,6 +3418,12 @@ echo " Pulse Monitoring Setup for Proxmox VE"
echo "============================================" echo "============================================"
echo "" echo ""
PULSE_URL="%s"
SERVER_HOST="%s"
TOKEN_NAME="%s"
PULSE_TOKEN_ID="pulse-monitor@pam!${TOKEN_NAME}"
SETUP_SCRIPT_URL="$PULSE_URL/api/setup-script?type=pve&host=$SERVER_HOST&pulse_url=$PULSE_URL"
# Check if running as root # Check if running as root
if [ "$EUID" -ne 0 ]; then if [ "$EUID" -ne 0 ]; then
echo "Please run this script as root" echo "Please run this script as root"
@ -3497,10 +3503,10 @@ case "$ENVIRONMENT" in
# 1) Create or reuse the Pulse monitoring API token # 1) Create or reuse the Pulse monitoring API token
pveum user add pulse-monitor@pam --comment "Pulse monitoring service" pveum user add pulse-monitor@pam --comment "Pulse monitoring service"
pveum aclmod / -user pulse-monitor@pam -role PVEAuditor pveum aclmod / -user pulse-monitor@pam -role PVEAuditor
pveum user token add pulse-monitor@pam %s --privsep 0 pveum user token add pulse-monitor@pam "$TOKEN_NAME" --privsep 0
# 2) Install or update pulse-sensor-proxy on the host # 2) Install or update pulse-sensor-proxy on the host
curl -sSL "%s/api/install/install-sensor-proxy.sh" | bash -s -- --ctid ${CTID_DISPLAY} --pulse-server "%s" curl -sSL "$PULSE_URL/api/install/install-sensor-proxy.sh" | bash -s -- --ctid ${CTID_DISPLAY} --pulse-server "$PULSE_URL"
# 3) Ensure the proxy socket is mounted into this container # 3) Ensure the proxy socket is mounted into this container
NEXT_MP=\$(pct config ${CTID_DISPLAY} | awk '\$1 ~ /^mp[0-9]+:/ && index(\$0, "mp=/mnt/pulse-proxy") {gsub(":", "", \$1); print \$1; exit}') NEXT_MP=\$(pct config ${CTID_DISPLAY} | awk '\$1 ~ /^mp[0-9]+:/ && index(\$0, "mp=/mnt/pulse-proxy") {gsub(":", "", \$1); print \$1; exit}')
@ -3510,7 +3516,7 @@ pct exec ${CTID_DISPLAY} -- test -S /mnt/pulse-proxy/pulse-sensor-proxy.sock &&
EOF EOF
echo "For the simplest experience, run this script on your Proxmox host instead:" echo "For the simplest experience, run this script on your Proxmox host instead:"
echo " curl -sSL \"%s/api/setup-script?type=pve&host=%s&pulse_url=%s\" | bash" echo " curl -sSL \"$SETUP_SCRIPT_URL\" | bash"
echo "" echo ""
echo "Exiting without error. Re-run after completing the host steps." echo "Exiting without error. Re-run after completing the host steps."
exit 0 exit 0
@ -3519,23 +3525,23 @@ EOF
echo "This script requires Proxmox host tooling (pveum)." echo "This script requires Proxmox host tooling (pveum)."
echo "" echo ""
echo "Run on your Proxmox host:" echo "Run on your Proxmox host:"
echo " curl -sSL \"%s/api/setup-script?type=pve&host=%s&pulse_url=%s\" | bash" echo " curl -sSL \"$SETUP_SCRIPT_URL\" | bash"
echo "" echo ""
echo "Manual setup steps:" echo "Manual setup steps:"
echo " 1. On Proxmox host, create API token:" echo " 1. On Proxmox host, create API token:"
echo " pveum user add pulse-monitor@pam --comment \"Pulse monitoring service\"" echo " pveum user add pulse-monitor@pam --comment \"Pulse monitoring service\""
echo " pveum aclmod / -user pulse-monitor@pam -role PVEAuditor" echo " pveum aclmod / -user pulse-monitor@pam -role PVEAuditor"
echo " pveum user token add pulse-monitor@pam %s --privsep 0" echo " pveum user token add pulse-monitor@pam "$TOKEN_NAME" --privsep 0"
echo "" echo ""
echo " 2. In Pulse: Settings → Nodes → Add Node (enter token from above)" echo " 2. In Pulse: Settings → Nodes → Add Node (enter token from above)"
echo "" echo ""
echo " 3. (Optional) For temperature monitoring on containerized Pulse:" echo " 3. (Optional) For temperature monitoring on containerized Pulse:"
echo "" echo ""
echo " For LXC containers, run on Proxmox host:" echo " For LXC containers, run on Proxmox host:"
echo " curl -sSL %s/api/install/install-sensor-proxy.sh | bash -s -- --ctid <CTID> --pulse-server %s" echo " curl -sSL $PULSE_URL/api/install/install-sensor-proxy.sh | bash -s -- --ctid <CTID> --pulse-server $PULSE_URL"
echo "" echo ""
echo " For Docker containers, run on Proxmox host:" echo " For Docker containers, run on Proxmox host:"
echo " curl -sSL %s/api/install/install-sensor-proxy.sh | bash -s -- --standalone --pulse-server %s" echo " curl -sSL $PULSE_URL/api/install/install-sensor-proxy.sh | bash -s -- --standalone --pulse-server $PULSE_URL"
echo " Then add to docker-compose.yml:" echo " Then add to docker-compose.yml:"
echo " volumes:" echo " volumes:"
echo " - /run/pulse-sensor-proxy:/run/pulse-sensor-proxy:rw" echo " - /run/pulse-sensor-proxy:/run/pulse-sensor-proxy:rw"
@ -3809,25 +3815,25 @@ echo "Generating API token..."
# Check if token already exists # Check if token already exists
TOKEN_EXISTED=false TOKEN_EXISTED=false
if pveum user token list pulse-monitor@pam 2>/dev/null | grep -q "%s"; then if pveum user token list pulse-monitor@pam 2>/dev/null | grep -q "$TOKEN_NAME"; then
TOKEN_EXISTED=true TOKEN_EXISTED=true
echo "" echo ""
echo "================================================================" echo "================================================================"
echo "WARNING: Token '%s' already exists!" echo "WARNING: Token '$TOKEN_NAME' already exists!"
echo "================================================================" echo "================================================================"
echo "" echo ""
echo "To create a new token, first remove the existing one:" echo "To create a new token, first remove the existing one:"
echo " pveum user token remove pulse-monitor@pam %s" echo " pveum user token remove pulse-monitor@pam $TOKEN_NAME"
echo "" echo ""
echo "Or create a token with a different name:" echo "Or create a token with a different name:"
echo " pveum user token add pulse-monitor@pam %s-$(date +%%s) --privsep 0" echo " pveum user token add pulse-monitor@pam ${TOKEN_NAME}-$(date +%%s) --privsep 0"
echo "" echo ""
echo "Then use the new token ID in Pulse (e.g., pulse-monitor@pam!%s-1234567890)" echo "Then use the new token ID in Pulse (e.g., ${PULSE_TOKEN_ID}-1234567890)"
echo "================================================================" echo "================================================================"
echo "" echo ""
else else
# Create token silently first # Create token silently first
TOKEN_OUTPUT=$(pveum user token add pulse-monitor@pam %s --privsep 0) TOKEN_OUTPUT=$(pveum user token add pulse-monitor@pam "$TOKEN_NAME" --privsep 0)
# Extract the token value for auto-registration # Extract the token value for auto-registration
TOKEN_VALUE=$(echo "$TOKEN_OUTPUT" | grep "│ value" | awk -F'│' '{print $3}' | tr -d ' ' | tail -1) TOKEN_VALUE=$(echo "$TOKEN_OUTPUT" | grep "│ value" | awk -F'│' '{print $3}' | tr -d ' ' | tail -1)
@ -3884,11 +3890,8 @@ else
SERVER_HOSTNAME=$(hostname -s 2>/dev/null || hostname) SERVER_HOSTNAME=$(hostname -s 2>/dev/null || hostname)
SERVER_IP=$(hostname -I | awk '{print $1}') SERVER_IP=$(hostname -I | awk '{print $1}')
# Send registration to Pulse
PULSE_URL="%s"
# Check if host URL was provided # Check if host URL was provided
HOST_URL="%s" HOST_URL="$SERVER_HOST"
if [ "$HOST_URL" = "https://YOUR_PROXMOX_HOST:8006" ] || [ -z "$HOST_URL" ]; then if [ "$HOST_URL" = "https://YOUR_PROXMOX_HOST:8006" ] || [ -z "$HOST_URL" ]; then
echo "" echo ""
echo "❌ ERROR: No Proxmox host URL provided!" echo "❌ ERROR: No Proxmox host URL provided!"
@ -3901,7 +3904,7 @@ else
echo " curl -sSL \"$PULSE_URL/api/setup-script?type=pve&host=https://192.168.0.5:8006&pulse_url=$PULSE_URL\" | bash" echo " curl -sSL \"$PULSE_URL/api/setup-script?type=pve&host=https://192.168.0.5:8006&pulse_url=$PULSE_URL\" | bash"
echo "" echo ""
echo "📝 For manual setup, use the token created above with:" echo "📝 For manual setup, use the token created above with:"
echo " Token ID: pulse-monitor@pam!%s" echo " Token ID: $PULSE_TOKEN_ID"
echo " Token Value: [See above]" echo " Token Value: [See above]"
echo "" echo ""
exit 1 exit 1
@ -3909,7 +3912,7 @@ else
# Construct registration request with setup code # Construct registration request with setup code
# Build JSON carefully to preserve the exclamation mark # Build JSON carefully to preserve the exclamation mark
REGISTER_JSON='{"type":"pve","host":"'"$HOST_URL"'","serverName":"'"$SERVER_HOSTNAME"'","tokenId":"pulse-monitor@pam!%s","tokenValue":"'"$TOKEN_VALUE"'","authToken":"'"$AUTH_TOKEN"'"}' REGISTER_JSON='{"type":"pve","host":"'"$HOST_URL"'","serverName":"'"$SERVER_HOSTNAME"'","tokenId":"'"$PULSE_TOKEN_ID"'","tokenValue":"'"$TOKEN_VALUE"'","authToken":"'"$AUTH_TOKEN"'"}'
# Send registration with setup code # Send registration with setup code
REGISTER_RESPONSE=$(echo "$REGISTER_JSON" | curl -s -X POST "$PULSE_URL/api/auto-register" \ REGISTER_RESPONSE=$(echo "$REGISTER_JSON" | curl -s -X POST "$PULSE_URL/api/auto-register" \
@ -4043,7 +4046,7 @@ SSH_SENSORS_KEY_ENTRY="command=\"sensors -j\",no-port-forwarding,no-X11-forwardi
TEMPERATURE_ENABLED=false TEMPERATURE_ENABLED=false
TEMP_MONITORING_AVAILABLE=true TEMP_MONITORING_AVAILABLE=true
MIN_PROXY_VERSION="%s" MIN_PROXY_VERSION="%s"
PULSE_VERSION_ENDPOINT="%s/api/version" PULSE_VERSION_ENDPOINT="$PULSE_URL/api/version"
STANDALONE_PROXY_DEPLOYED=false STANDALONE_PROXY_DEPLOYED=false
SKIP_TEMPERATURE_PROMPT=false SKIP_TEMPERATURE_PROMPT=false
PROXY_SOCKET_EXISTED_AT_START=false PROXY_SOCKET_EXISTED_AT_START=false
@ -4067,7 +4070,7 @@ version_ge() {
} }
# Check if temperature proxy is available and override SSH key if it is # Check if temperature proxy is available and override SSH key if it is
PROXY_KEY_URL="%s/api/system/proxy-public-key" PROXY_KEY_URL="$PULSE_URL/api/system/proxy-public-key"
TEMPERATURE_PROXY_KEY=$(curl -s -f "$PROXY_KEY_URL" 2>/dev/null || echo "") TEMPERATURE_PROXY_KEY=$(curl -s -f "$PROXY_KEY_URL" 2>/dev/null || echo "")
if [ -n "$TEMPERATURE_PROXY_KEY" ] && [[ "$TEMPERATURE_PROXY_KEY" =~ ^ssh-(rsa|ed25519) ]]; then if [ -n "$TEMPERATURE_PROXY_KEY" ] && [[ "$TEMPERATURE_PROXY_KEY" =~ ^ssh-(rsa|ed25519) ]]; then
# Proxy is available - use its key instead of container's key # Proxy is available - use its key instead of container's key
@ -4080,7 +4083,7 @@ PULSE_CTID=""
PULSE_IS_CONTAINERIZED=false PULSE_IS_CONTAINERIZED=false
if command -v pct >/dev/null 2>&1; then if command -v pct >/dev/null 2>&1; then
# Extract Pulse IP from URL # Extract Pulse IP from URL
PULSE_IP=$(echo "%s" | sed -E 's|^https?://([^:/]+).*|\1|') PULSE_IP=$(echo "$PULSE_URL" | sed -E 's|^https?://([^:/]+).*|\1|')
# Find container with this IP # Find container with this IP
if [[ "$PULSE_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then if [[ "$PULSE_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
@ -4199,7 +4202,7 @@ if [ "$TEMP_MONITORING_AVAILABLE" = true ] && [ "$PULSE_IS_CONTAINERIZED" = true
if true; then if true; then
# Download installer script from Pulse server # Download installer script from Pulse server
PROXY_INSTALLER="/tmp/install-sensor-proxy-$$.sh" PROXY_INSTALLER="/tmp/install-sensor-proxy-$$.sh"
INSTALLER_URL="%s/api/install/install-sensor-proxy.sh" INSTALLER_URL="$PULSE_URL/api/install/install-sensor-proxy.sh"
echo "Installing pulse-sensor-proxy..." echo "Installing pulse-sensor-proxy..."
if curl --fail --silent --location \ if curl --fail --silent --location \
@ -4208,7 +4211,7 @@ if [ "$TEMP_MONITORING_AVAILABLE" = true ] && [ "$PULSE_IS_CONTAINERIZED" = true
chmod +x "$PROXY_INSTALLER" chmod +x "$PROXY_INSTALLER"
# Run installer with Pulse server as fallback # Run installer with Pulse server as fallback
INSTALL_OUTPUT=$("$PROXY_INSTALLER" --ctid "$PULSE_CTID" --pulse-server "%s" 2>&1) INSTALL_OUTPUT=$("$PROXY_INSTALLER" --ctid "$PULSE_CTID" --pulse-server "$PULSE_URL" 2>&1)
INSTALL_STATUS=$? INSTALL_STATUS=$?
if [ -n "$INSTALL_OUTPUT" ]; then if [ -n "$INSTALL_OUTPUT" ]; then
@ -4311,7 +4314,7 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then
# Download and run installer to refresh config # Download and run installer to refresh config
PROXY_INSTALLER="/tmp/install-sensor-proxy-repair-$$.sh" PROXY_INSTALLER="/tmp/install-sensor-proxy-repair-$$.sh"
INSTALLER_URL="%s/api/install/install-sensor-proxy.sh" INSTALLER_URL="$PULSE_URL/api/install/install-sensor-proxy.sh"
if curl --fail --silent --location "$INSTALLER_URL" -o "$PROXY_INSTALLER" 2>/dev/null; then if curl --fail --silent --location "$INSTALLER_URL" -o "$PROXY_INSTALLER" 2>/dev/null; then
chmod +x "$PROXY_INSTALLER" chmod +x "$PROXY_INSTALLER"
@ -4331,10 +4334,10 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then
if [ -n "$DETECTED_CTID" ]; then if [ -n "$DETECTED_CTID" ]; then
# Was deployed for containerized Pulse - use --ctid mode # Was deployed for containerized Pulse - use --ctid mode
INSTALLER_ARGS="--ctid $DETECTED_CTID --pulse-server %s" INSTALLER_ARGS="--ctid $DETECTED_CTID --pulse-server $PULSE_URL"
elif [ "$IS_STANDALONE_NODE" = true ]; then elif [ "$IS_STANDALONE_NODE" = true ]; then
# Standalone node - use --standalone --http-mode # Standalone node - use --standalone --http-mode
INSTALLER_ARGS="--standalone --http-mode --pulse-server %s" INSTALLER_ARGS="--standalone --http-mode --pulse-server $PULSE_URL"
else else
# Cannot determine deployment type - bail out # Cannot determine deployment type - bail out
echo "" echo ""
@ -4342,10 +4345,10 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then
echo " Manual repair required. Run one of:" echo " Manual repair required. Run one of:"
echo "" echo ""
echo " • For container:" echo " • For container:"
echo " curl -fsSL %s/api/install/install-sensor-proxy.sh | bash -s -- --ctid <CTID> --pulse-server %s" echo " curl -fsSL $PULSE_URL/api/install/install-sensor-proxy.sh | bash -s -- --ctid <CTID> --pulse-server $PULSE_URL"
echo "" echo ""
echo " • For standalone:" echo " • For standalone:"
echo " curl -fsSL %s/api/install/install-sensor-proxy.sh | bash -s -- --standalone --http-mode --pulse-server %s" echo " curl -fsSL $PULSE_URL/api/install/install-sensor-proxy.sh | bash -s -- --standalone --http-mode --pulse-server $PULSE_URL"
echo "" echo ""
echo " Keeping existing proxy configuration" echo " Keeping existing proxy configuration"
rm -f "$PROXY_INSTALLER" rm -f "$PROXY_INSTALLER"
@ -4353,7 +4356,15 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then
fi fi
if [ -n "$INSTALLER_ARGS" ]; then if [ -n "$INSTALLER_ARGS" ]; then
# Run installer and capture output for diagnostics # Check if service is already running
if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then
echo "✓ pulse-sensor-proxy service is already running"
echo " Refreshing configuration and token..."
echo ""
fi
# Run installer to refresh config and restart service
# The installer handles stopping/restarting on its own
INSTALL_OUTPUT=$("$PROXY_INSTALLER" $INSTALLER_ARGS 2>&1) INSTALL_OUTPUT=$("$PROXY_INSTALLER" $INSTALLER_ARGS 2>&1)
REPAIR_STATUS=$? REPAIR_STATUS=$?
@ -4486,7 +4497,7 @@ elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then
# SECURITY: Block SSH-based temperature monitoring for containerized Pulse (unless dev mode) # SECURITY: Block SSH-based temperature monitoring for containerized Pulse (unless dev mode)
if [ "$PULSE_IS_CONTAINERIZED" = true ]; then if [ "$PULSE_IS_CONTAINERIZED" = true ]; then
# Check for dev mode override (from Pulse server environment) # Check for dev mode override (from Pulse server environment)
DEV_MODE_RESPONSE=$(curl -s "%s/api/health" 2>/dev/null | grep -o '"devModeSSH"[[:space:]]*:[[:space:]]*true' || echo "") DEV_MODE_RESPONSE=$(curl -s "$PULSE_URL/api/health" 2>/dev/null | grep -o '"devModeSSH"[[:space:]]*:[[:space:]]*true' || echo "")
if [ -n "$DEV_MODE_RESPONSE" ]; then if [ -n "$DEV_MODE_RESPONSE" ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@ -4743,7 +4754,7 @@ Host ${NODE}
# Write SSH config to Pulse container # Write SSH config to Pulse container
# This will be written to /home/pulse/.ssh/config inside the container # This will be written to /home/pulse/.ssh/config inside the container
echo "$SSH_CONFIG" | curl -s -X POST "%s/api/system/ssh-config" \ echo "$SSH_CONFIG" | curl -s -X POST "$PULSE_URL/api/system/ssh-config" \
-H "Content-Type: text/plain" \ -H "Content-Type: text/plain" \
-H "Authorization: Bearer $AUTH_TOKEN" \ -H "Authorization: Bearer $AUTH_TOKEN" \
--data-binary @- > /dev/null 2>&1 --data-binary @- > /dev/null 2>&1
@ -4932,7 +4943,7 @@ EOF
CONFIGURED_NODES="$(hostname) ${CONFIGURED_NODES}" CONFIGURED_NODES="$(hostname) ${CONFIGURED_NODES}"
fi fi
VERIFY_RESPONSE=$(curl -s -X POST "%s/api/system/verify-temperature-ssh" \ VERIFY_RESPONSE=$(curl -s -X POST "$PULSE_URL/api/system/verify-temperature-ssh" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: Bearer $AUTH_TOKEN" \ -H "Authorization: Bearer $AUTH_TOKEN" \
-d "{\"nodes\": \"$CONFIGURED_NODES\"}" 2>/dev/null || echo "") -d "{\"nodes\": \"$CONFIGURED_NODES\"}" 2>/dev/null || echo "")
@ -4975,7 +4986,7 @@ if [ "$IS_STANDALONE_NODE" = true ] && [ "$TEMPERATURE_ENABLED" = true ] && [ "$
echo "" echo ""
# Try to fetch the proxy's public key from Pulse server # Try to fetch the proxy's public key from Pulse server
PROXY_KEY_URL="%s/api/system/proxy-public-key" PROXY_KEY_URL="$PULSE_URL/api/system/proxy-public-key"
echo "Fetching temperature proxy public key..." echo "Fetching temperature proxy public key..."
PROXY_PUBLIC_KEY=$(curl -s -f "$PROXY_KEY_URL" 2>/dev/null || echo "") PROXY_PUBLIC_KEY=$(curl -s -f "$PROXY_KEY_URL" 2>/dev/null || echo "")
@ -5035,7 +5046,7 @@ echo ""
# Only show manual setup instructions if auto-registration failed # Only show manual setup instructions if auto-registration failed
if [ "$AUTO_REG_SUCCESS" != true ]; then if [ "$AUTO_REG_SUCCESS" != true ]; then
echo "Manual setup instructions:" echo "Manual setup instructions:"
echo " Token ID: pulse-monitor@pam!%s" echo " Token ID: $PULSE_TOKEN_ID"
if [ "$TOKEN_EXISTED" = true ]; then if [ "$TOKEN_EXISTED" = true ]; then
echo " Token Value: [Use your existing token or create a new one as shown above]" echo " Token Value: [Use your existing token or create a new one as shown above]"
elif [ -n "$TOKEN_VALUE" ]; then elif [ -n "$TOKEN_VALUE" ]; then
@ -5047,16 +5058,11 @@ if [ "$AUTO_REG_SUCCESS" != true ]; then
echo "" echo ""
fi fi
`, serverName, time.Now().Format("2006-01-02 15:04:05"), `, serverName, time.Now().Format("2006-01-02 15:04:05"),
tokenName, pulseURL, pulseURL, pulseURL, serverHost, tokenName,
pulseURL, serverHost, pulseURL,
pulseURL, serverHost, pulseURL,
tokenName, pulseURL, pulseURL,
pulseURL, pulseURL,
pulseIP, pulseIP,
tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, authToken,
authToken, pulseURL, serverHost, tokenName, tokenName, storagePerms, storagePerms,
sshKeys.ProxyPublicKey, sshKeys.SensorsPublicKey, minProxyReadyVersion, sshKeys.ProxyPublicKey, sshKeys.SensorsPublicKey, minProxyReadyVersion)
pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, tokenName)
} else { // PBS } else { // PBS
script = fmt.Sprintf(`#!/bin/bash script = fmt.Sprintf(`#!/bin/bash

View file

@ -69,6 +69,7 @@ func TestPVESetupScriptArgumentAlignment(t *testing.T) {
script := rr.Body.String() script := rr.Body.String()
// Critical alignment checks to prevent fmt.Sprintf argument mismatch bugs // Critical alignment checks to prevent fmt.Sprintf argument mismatch bugs
// After refactor: script uses bash variables ($PULSE_URL, $TOKEN_NAME) instead of fmt.Sprintf substitutions
tests := []struct { tests := []struct {
name string name string
contains string contains string
@ -76,13 +77,13 @@ func TestPVESetupScriptArgumentAlignment(t *testing.T) {
}{ }{
{ {
name: "repair_installer_url", name: "repair_installer_url",
contains: `INSTALLER_URL="http://SENTINEL_URL:7656/api/install/install-sensor-proxy.sh"`, contains: `INSTALLER_URL="$PULSE_URL/api/install/install-sensor-proxy.sh"`,
desc: "Repair block INSTALLER_URL should get pulseURL, not authToken", desc: "Repair block INSTALLER_URL should use $PULSE_URL bash variable",
}, },
{ {
name: "repair_ctid_pulse_server", name: "repair_ctid_pulse_server",
contains: `--pulse-server http://SENTINEL_URL:7656`, contains: `--pulse-server $PULSE_URL`,
desc: "Repair --ctid --pulse-server should get pulseURL, not authToken", desc: "Repair --ctid --pulse-server should use $PULSE_URL bash variable",
}, },
{ {
name: "runtime_auth_token_ssh_config", name: "runtime_auth_token_ssh_config",
@ -91,8 +92,18 @@ func TestPVESetupScriptArgumentAlignment(t *testing.T) {
}, },
{ {
name: "token_id_uses_tokenname", name: "token_id_uses_tokenname",
contains: `Token ID: pulse-monitor@pam!pulse-`, contains: `Token ID: $PULSE_TOKEN_ID`,
desc: "Token ID should use tokenName (pulse-*), not pulseURL or authToken", desc: "Token ID should use $PULSE_TOKEN_ID bash variable",
},
{
name: "bash_variables_defined",
contains: `PULSE_URL="http://SENTINEL_URL:7656"`,
desc: "Bash variable PULSE_URL should be defined at top of script",
},
{
name: "token_name_variable_defined",
contains: `TOKEN_NAME="pulse-SENTINEL_URL-`,
desc: "Bash variable TOKEN_NAME should be defined with correct format",
}, },
} }

View file

@ -173,7 +173,7 @@ func (r *Router) setupRoutes() {
updateHandlers := NewUpdateHandlers(r.updateManager, r.updateHistory) updateHandlers := NewUpdateHandlers(r.updateManager, r.updateHistory)
r.dockerAgentHandlers = NewDockerAgentHandlers(r.monitor, r.wsHub) r.dockerAgentHandlers = NewDockerAgentHandlers(r.monitor, r.wsHub)
r.hostAgentHandlers = NewHostAgentHandlers(r.monitor, r.wsHub) r.hostAgentHandlers = NewHostAgentHandlers(r.monitor, r.wsHub)
r.temperatureProxyHandlers = NewTemperatureProxyHandlers(r.persistence) r.temperatureProxyHandlers = NewTemperatureProxyHandlers(r.config, r.persistence, r.reloadFunc)
// API routes // API routes
r.mux.HandleFunc("/api/health", r.handleHealth) r.mux.HandleFunc("/api/health", r.handleHealth)
@ -1208,6 +1208,9 @@ func (r *Router) SetConfig(cfg *config.Config) {
if r.systemSettingsHandler != nil { if r.systemSettingsHandler != nil {
r.systemSettingsHandler.SetConfig(r.config) r.systemSettingsHandler.SetConfig(r.config)
} }
if r.temperatureProxyHandlers != nil {
r.temperatureProxyHandlers.SetConfig(r.config)
}
} }
// reloadSystemSettings loads system settings from disk and caches them // reloadSystemSettings loads system settings from disk and caches them

View file

@ -22,7 +22,9 @@ import (
// TemperatureProxyHandlers manages temperature proxy registration // TemperatureProxyHandlers manages temperature proxy registration
type TemperatureProxyHandlers struct { type TemperatureProxyHandlers struct {
config *config.Config
persistence *config.ConfigPersistence persistence *config.ConfigPersistence
reloadFunc func() error
syncMu sync.RWMutex syncMu sync.RWMutex
syncStatus map[string]proxySyncState syncStatus map[string]proxySyncState
} }
@ -41,13 +43,22 @@ type authorizedNode struct {
} }
// NewTemperatureProxyHandlers constructs a new handler set for temperature proxy // NewTemperatureProxyHandlers constructs a new handler set for temperature proxy
func NewTemperatureProxyHandlers(persistence *config.ConfigPersistence) *TemperatureProxyHandlers { func NewTemperatureProxyHandlers(cfg *config.Config, persistence *config.ConfigPersistence, reloadFunc func() error) *TemperatureProxyHandlers {
return &TemperatureProxyHandlers{ return &TemperatureProxyHandlers{
config: cfg,
persistence: persistence, persistence: persistence,
reloadFunc: reloadFunc,
syncStatus: make(map[string]proxySyncState), syncStatus: make(map[string]proxySyncState),
} }
} }
// SetConfig updates the configuration reference used by the handler.
func (h *TemperatureProxyHandlers) SetConfig(cfg *config.Config) {
if h != nil {
h.config = cfg
}
}
func (h *TemperatureProxyHandlers) recordSync(instance string, refreshSeconds int) { func (h *TemperatureProxyHandlers) recordSync(instance string, refreshSeconds int) {
if h == nil { if h == nil {
return return
@ -278,11 +289,28 @@ func (h *TemperatureProxyHandlers) HandleRegister(w http.ResponseWriter, r *http
nodesConfig.PVEInstances[matchedIndex].TemperatureProxyControlToken = ctrlToken nodesConfig.PVEInstances[matchedIndex].TemperatureProxyControlToken = ctrlToken
// Save updated configuration // Save updated configuration
log.Debug().
Int("matchedIndex", matchedIndex).
Str("saving_url", nodesConfig.PVEInstances[matchedIndex].TemperatureProxyURL).
Bool("saving_has_token", nodesConfig.PVEInstances[matchedIndex].TemperatureProxyToken != "").
Str("instance_name", nodesConfig.PVEInstances[matchedIndex].Name).
Msg("About to save nodes config with proxy registration")
if err := h.persistence.SaveNodesConfig(nodesConfig.PVEInstances, nodesConfig.PBSInstances, nodesConfig.PMGInstances); err != nil { if err := h.persistence.SaveNodesConfig(nodesConfig.PVEInstances, nodesConfig.PBSInstances, nodesConfig.PMGInstances); err != nil {
writeErrorResponse(w, http.StatusInternalServerError, "config_save_failed", "Failed to save configuration", map[string]string{"error": err.Error()}) writeErrorResponse(w, http.StatusInternalServerError, "config_save_failed", "Failed to save configuration", map[string]string{"error": err.Error()})
return return
} }
// Reload the entire config to ensure all components (router, monitor, handlers) get the fresh config
// This prevents the monitor from later overwriting nodes.enc with stale data
if h.reloadFunc != nil {
if err := h.reloadFunc(); err != nil {
log.Error().Err(err).Msg("Failed to reload config after temperature proxy registration")
// Don't fail the request - the save succeeded, reload is best-effort
} else {
log.Info().Str("instance", matchedInstance.Name).Msg("Config reloaded after temperature proxy registration")
}
}
log.Info(). log.Info().
Str("hostname", hostname). Str("hostname", hostname).
Str("proxy_url", proxyURL). Str("proxy_url", proxyURL).

View file

@ -1738,17 +1738,35 @@ if [[ "$HTTP_MODE" == true ]]; then
# Check if port is already in use # Check if port is already in use
PORT_NUMBER="${HTTP_ADDR#:}" PORT_NUMBER="${HTTP_ADDR#:}"
if ss -ltn | grep -q ":${PORT_NUMBER} "; then if ss -ltn | grep -q ":${PORT_NUMBER} "; then
print_error "Port ${PORT_NUMBER} is already in use" # Port is in use - check if it's our own service (refresh scenario)
print_error "" if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then
print_error "Currently using port ${PORT_NUMBER}:" # Check if the process using the port is pulse-sensor-proxy
ss -ltnp | grep ":${PORT_NUMBER} " || true PORT_OWNER=$(ss -ltnp | grep ":${PORT_NUMBER} " | grep -o 'pulse-sensor-pr' || true)
print_error "" if [[ -n "$PORT_OWNER" ]]; then
print_error "Options:" # Our service is using the port - this is a refresh, continue
print_error " 1. Stop the conflicting service" print_info "Existing pulse-sensor-proxy detected on port ${PORT_NUMBER} - will refresh configuration"
print_error " 2. Use a different port: --http-addr :PORT" else
print_error " 3. If this is a previous sensor-proxy, uninstall first:" # Service is active but something else is using the port
print_error " $0 --uninstall" print_error "Port ${PORT_NUMBER} is already in use by another process"
exit 1 print_error ""
print_error "Currently using port ${PORT_NUMBER}:"
ss -ltnp | grep ":${PORT_NUMBER} " || true
exit 1
fi
else
# Service not active, port conflict with something else
print_error "Port ${PORT_NUMBER} is already in use"
print_error ""
print_error "Currently using port ${PORT_NUMBER}:"
ss -ltnp | grep ":${PORT_NUMBER} " || true
print_error ""
print_error "Options:"
print_error " 1. Stop the conflicting service"
print_error " 2. Use a different port: --http-addr :PORT"
print_error " 3. If this is a previous sensor-proxy, uninstall first:"
print_error " $0 --uninstall"
exit 1
fi
fi fi
# Setup TLS certificates # Setup TLS certificates
@ -1814,12 +1832,19 @@ if [[ "$HTTP_MODE" == true ]]; then
chmod 600 /etc/pulse-sensor-proxy/.http-auth-token chmod 600 /etc/pulse-sensor-proxy/.http-auth-token
chown pulse-sensor-proxy:pulse-sensor-proxy /etc/pulse-sensor-proxy/.http-auth-token chown pulse-sensor-proxy:pulse-sensor-proxy /etc/pulse-sensor-proxy/.http-auth-token
# Backup config before modifying # Backup config and token files before modifying
if [[ -f /etc/pulse-sensor-proxy/config.yaml ]]; then if [[ -f /etc/pulse-sensor-proxy/config.yaml ]]; then
BACKUP_CONFIG="/etc/pulse-sensor-proxy/config.yaml.backup.$(date +%s)" BACKUP_TIMESTAMP="$(date +%s)"
BACKUP_CONFIG="/etc/pulse-sensor-proxy/config.yaml.backup.$BACKUP_TIMESTAMP"
cp /etc/pulse-sensor-proxy/config.yaml "$BACKUP_CONFIG" cp /etc/pulse-sensor-proxy/config.yaml "$BACKUP_CONFIG"
print_info "Config backed up to: $BACKUP_CONFIG" print_info "Config backed up to: $BACKUP_CONFIG"
# Also backup token files so rollback restores matching secrets
if [[ -f /etc/pulse-sensor-proxy/.pulse-control-token ]]; then
BACKUP_CONTROL_TOKEN="/etc/pulse-sensor-proxy/.pulse-control-token.backup.$BACKUP_TIMESTAMP"
cp /etc/pulse-sensor-proxy/.pulse-control-token "$BACKUP_CONTROL_TOKEN"
fi
# Remove any existing HTTP configuration to prevent duplicates # Remove any existing HTTP configuration to prevent duplicates
if grep -q "^# HTTP Mode Configuration" /etc/pulse-sensor-proxy/config.yaml; then if grep -q "^# HTTP Mode Configuration" /etc/pulse-sensor-proxy/config.yaml; then
print_info "Removing existing HTTP configuration..." print_info "Removing existing HTTP configuration..."
@ -1841,9 +1866,15 @@ if [[ "$HTTP_MODE" == true ]]; then
print_info "Pulse server detected at: $PULSE_IP" print_info "Pulse server detected at: $PULSE_IP"
# Append HTTP configuration to config.yaml # Configure HTTP mode - check if already configured to avoid duplicates
print_info "Configuring HTTP mode..." print_info "Configuring HTTP mode..."
cat >> /etc/pulse-sensor-proxy/config.yaml << EOF if grep -q "^http_enabled:" /etc/pulse-sensor-proxy/config.yaml 2>/dev/null; then
# HTTP mode already configured - only update the token (avoid duplicates)
sed -i "s|^http_auth_token:.*|http_auth_token: $HTTP_AUTH_TOKEN|" /etc/pulse-sensor-proxy/config.yaml
print_info "Updated HTTP auth token (existing HTTP mode configuration kept)"
else
# Fresh HTTP mode configuration - append to file
cat >> /etc/pulse-sensor-proxy/config.yaml << EOF
# HTTP Mode Configuration (External PVE Host) # HTTP Mode Configuration (External PVE Host)
http_enabled: true http_enabled: true
@ -1857,6 +1888,7 @@ allowed_source_subnets:
- $PULSE_IP/32 - $PULSE_IP/32
- 127.0.0.1/32 - 127.0.0.1/32
EOF EOF
fi
chown pulse-sensor-proxy:pulse-sensor-proxy /etc/pulse-sensor-proxy/config.yaml chown pulse-sensor-proxy:pulse-sensor-proxy /etc/pulse-sensor-proxy/config.yaml
chmod 0644 /etc/pulse-sensor-proxy/config.yaml chmod 0644 /etc/pulse-sensor-proxy/config.yaml
@ -1876,7 +1908,10 @@ fi
# Stop existing service if running (for upgrades) # Stop existing service if running (for upgrades)
if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then
print_info "Stopping existing service for upgrade..." print_info "Stopping existing service for upgrade..."
systemctl stop pulse-sensor-proxy # Tolerate timeout from slow HTTPS shutdown (can take 30s)
systemctl stop pulse-sensor-proxy || true
# Clear any failed state from the stop
systemctl reset-failed pulse-sensor-proxy 2>/dev/null || true
fi fi
# Install hardened systemd service # Install hardened systemd service
@ -2031,7 +2066,7 @@ if ! systemctl enable pulse-sensor-proxy.service; then
exit 1 exit 1
fi fi
if ! systemctl restart pulse-sensor-proxy.service; then if ! systemctl start pulse-sensor-proxy.service; then
print_error "Failed to start pulse-sensor-proxy service" print_error "Failed to start pulse-sensor-proxy service"
print_error "" print_error ""
@ -2040,7 +2075,14 @@ if ! systemctl restart pulse-sensor-proxy.service; then
print_warn "Attempting to rollback to previous configuration..." print_warn "Attempting to rollback to previous configuration..."
if cp "$BACKUP_CONFIG" /etc/pulse-sensor-proxy/config.yaml; then if cp "$BACKUP_CONFIG" /etc/pulse-sensor-proxy/config.yaml; then
print_info "Config restored from backup" print_info "Config restored from backup"
if systemctl restart pulse-sensor-proxy.service; then # Also restore token files to match the old config
if [[ -n "$BACKUP_CONTROL_TOKEN" && -f "$BACKUP_CONTROL_TOKEN" ]]; then
cp "$BACKUP_CONTROL_TOKEN" /etc/pulse-sensor-proxy/.pulse-control-token
print_info "Control plane token restored from backup"
fi
# Clear failed state before attempting rollback start
systemctl reset-failed pulse-sensor-proxy 2>/dev/null || true
if systemctl start pulse-sensor-proxy.service; then
print_success "Service restarted with previous configuration" print_success "Service restarted with previous configuration"
print_error "" print_error ""
print_error "HTTP mode installation failed but previous config restored" print_error "HTTP mode installation failed but previous config restored"