From 7d0bbaf961908ec60ae4f9bbe6a8069a4c77dd97 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 19 Nov 2025 20:12:19 +0000 Subject: [PATCH] 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 --- internal/api/config_handlers.go | 94 ++++++++++--------- .../api/config_handlers_setup_script_test.go | 23 +++-- internal/api/router.go | 5 +- internal/api/temperature_proxy.go | 30 +++++- scripts/install-sensor-proxy.sh | 78 +++++++++++---- 5 files changed, 160 insertions(+), 70 deletions(-) diff --git a/internal/api/config_handlers.go b/internal/api/config_handlers.go index 9faa80b48..12ea289ec 100644 --- a/internal/api/config_handlers.go +++ b/internal/api/config_handlers.go @@ -3418,6 +3418,12 @@ echo " Pulse Monitoring Setup for Proxmox VE" 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 if [ "$EUID" -ne 0 ]; then echo "Please run this script as root" @@ -3497,10 +3503,10 @@ case "$ENVIRONMENT" in # 1) Create or reuse the Pulse monitoring API token pveum user add pulse-monitor@pam --comment "Pulse monitoring service" 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 -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 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 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 "Exiting without error. Re-run after completing the host steps." exit 0 @@ -3519,23 +3525,23 @@ EOF echo "This script requires Proxmox host tooling (pveum)." echo "" 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 "Manual setup steps:" echo " 1. On Proxmox host, create API token:" echo " pveum user add pulse-monitor@pam --comment \"Pulse monitoring service\"" 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 " 2. In Pulse: Settings → Nodes → Add Node (enter token from above)" echo "" echo " 3. (Optional) For temperature monitoring on containerized Pulse:" echo "" echo " For LXC containers, run on Proxmox host:" - echo " curl -sSL %s/api/install/install-sensor-proxy.sh | bash -s -- --ctid --pulse-server %s" + echo " curl -sSL $PULSE_URL/api/install/install-sensor-proxy.sh | bash -s -- --ctid --pulse-server $PULSE_URL" echo "" 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 " volumes:" echo " - /run/pulse-sensor-proxy:/run/pulse-sensor-proxy:rw" @@ -3809,25 +3815,25 @@ echo "Generating API token..." # Check if token already exists 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 echo "" echo "================================================================" - echo "WARNING: Token '%s' already exists!" + echo "WARNING: Token '$TOKEN_NAME' already exists!" echo "================================================================" echo "" 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 "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 "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 "" else # 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 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_IP=$(hostname -I | awk '{print $1}') - # Send registration to Pulse - PULSE_URL="%s" - # 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 echo "" 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 "" 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 "" exit 1 @@ -3909,7 +3912,7 @@ else # Construct registration request with setup code # 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 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 TEMP_MONITORING_AVAILABLE=true MIN_PROXY_VERSION="%s" -PULSE_VERSION_ENDPOINT="%s/api/version" +PULSE_VERSION_ENDPOINT="$PULSE_URL/api/version" STANDALONE_PROXY_DEPLOYED=false SKIP_TEMPERATURE_PROMPT=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 -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 "") if [ -n "$TEMPERATURE_PROXY_KEY" ] && [[ "$TEMPERATURE_PROXY_KEY" =~ ^ssh-(rsa|ed25519) ]]; then # Proxy is available - use its key instead of container's key @@ -4080,7 +4083,7 @@ PULSE_CTID="" PULSE_IS_CONTAINERIZED=false if command -v pct >/dev/null 2>&1; then # 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 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 # Download installer script from Pulse server 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..." if curl --fail --silent --location \ @@ -4208,7 +4211,7 @@ if [ "$TEMP_MONITORING_AVAILABLE" = true ] && [ "$PULSE_IS_CONTAINERIZED" = true chmod +x "$PROXY_INSTALLER" # 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=$? if [ -n "$INSTALL_OUTPUT" ]; then @@ -4311,7 +4314,7 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then # Download and run installer to refresh config 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 chmod +x "$PROXY_INSTALLER" @@ -4331,10 +4334,10 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then if [ -n "$DETECTED_CTID" ]; then # 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 # Standalone node - use --standalone --http-mode - INSTALLER_ARGS="--standalone --http-mode --pulse-server %s" + INSTALLER_ARGS="--standalone --http-mode --pulse-server $PULSE_URL" else # Cannot determine deployment type - bail out echo "" @@ -4342,10 +4345,10 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then echo " Manual repair required. Run one of:" echo "" echo " • For container:" - echo " curl -fsSL %s/api/install/install-sensor-proxy.sh | bash -s -- --ctid --pulse-server %s" + echo " curl -fsSL $PULSE_URL/api/install/install-sensor-proxy.sh | bash -s -- --ctid --pulse-server $PULSE_URL" echo "" 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 " Keeping existing proxy configuration" rm -f "$PROXY_INSTALLER" @@ -4353,7 +4356,15 @@ if [ "$SKIP_TEMPERATURE_PROMPT" = true ]; then fi 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) REPAIR_STATUS=$? @@ -4486,7 +4497,7 @@ elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then # SECURITY: Block SSH-based temperature monitoring for containerized Pulse (unless dev mode) if [ "$PULSE_IS_CONTAINERIZED" = true ]; then # 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 echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @@ -4743,7 +4754,7 @@ Host ${NODE} # Write SSH config to Pulse 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 "Authorization: Bearer $AUTH_TOKEN" \ --data-binary @- > /dev/null 2>&1 @@ -4932,7 +4943,7 @@ EOF CONFIGURED_NODES="$(hostname) ${CONFIGURED_NODES}" 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 "Authorization: Bearer $AUTH_TOKEN" \ -d "{\"nodes\": \"$CONFIGURED_NODES\"}" 2>/dev/null || echo "") @@ -4975,7 +4986,7 @@ if [ "$IS_STANDALONE_NODE" = true ] && [ "$TEMPERATURE_ENABLED" = true ] && [ "$ echo "" # 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..." 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 if [ "$AUTO_REG_SUCCESS" != true ]; then echo "Manual setup instructions:" - echo " Token ID: pulse-monitor@pam!%s" + echo " Token ID: $PULSE_TOKEN_ID" if [ "$TOKEN_EXISTED" = true ]; then echo " Token Value: [Use your existing token or create a new one as shown above]" elif [ -n "$TOKEN_VALUE" ]; then @@ -5047,16 +5058,11 @@ if [ "$AUTO_REG_SUCCESS" != true ]; then echo "" fi `, serverName, time.Now().Format("2006-01-02 15:04:05"), - tokenName, pulseURL, pulseURL, - pulseURL, serverHost, pulseURL, - pulseURL, serverHost, pulseURL, - tokenName, pulseURL, pulseURL, - pulseURL, pulseURL, + pulseURL, serverHost, tokenName, pulseIP, - tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, - authToken, pulseURL, serverHost, tokenName, tokenName, storagePerms, - sshKeys.ProxyPublicKey, sshKeys.SensorsPublicKey, minProxyReadyVersion, - pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, pulseURL, tokenName) + authToken, + storagePerms, + sshKeys.ProxyPublicKey, sshKeys.SensorsPublicKey, minProxyReadyVersion) } else { // PBS script = fmt.Sprintf(`#!/bin/bash diff --git a/internal/api/config_handlers_setup_script_test.go b/internal/api/config_handlers_setup_script_test.go index 2b657b3e2..3b555288f 100644 --- a/internal/api/config_handlers_setup_script_test.go +++ b/internal/api/config_handlers_setup_script_test.go @@ -69,6 +69,7 @@ func TestPVESetupScriptArgumentAlignment(t *testing.T) { script := rr.Body.String() // 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 { name string contains string @@ -76,13 +77,13 @@ func TestPVESetupScriptArgumentAlignment(t *testing.T) { }{ { name: "repair_installer_url", - contains: `INSTALLER_URL="http://SENTINEL_URL:7656/api/install/install-sensor-proxy.sh"`, - desc: "Repair block INSTALLER_URL should get pulseURL, not authToken", + contains: `INSTALLER_URL="$PULSE_URL/api/install/install-sensor-proxy.sh"`, + desc: "Repair block INSTALLER_URL should use $PULSE_URL bash variable", }, { name: "repair_ctid_pulse_server", - contains: `--pulse-server http://SENTINEL_URL:7656`, - desc: "Repair --ctid --pulse-server should get pulseURL, not authToken", + contains: `--pulse-server $PULSE_URL`, + desc: "Repair --ctid --pulse-server should use $PULSE_URL bash variable", }, { name: "runtime_auth_token_ssh_config", @@ -91,8 +92,18 @@ func TestPVESetupScriptArgumentAlignment(t *testing.T) { }, { name: "token_id_uses_tokenname", - contains: `Token ID: pulse-monitor@pam!pulse-`, - desc: "Token ID should use tokenName (pulse-*), not pulseURL or authToken", + contains: `Token ID: $PULSE_TOKEN_ID`, + 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", }, } diff --git a/internal/api/router.go b/internal/api/router.go index 1c8626691..359e527d6 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -173,7 +173,7 @@ func (r *Router) setupRoutes() { updateHandlers := NewUpdateHandlers(r.updateManager, r.updateHistory) r.dockerAgentHandlers = NewDockerAgentHandlers(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 r.mux.HandleFunc("/api/health", r.handleHealth) @@ -1208,6 +1208,9 @@ func (r *Router) SetConfig(cfg *config.Config) { if r.systemSettingsHandler != nil { r.systemSettingsHandler.SetConfig(r.config) } + if r.temperatureProxyHandlers != nil { + r.temperatureProxyHandlers.SetConfig(r.config) + } } // reloadSystemSettings loads system settings from disk and caches them diff --git a/internal/api/temperature_proxy.go b/internal/api/temperature_proxy.go index aff024438..f2b639f64 100644 --- a/internal/api/temperature_proxy.go +++ b/internal/api/temperature_proxy.go @@ -22,7 +22,9 @@ import ( // TemperatureProxyHandlers manages temperature proxy registration type TemperatureProxyHandlers struct { + config *config.Config persistence *config.ConfigPersistence + reloadFunc func() error syncMu sync.RWMutex syncStatus map[string]proxySyncState } @@ -41,13 +43,22 @@ type authorizedNode struct { } // 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{ + config: cfg, persistence: persistence, + reloadFunc: reloadFunc, 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) { if h == nil { return @@ -278,11 +289,28 @@ func (h *TemperatureProxyHandlers) HandleRegister(w http.ResponseWriter, r *http nodesConfig.PVEInstances[matchedIndex].TemperatureProxyControlToken = ctrlToken // 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 { writeErrorResponse(w, http.StatusInternalServerError, "config_save_failed", "Failed to save configuration", map[string]string{"error": err.Error()}) 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(). Str("hostname", hostname). Str("proxy_url", proxyURL). diff --git a/scripts/install-sensor-proxy.sh b/scripts/install-sensor-proxy.sh index 8f5031f0f..7f52692b4 100755 --- a/scripts/install-sensor-proxy.sh +++ b/scripts/install-sensor-proxy.sh @@ -1738,17 +1738,35 @@ if [[ "$HTTP_MODE" == true ]]; then # Check if port is already in use PORT_NUMBER="${HTTP_ADDR#:}" if ss -ltn | grep -q ":${PORT_NUMBER} "; then - 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 + # Port is in use - check if it's our own service (refresh scenario) + if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then + # Check if the process using the port is pulse-sensor-proxy + PORT_OWNER=$(ss -ltnp | grep ":${PORT_NUMBER} " | grep -o 'pulse-sensor-pr' || true) + if [[ -n "$PORT_OWNER" ]]; then + # Our service is using the port - this is a refresh, continue + print_info "Existing pulse-sensor-proxy detected on port ${PORT_NUMBER} - will refresh configuration" + else + # Service is active but something else is using the port + print_error "Port ${PORT_NUMBER} is already in use by another process" + 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 # Setup TLS certificates @@ -1814,12 +1832,19 @@ if [[ "$HTTP_MODE" == true ]]; then chmod 600 /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 - 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" 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 if grep -q "^# HTTP Mode Configuration" /etc/pulse-sensor-proxy/config.yaml; then print_info "Removing existing HTTP configuration..." @@ -1841,9 +1866,15 @@ if [[ "$HTTP_MODE" == true ]]; then 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..." - 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_enabled: true @@ -1857,6 +1888,7 @@ allowed_source_subnets: - $PULSE_IP/32 - 127.0.0.1/32 EOF + fi chown pulse-sensor-proxy:pulse-sensor-proxy /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) if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then 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 # Install hardened systemd service @@ -2031,7 +2066,7 @@ if ! systemctl enable pulse-sensor-proxy.service; then exit 1 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 "" @@ -2040,7 +2075,14 @@ if ! systemctl restart pulse-sensor-proxy.service; then print_warn "Attempting to rollback to previous configuration..." if cp "$BACKUP_CONFIG" /etc/pulse-sensor-proxy/config.yaml; then 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_error "" print_error "HTTP mode installation failed but previous config restored"