Commit badc110670
badc110670d80cc0727d187b52638f3b27c9357d
parent: 7b6823e3e9
Verified · cmc ci/build: success ci/test: success ci/vuln: success
cmc <hello@cleberg.net> · 2026-09-02 02:52 UTC
http: /healthz
The host monitor checked systemd, disk and the certificate from outside
the process. Nothing reported from inside it.
GET /healthz answers with no auth and no repository data: whether the
database answers, and the commit serving. 503 when it does not, so a
checker reading only the status code still learns the truth. healthz
joins the reserved names.
Closes #77
Layout: unified · split
e2e/reap_test.go
+13
| @@ -139,3 +139,16 @@ func TestAdminRunners(t *testing.T) { |
| 139 | t.Fatalf("host runners:\n%s", out) |
139 | t.Fatalf("host runners:\n%s", out) |
| 140 | } |
140 | } |
| 141 | } |
141 | } |
| |
142 | |
| |
143 | // /healthz is unauthenticated, cache-free, and says which build serves. |
| |
144 | func TestHealthz(t *testing.T) { |
| |
145 | inst := startInstance(t) |
| |
146 | status, body := inst.get(t, "/healthz") |
| |
147 | if status != 200 || !strings.Contains(body, `"ok":true`) || !strings.Contains(body, `"commit":"`) { |
| |
148 | t.Fatalf("healthz: %d %s", status, body) |
| |
149 | } |
| |
150 | // The name is reserved: no account can shadow the route. |
| |
151 | if _, errOut, code := inst.ssh(t, inst.newKey(t, "x"), "", "register", "--username", "healthz"); code == 0 { |
| |
152 | t.Fatalf("healthz registered as a username: %s", errOut) |
| |
153 | } |
| |
154 | } |
internal/httpd/healthz.go
added
+31
| @@ -0,0 +1,31 @@ |
| |
1 | package httpd |
| |
2 | |
| |
3 | import ( |
| |
4 | "encoding/json" |
| |
5 | "net/http" |
| |
6 | |
| |
7 | "gitbay.org/gitbay/internal/buildinfo" |
| |
8 | ) |
| |
9 | |
| |
10 | // healthz answers from inside the process: whether the database answers, |
| |
11 | // and which build is serving. No auth, no repository data; a monitor or |
| |
12 | // an uptime check reads it. 503 when the database does not answer, so a |
| |
13 | // checker that only reads the status code still learns the truth. |
| |
14 | func (s *Server) healthz(w http.ResponseWriter, r *http.Request) { |
| |
15 | dbErr := "" |
| |
16 | if err := s.st.DB.Ping(); err != nil { |
| |
17 | dbErr = err.Error() |
| |
18 | } else if _, err := s.st.Version(); err != nil { |
| |
19 | dbErr = err.Error() |
| |
20 | } |
| |
21 | w.Header().Set("Content-Type", "application/json") |
| |
22 | w.Header().Set("Cache-Control", "no-store") |
| |
23 | if dbErr != "" { |
| |
24 | w.WriteHeader(http.StatusServiceUnavailable) |
| |
25 | } |
| |
26 | json.NewEncoder(w).Encode(map[string]any{ |
| |
27 | "ok": dbErr == "", |
| |
28 | "commit": buildinfo.String(), |
| |
29 | "db": map[string]any{"ok": dbErr == "", "error": dbErr}, |
| |
30 | }) |
| |
31 | } |
internal/httpd/routes.go
+1
| @@ -38,6 +38,7 @@ func (s *Server) Routes() []Route { |
| 38 | routes = append(routes, |
38 | routes = append(routes, |
| 39 | Route{Method: "GET", Pattern: "/{$}", Handler: s.index}, |
39 | Route{Method: "GET", Pattern: "/{$}", Handler: s.index}, |
| 40 | Route{Method: "GET", Pattern: "/explore", Handler: s.explore}, |
40 | Route{Method: "GET", Pattern: "/explore", Handler: s.explore}, |
| |
41 | Route{Method: "GET", Pattern: "/healthz", Handler: s.healthz}, |
| 41 | Route{Method: "GET", Pattern: "/privacy", Handler: s.privacy}, |
42 | Route{Method: "GET", Pattern: "/privacy", Handler: s.privacy}, |
| 42 | Route{Method: "GET", Pattern: "/static/style.css", Handler: s.stylesheet}, |
43 | Route{Method: "GET", Pattern: "/static/style.css", Handler: s.stylesheet}, |
| 43 | // Literal per-file routes: a {name} wildcard is ambiguous against |
44 | // Literal per-file routes: a {name} wildcard is ambiguous against |
internal/policy/names.go
+1
| @@ -18,6 +18,7 @@ var reservedNames = map[string]bool{ |
| 18 | "favicon.svg": true, |
18 | "favicon.svg": true, |
| 19 | "gitbay": true, // vanity go-import path on gitbay.org |
19 | "gitbay": true, // vanity go-import path on gitbay.org |
| 20 | "gitbay-bot": true, // authors dependency-update issues |
20 | "gitbay-bot": true, // authors dependency-update issues |
| |
21 | "healthz": true, |
| 21 | "login": true, |
22 | "login": true, |
| 22 | "logout": true, |
23 | "logout": true, |
| 23 | "new": true, |
24 | "new": true, |