From 6d56917cd952f98f9b8fb041d390b652b9b50e2f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 13 Oct 2025 14:40:03 +0000 Subject: [PATCH] feat: Add detection for legacy SSH temperature monitoring Added automatic detection to alert users when they're using the old SSH-in-container method for temperature monitoring so they can upgrade to the secure proxy architecture. **Detection Logic:** - Checks if Pulse is running in a container (Docker or LXC) - Checks if SSH keys exist in data directory (/etc/pulse/.ssh) - Checks if pulse-sensor-proxy socket is NOT available - Sets legacySSHDetected and recommendProxyUpgrade flags in health endpoint **API Changes:** - Added fields to HealthResponse: - legacySSHDetected: true when old method detected - recommendProxyUpgrade: true when upgrade is recommended - proxyInstallScriptAvailable: always true **Use Case:** Users who set up temperature monitoring before the proxy feature won't know they should upgrade. This detection allows the frontend to show a banner prompting them to re-run the setup script to migrate to the secure proxy architecture. **Frontend Integration (to be added):** Frontend can poll /api/health and show a dismissible banner similar to UpdateBanner when legacySSHDetected is true, with a button to view the setup script. Addresses #123 --- internal/api/router.go | 62 ++++++++++++++++++++++++++++++++++++++++-- internal/api/types.go | 9 ++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/internal/api/router.go b/internal/api/router.go index d708a15..5f78cb2 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -1128,6 +1128,56 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { }), allowEmbedding, allowedEmbedOrigins).ServeHTTP(w, req) } +// detectLegacySSH checks if Pulse is using legacy SSH for temperature monitoring +func (r *Router) detectLegacySSH() (legacyDetected, recommendProxy bool) { + // Check if running in a container + inContainer := false + if _, err := os.Stat("/.dockerenv"); err == nil { + inContainer = true + } else if data, err := os.ReadFile("/proc/1/cgroup"); err == nil { + if strings.Contains(string(data), "docker") || strings.Contains(string(data), "lxc") { + inContainer = true + } + } + + // If not in container, no need for proxy + if !inContainer { + return false, false + } + + // Check if SSH keys are configured in the data directory + sshKeysConfigured := false + + // Check for SSH keys in the configured data directory + dataDir := r.config.DataPath + if dataDir == "" { + dataDir = "/etc/pulse" + } + + sshPrivKeyPath := filepath.Join(dataDir, ".ssh", "id_ed25519") + if _, err := os.Stat(sshPrivKeyPath); err == nil { + sshKeysConfigured = true + } + + // Also check for RSA keys + sshPrivKeyPathRSA := filepath.Join(dataDir, ".ssh", "id_rsa") + if _, err := os.Stat(sshPrivKeyPathRSA); err == nil { + sshKeysConfigured = true + } + + // If SSH keys exist, check if proxy is running + if sshKeysConfigured { + // Check if pulse-sensor-proxy is available via unix socket + proxySocket := "/run/pulse-sensor-proxy/sensor.sock" + if _, err := os.Stat(proxySocket); err != nil { + // Socket doesn't exist - legacy SSH method detected + return true, true + } + } + + return false, false +} + // handleHealth handles health check requests func (r *Router) handleHealth(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet && req.Method != http.MethodHead { @@ -1135,10 +1185,16 @@ func (r *Router) handleHealth(w http.ResponseWriter, req *http.Request) { return } + // Detect legacy SSH setup + legacySSH, recommendProxy := r.detectLegacySSH() + response := HealthResponse{ - Status: "healthy", - Timestamp: time.Now().Unix(), - Uptime: time.Since(r.monitor.GetStartTime()).Seconds(), + Status: "healthy", + Timestamp: time.Now().Unix(), + Uptime: time.Since(r.monitor.GetStartTime()).Seconds(), + LegacySSHDetected: legacySSH, + RecommendProxyUpgrade: recommendProxy, + ProxyInstallScriptAvailable: true, // Install script is always available } if err := utils.WriteJSONResponse(w, response); err != nil { diff --git a/internal/api/types.go b/internal/api/types.go index b7acc4c..6815c70 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -11,9 +11,12 @@ import ( // HealthResponse represents the health check response type HealthResponse struct { - Status string `json:"status"` - Timestamp int64 `json:"timestamp"` - Uptime float64 `json:"uptime"` + Status string `json:"status"` + Timestamp int64 `json:"timestamp"` + Uptime float64 `json:"uptime"` + LegacySSHDetected bool `json:"legacySSHDetected,omitempty"` + RecommendProxyUpgrade bool `json:"recommendProxyUpgrade,omitempty"` + ProxyInstallScriptAvailable bool `json:"proxyInstallScriptAvailable,omitempty"` } // VersionResponse represents version information