Commit Graph
107 Commits
Author SHA1 Message Date
shcizo cbcb8290d0 fix(master): keep agent rows and the add-agent drawer inside their columns (#18)
- Move the .agent @container rule after the base .agent rules. A container
  query adds no specificity, so the earlier position always lost and rows
  kept six fixed columns, spilling Revoke buttons onto the open drawer.
- Give .drawer min-width:0 so the compose snippet scrolls inside its <pre>
  instead of widening the drawer past the viewport at 390px.

Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
2026-10-13 10:34:17 +02:00
shcizo cdda174178 fix(master): review fixes for the Ledger UI (#18)
- icons/gen writes to the current directory so `go generate
  ./internal/master/web/icons` works (output verified byte-identical)
- share the registry-host check as imageref.IsRegistryHost (with test)
- agents fragment refreshes the "N machines" header count out of band
- ignore the saved machine filter at <=760px where the machine list is hidden
- aria-current on the active machine filter link, aria-pressed on theme toggle
- CLAUDE.md: icons package, SSE ping event, swarm_cluster_id

Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
2026-10-13 10:25:02 +02:00
shcizo dd2b90bece feat(master): Ledger UI for Services and Agents (#18)
Restyles the master UI after the Ledger design: a sidebar with live counts,
a machine filter, a Live box and a light/dark toggle; the overview groups
services per Swarm cluster and machine with icons, search and the
Icon / Icon + text / Text switch; the agents page gets a revoke popover and
an add-agent panel with a connect step. Jobs, Job and Service pages get the
new shell and tokens only.

- fonts and Simple Icons are served by the master (no CDN)
- the overview reads agents too, to show offline banners and cluster managers
- revoking an agent also publishes the agents event
- the SSE keepalive is a named event (ping) the Live box counts
- the master passes its build version to the UI so agents on another
  version are marked old

Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
2026-10-13 10:18:24 +02:00
shcizo 11c876af7f build(master): vendor Simple Icons data and the Ledger fonts
Icons: simple-icons@16.35.0 (CC0), generated by
go run ./internal/master/web/icons/gen, which downloads and verifies:
https://registry.npmjs.org/simple-icons/-/simple-icons-16.35.0.tgz
20ebead4f6bc93464812a5e260d19fc4d84772e5eeff69a6fbea263ca2441c5f
Output (deterministic): simple-icons.json.gz
a8f3b504cc21207e8d55d50f3724630a470968da69451c731538589c4debc87b

Fonts (@fontsource-variable 5.3.0, latin, SIL OFL 1.1), sha256:
4c8b93f431d462c696e12b9d6a033feb3394d36e66e781357c496b95d8a75e05  schibsted-grotesk-latin-wght-normal.woff2
18be452724bfdc236c074ca94a249a7f41a86752c7d04ab258ce9ed5651f6a7e  jetbrains-mono-latin-wght-normal.woff2
15222412a95d70042e66e79bfb3b985653e0693e4d41703bc131bee9c5aa1aa7  OFL-schibsted-grotesk.txt
403581b69dac5cff4079205e01c6b467e56af449ecbd7247693ddb1baafa005b  OFL-jetbrains-mono.txt
from https://cdn.jsdelivr.net/npm/@fontsource-variable/{schibsted-grotesk,jetbrains-mono}@5.3.0/

Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
2026-10-13 10:06:09 +02:00
shcizo 9d5f24b125 feat(store): persist the Swarm cluster id of each agent
The Ledger UI groups Swarm services under their cluster and lists the
cluster's managers, so the master needs to know which cluster an agent
belongs to. RecordHello now stores hello.SwarmClusterID in the new
agents.swarm_cluster_id column.

Claude-Session: https://claude.ai/code/session_01GAZsp3P7Qto6JnhipxWUJQ
2026-10-13 10:04:58 +02:00
shcizo 307e46e4c7 chore(master): run the master as a non-root user (#25) (#27)
Docker Build / docker (push) Successful in 40s
v0.6.2
2026-10-13 07:33:45 +00:00
shcizo f95e5319a8 fix(agent): give compose.override.yaml the owner of the stack (#24) (#26)
Docker Build / docker (push) Successful in 40s
v0.6.1
2026-10-13 07:27:42 +00:00
shcizo 1664e47183 feat: master/agent phase 4 — registry polling and version history (#9) (#23)
Docker Build / docker (push) Successful in 47s
v0.6.0
2026-10-13 07:02:34 +00:00
shcizo 41a619b316 feat: master/agent phase 3 — webhook and auto mode (#8) (#22)
Docker Build / docker (push) Successful in 38s
## Summary
- `POST /update` on the master (outside OIDC, bearer `UPDATER_API_KEY`; unset → 503). Matches `image` against `image_repo` on all agents, marks `available_*` on every match, starts webhook-triggered jobs for `auto` services targeting `<image_repo>:<tag>` (followed tag when no tag is given), and waits for them up to `UPDATE_TIMEOUT` (default 5m). 404 on no match, 500 if any auto job failed or timed out, otherwise 200.
- Manual/auto toggle per service on the overview and service page (`POST /services/{id}/mode`, CSRF); auto is blocked for self, `build:` and container services with the reason shown. "Update available" badge and an "Available" row.
- `FinishJob` clears `available_*` only when the successful job was created for that exact ref, after it was reported. Migration `0003_webhook.sql`, phase-3 plan doc, README / gitea-action README / CLAUDE.md / deploy example updated. `action.yml` unchanged.

Closes #8

## Behaviour changes for CI
- Mixed results now return 500 (the old 207 is gone); no match returns 404 instead of 200.
- With a `tag`, Compose services get pinned to `<image_repo>:<tag>` via `compose.override.yaml`; the tag must be a pushed tag.
- `busy` (job already active) and `refused` (not updatable) are reported but return 200.
- The reverse proxy read timeout must exceed `UPDATE_TIMEOUT`.

## Test plan
- [x] `go test ./...` and `go test -race ./internal/agent/... ./internal/master/... ./internal/e2e/`
- [x] `go test -tags integration ./internal/master/store/` against throwaway Postgres
- [x] UI verified in browser (mode toggle, disabled reason, badge via SSE, Available row)
- [ ] Manual: webhook with manual service → `marked` + badge
- [ ] Manual: webhook with auto service → waits, `succeeded`, badge cleared, override pinned
- [ ] Manual: wrong token 401, unknown image 404, offline agent 500
- [ ] Gitea action pointed at the master's `/update` in a real CI run

Reviewed-on: #22
v0.5.0
2026-10-12 20:03:22 +00:00
shcizo 417ef315c2 Merge pull request 'feat: master/agent phase 2 — jobs and manual update (#7)' (#20) from feat/master-agent-jobs-7 into main
Docker Build / docker (push) Successful in 40s
Reviewed-on: #20
v0.4.0
2026-10-12 17:58:48 +00:00
shcizo ec96d5687d fix(agent): reset per-service warnings after a successful build check (#7) 2026-10-12 19:45:29 +02:00
shcizo c5dba96cf8 fix(agent): list all compose profiles and fail closed on unknown services (#7) 2026-10-12 19:38:07 +02:00
shcizo 27c4200dda feat(agent): report and refuse Compose services built from source; honour --env-file (#7) 2026-10-12 19:32:05 +02:00
shcizo 69ef55837e docs: compose environment and built-from-source services (#7) 2026-10-12 19:30:38 +02:00
shcizo 3d3dfb47e6 feat(master): show built-from-source services but never update them (#7) 2026-10-12 19:30:38 +02:00
shcizo c3bd4ca1ed test(master): CreateJob waits for a concurrent revoke (#7) 2026-10-12 19:29:43 +02:00
shcizo 91cb027ef6 feat(protocol): Service.Built marks Compose services built from source (#7) 2026-10-12 19:29:18 +02:00
shcizo 7e23e1843f docs: spell out the upgrade risk and the bare-image rule (#7) 2026-10-12 19:15:24 +02:00
shcizo 961ddd275c test(master): wait for the replacement connection to register (#7) 2026-10-12 19:15:24 +02:00
shcizo caa6c81716 fix(master): treat a bare image as :latest and handle a revoked agent in Trigger (#7) 2026-10-12 19:14:59 +02:00
shcizo c09e774d74 fix(master): refuse jobs for revoked agents, signal revoked jobs, agent start time wins (#7) 2026-10-12 19:14:32 +02:00
shcizo 93ba6915b9 fix(agent): minimal env and explicit project dir for compose, resolve every config file's symlinks (#7) 2026-10-12 19:13:25 +02:00
shcizo 8b2c10cc05 fix(web): job log polls while active, one service history limit (#7) 2026-10-12 19:13:01 +02:00
shcizo 88d5507217 docs: describe Swarm registry auth (#7) 2026-10-12 18:50:12 +02:00
shcizo b7e25bb1fb docs: master/agent phase 2 — jobs and manual update (#7) 2026-10-12 18:48:53 +02:00
shcizo b392e11b37 test: jobs end to end over pinned TLS (#7) 2026-10-12 18:48:06 +02:00
shcizo 55f8021b0a build: agent is the image's default entrypoint; drop the old server examples (#7) 2026-10-12 18:45:47 +02:00
shcizo 601c6da0d4 feat(agent): run jobs from the master and resend results until acked (#7) 2026-10-12 18:45:44 +02:00
shcizo e313365686 feat(agent): job runner, compose override executor and swarm executor (#7) 2026-10-12 18:45:44 +02:00
shcizo 32c30194c4 feat(master): wire jobs into the master (#7) 2026-10-12 18:43:20 +02:00
shcizo 02de0b8cf3 feat(master): start manual update jobs with self-protection (#7) 2026-10-12 18:43:11 +02:00
shcizo 68e5c33722 feat(master): route jobs to agents and reconcile them on reconnect (#7) 2026-10-12 18:42:30 +02:00
shcizo 574f425267 feat(web): service page, manual update, jobs list and live job log (#7)
Also publishes the jobs topic on agent revoke and updates the agent
snippet to mount stacks read-write at the same path.
2026-10-12 18:41:46 +02:00
shcizo 9d3a6f808c feat(master): store update jobs (#7) 2026-10-12 18:40:48 +02:00
shcizo e2b73dfd81 feat(agent): UPDATE_TIMEOUT and DOCKER_CONFIG settings (#7) 2026-10-12 18:40:15 +02:00
shcizo c9c4e0cadb refactor(web): web.New takes the jobs service (#7) 2026-10-12 18:39:57 +02:00
shcizo 9e09de0a16 feat(protocol): job messages, protocol version and job model (#7) 2026-10-12 18:38:39 +02:00
shcizo eb41967cd1 docs: plan revisions after review (#7) 2026-10-12 18:37:28 +02:00
shcizo 7e55ec3b33 docs: implementation plan for master/agent phase 2 (jobs) (#7) 2026-10-12 17:13:15 +02:00
shcizo 3090b6d4f0 Merge pull request 'feat: master/agent phase 1 — live inventory (#6)' (#19) from feat/master-agent-inventory-6 into main
Docker Build / docker (push) Successful in 58s
v0.3.0
2026-10-12 13:50:09 +00:00
shcizo 2fe9b5ba0d fix(master): block inventory writes from revoked agents (#6)
ApplySnapshot and UpsertService now take a FOR SHARE lock on the agent's
row and refuse with model.ErrRevoked when it is revoked. RevokeAgent's
UPDATE waits on that lock, so an in-flight snapshot or upsert can no
longer bring a revoked agent's services back after the revoke cleanup.
2026-10-12 15:18:51 +02:00
shcizo 0e9e34db06 fix: address final review findings for master/agent phase 1 (#6)
- inventory: resolve the image reference via ContainerInspect when a moved
  tag makes ContainerList report the image ID
- store: allow reusing a revoked agent's name (partial unique index) and
  mark the revoked agent's compose/container services removed
- web: publish a services event on revoke; no-store on the one-time token page
- webauth: reject empty OIDC state; accept the CSRF form token from the body only
- agent conn: bound the WebSocket handshake to 30s
- hub: log agent_replaced when a connection replaces an older one
2026-10-12 15:16:29 +02:00
shcizo 4f092eaf79 docs: document master/agent phase 1 and add master stack example 2026-10-12 15:04:43 +02:00
shcizo 05610391e3 test: agent to master inventory over pinned TLS end to end 2026-10-12 15:02:50 +02:00
shcizo e8f7cb3378 feat(master): add updater-master binary wiring store, hub, auth and UI 2026-10-12 15:01:11 +02:00
shcizo a596d67728 feat(master): htmx UI with live service overview and agent management
Vendored static assets (sha256):
e209dda5c8235479f3166defc7750e1dbcd5a5c1808b7792fc2e6733768fb447  htmx.min.js (htmx.org@2.0.4)
83eca6fa0611fe2b0bf1700b424b88b5eced38ef448ef9760a2ea08fbc875611  htmx-ext-sse.js (htmx-ext-sse@2.2.2)
2026-10-12 14:57:23 +02:00
shcizo b4894db6d7 feat(master): OIDC login with group check, sessions and CSRF protection 2026-10-12 14:54:11 +02:00
shcizo 049324b2f4 fix(master): recheck revocation after register, reject duplicate hello 2026-10-12 14:52:24 +02:00
shcizo 75e28262f8 feat(master): agent hub that authenticates agents and stores their inventory 2026-10-12 14:50:22 +02:00
shcizo 5d75f9a7da feat(master): in-process broker for live UI change signals 2026-10-12 14:48:03 +02:00