Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixed-crux-health-tmux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Crux `GET /v1/health` now reports a `tmux` field (where tmux resolves on PATH) and answers 503, with the full health body, when tmux is missing: every agent runs in tmux, so a server without it cannot serve traffic and was reporting healthy ([hivecommons/crux#137](https://github.com/hivecommons/crux/issues/137)). Existing fields are unchanged; probes that treated any 200 as healthy now see the failure.
3 changes: 3 additions & 0 deletions src/crux/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ type Health struct {
// that restarted the server sees the uptime reset.
StartedAt time.Time `json:"started_at"`
UptimeSeconds float64 `json:"uptime_seconds"`
// Tmux is where tmux resolves on PATH, "" when it does not. Every agent
// runs inside tmux, so the route answers 503 (with this body) when empty.
Tmux string `json:"tmux"`
}

// HiveCallback is the wire form of the callback client's status.
Expand Down
1 change: 1 addition & 0 deletions src/crux/serve/families_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ func TestServeWithoutHostOrBackground(t *testing.T) {
if len(rt.Background()) != 0 {
t.Fatal("background loops started while disabled")
}
t.Setenv("PATH", fakeTmuxDir(t))
var h api.Health
newFamServer(t).expect(http.MethodGet, api.PathHealth, nil, http.StatusOK, &h)
if len(h.Background) != 0 {
Expand Down
42 changes: 42 additions & 0 deletions src/crux/serve/health_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package serve

import (
"net/http"
"os"
"os/exec"
"path/filepath"
"testing"

"github.com/hivecommons/hive/crux/api"
)

// fakeTmuxDir returns a directory holding an executable named tmux, for a
// PATH that resolves it without needing a real install.
func fakeTmuxDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "tmux"), []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatal(err)
}
return dir
}

func TestHealthTmuxMissingIs503(t *testing.T) {
t.Setenv("PATH", t.TempDir())
var h api.Health
newFamServer(t).expect(http.MethodGet, api.PathHealth, nil, http.StatusServiceUnavailable, &h)
if h.Tmux != "" || h.Version != api.Version {
t.Fatalf("health: %+v", h)
}
}

func TestHealthTmuxPresentIs200(t *testing.T) {
if _, err := exec.LookPath("tmux"); err != nil {
t.Skip("tmux not installed")
}
var h api.Health
newFamServer(t).expect(http.MethodGet, api.PathHealth, nil, http.StatusOK, &h)
if h.Tmux == "" {
t.Fatalf("health: %+v", h)
}
}
9 changes: 8 additions & 1 deletion src/crux/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/hivecommons/hive/crux/internal/privatedir"

"github.com/hivecommons/hive/crux"
"github.com/hivecommons/hive/crux/agent/tmux"
"github.com/hivecommons/hive/crux/api"
"github.com/hivecommons/hive/crux/checkout"
"github.com/hivecommons/hive/crux/config"
Expand Down Expand Up @@ -388,7 +389,13 @@ func (s *Server) handleHealth(w http.ResponseWriter, _ *http.Request) {
hc := s.HiveCallback()
h.HiveCallback = &hc
}
s.writeJSON(w, http.StatusOK, h)
status := http.StatusOK
if path, err := tmux.LookPath(); err != nil {
status = http.StatusServiceUnavailable
} else {
h.Tmux = path
}
s.writeJSON(w, status, h)
}

func (s *Server) handleList(w http.ResponseWriter, _ *http.Request) {
Expand Down
Loading