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
This commit is contained in:
rcourtman 2025-10-13 14:40:03 +00:00
parent 8d6ab4113d
commit 6d56917cd9
2 changed files with 65 additions and 6 deletions

View file

@ -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 {

View file

@ -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