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"