Commit f8c1e35051

f8c1e35051a416f451bc97dd49f0253c6673c765

parent: 3ac40e6851

Verified · cmc ci/build: success ci/test: error

cmc <hello@cleberg.net> · 2026-09-23 06:20 UTC

docs: queued follows, proxies

Ref #250
.gitbay/wiki/Admin.org +4
@@ -81,6 +81,10 @@ rate limiting, to the last =X-Forwarded-For= hop that is not itself a
8181trusted proxy; from anyone else the header is ignored. Empty, the
8282default, is right when gitbayd terminates TLS itself.
8383
84A reverse proxy in front of gitbay must not buffer responses, or the
85build page's live log arrives only when the build ends;
86=X-Accel-Buffering: no= covers nginx.
87
8488** [web]
8589- =mode==view_only= (default) | =accounts=. In view_only the mutating
8690 web routes are never registered; in accounts, browser sessions are
.gitbay/wiki/CI.org +7 −5
@@ -41,11 +41,13 @@ a matching tag push and nothing else.
4141A running build is followed with =build log <owner/name> <n> --follow=:
4242the stored log, then output as the runner sends it, then the outcome
4343as =build <n> <status>= on stderr once the build ends. The exit code is
440 whatever the outcome. The build page does the same without
45JavaScript while a build is queued or running; =?follow=0= renders it
46once. An account holds at most eight follows open, and signed-out
47viewers share one account's eight. Over the JSON API the command
48answers when the build ends, with the whole log.
440 whatever the outcome. A follow of a build still queued after ten
45minutes ends on its own and says so on stderr, since nothing reaps a
46queued build. The build page does the same without JavaScript while a
47build is queued or running; =?follow=0= renders it once. An account
48holds at most eight follows open, and signed-out viewers share one
49account's eight. Over the JSON API the command answers when the build
50ends, with the whole log.
4951
5052* The table
5153
docs/specs/2026-09-23-build-log-follow-design.md +30 −9
@@ -62,10 +62,14 @@ and the wait still wakes the loop.
6262`Ctx` gains `Done <-chan struct{}`, nil when the surface has none. The
6363embedded sshd closes it when the session's channel closes (the CLI's
6464shared connection outlives a Ctrl-C, the channel does not); `gitbayd
65shell` passes nil, since its process ends with the session. httpd sets
66it from `r.Context()` on the web and both API endpoints. A write
67error also ends the loop. On `Done` the command returns
68`protocol.ExitFailure` with no message; nobody is reading.
65shell` still passes nil, but its process does not end with the
66session: OpenSSH closes the child's pipes and sends no signal to a
67session with no pty, so a follow there ends at its next write, at the
68build's outcome, or at the queued limit below; the per-account cap is
69per process in that mode. httpd sets it from `r.Context()` on the web
70and both API endpoints. A write error also ends the loop. On `Done`
71the command returns `protocol.ExitFailure` with no message; nobody is
72reading.
6973
7074At most 8 follows per account run at once (a counter in `control`,
7175decremented on return). The ninth exits 4: "8 follows are already open
@@ -73,13 +77,23 @@ for this account; close one and retry". Signed-out web viewers are
7377account 0 and share the 8; the ninth gets the stored log once with the
7478refusal under it.
7579
80Nothing reaps a queued build (`ReapStaleBuilds` only reaps `running`
81builds), and a running one is already bounded by the reaper's
82deadline, so a follow of a build that stays `pending` ends on its own
83after `followQueued` (10 minutes), writing to stderr `build <n> is
84still queued; nothing claimed it in 10m0s. Follow again once a runner
85has.` and exiting `protocol.ExitFailure`. The clock runs only while the
86follow has seen the build `pending`; once it sees `running` or a
87terminal status the limit no longer applies.
88
7689The CLI's `pass("log", …)` help in `cmd/gitbay/main.go` names
7790`--follow`.
7891
7992## Web
8093
8194`GET /{owner}/{repo}/builds/{n}` streams when the build is `pending` or
82`running` and the query has no `follow=0`. Otherwise it renders as now.
95`running`, the query has no `follow=0`, and the method is `GET`. A HEAD
96request (the route also matches it) renders once, like `?follow=0`.
8397
8498Streaming:
8599
@@ -89,10 +103,17 @@ Streaming:
891033. Dispatch `build log <repo> <n> --follow` with `Stdout` an escaping
90104 writer (`template.HTMLEscape` per chunk, then flush through
91105 `http.ResponseController`) and `Done` from the request context.
924. After the command returns, read the build and write
93 `<p class="notice" role="status">build finished: <status></p>` after
94 the `</pre>` that begins the tail, then the rest of the tail. A
95 dropped connection writes nothing more.
1064. If the request context is done (the client left), write nothing
107 more. Otherwise write the `</pre>` that begins the tail, then, by
108 the command's exit code: `ExitOK` reads the build and writes
109 `<p class="notice" role="status">build finished: <status></p>`;
110 `ExitDenied` (the follow cap) writes the stored log once above the
111 `</pre>` and an error paragraph with the refusal, except a
112 signed-out viewer (`viewer.ID == 0`) gets "Too many signed-out
113 viewers are watching live builds. This is the log so far; reload to
114 try again, or sign in." instead of the command's account-scoped
115 wording; `ExitFailure` with a message (the queued limit) writes it
116 as a `<p class="notice" role="status">`. Then the rest of the tail.
96117
97118`build.html`, when `Live`, puts a line above the log: the log streams
98119until the build ends; a stream that stops with no "build finished" line