Commit Graph
100 Commits
Author SHA1 Message Date
shcizo cf9c0c4b10 fix(agent): set the override owner on the temp file before the rename (#24) 2026-10-13 09:25:18 +02:00
shcizo 92b1a0835e fix(agent): give the override file the owner of the stack (#24) 2026-10-13 09:20:40 +02:00
shcizo 1664e47183 feat: master/agent phase 4 — registry polling and version history (#9) (#23)
Docker Build / docker (push) Successful in 47s
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
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
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
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
shcizo 8a1fb45971 feat(master): store service inventory snapshots and web sessions 2026-10-12 14:45:46 +02:00
shcizo 99c553b7c6 feat(master): postgres store with migrations, master identity and agents 2026-10-12 14:43:34 +02:00
shcizo 9b74286c9b feat(agent): add updater-agent binary and ship it in the image 2026-10-12 14:41:05 +02:00
shcizo ad6ff1e066 feat(agent): connect to master over pinned TLS and stream inventory 2026-10-12 14:38:14 +02:00
shcizo 3edaf4e602 feat(agent): diff inventories and debounce docker events 2026-10-12 14:36:11 +02:00
shcizo abee9abb1b feat(agent): collect compose, swarm and container inventory from docker 2026-10-12 14:34:13 +02:00
shcizo 61e738e39d feat: pinned master key TLS and per-agent tokens 2026-10-12 14:32:40 +02:00
shcizo b482c2e29c feat(protocol): define agent-to-master wire messages 2026-10-12 14:31:18 +02:00
shcizo adeddf0254 feat(imageref): parse image references into repo, tag and digest 2026-10-12 14:29:52 +02:00
shcizo a57df51532 Merge pull request 'docs: master/agent design spec and phase 1 plan' (#16) from feat/master-agent into main 2026-10-12 12:15:16 +00:00
shcizo d2a8b548c9 docs: implementation plan for master/agent phase 1 (inventory) 2026-10-12 13:42:27 +02:00
shcizo 1c833f9fbe docs: design for master/agent architecture with live inventory, update modes and polling 2026-10-12 13:20:50 +02:00
shcizo 9ea082de82 Merge branch 'worktree-docker-swarm-support'
Adds MODE=compose|swarm. Each instance owns one Docker daemon and runs
one paradigm; see docs/superpowers/specs/2026-08-04-multi-server-fanout-design.md
for why this exclusivity is the intended design.
2026-08-04 19:25:08 +02:00
shcizo 50ef1a9a07 Merge pull request 'feat: add Docker Swarm support (MODE=swarm)' (#4) from worktree-docker-swarm-support into main
Docker Build / docker (push) Successful in 1m7s
Reviewed-on: #4
2026-07-04 18:54:14 +00:00
shcizo db84a2f6ba fix(api): plumb request tag into discovery so swarm mode deploys the requested version
req.Tag was only echoed in the HTTP response, never used to match jobs.
Compose mode didn't care (ComposeExecutor re-pulls the compose file's own
pinned tag), but SwarmExecutor sets the service image directly from
Job.Image, which was built from the untagged req.Image alone -- so a
Swarm deploy silently rewrote the service to :latest instead of the
requested tag. Build the full image:tag reference once in the handler
and pass it into FindJobs; NormaliseImage/ImagesMatch already strip
tags before matching, so this doesn't change which jobs match in either
mode.
2026-07-04 20:41:59 +02:00
shcizo 9881812eca docs: document MODE=swarm support and its opt-in label / manager-node requirements 2026-07-04 20:32:22 +02:00
shcizo ac3912367f merge: bring in CLAUDE.md and CI workflow from main 2026-07-04 20:31:25 +02:00
shcizo 9a3a200873 feat: wire MODE config to select compose or swarm discovery/executor 2026-07-04 20:28:28 +02:00
shcizo f92f8ac6b1 feat(api): surface ServiceID in update results for swarm jobs 2026-07-04 20:22:30 +02:00
shcizo 34865c9fd0 feat(updater): add SwarmExecutor to run docker service update via the Docker API
Adds the Swarm-mode counterpart to ComposeExecutor: updates a Swarm
service's image directly through the Docker SDK (ServiceInspectWithRaw +
ServiceUpdate with QueryRegistry=true so floating tags resolve to a fresh
digest), gated by the same Refused guard used in Compose mode. Satisfies
the existing Executor interface unchanged, so Queue needs no changes.
2026-07-04 20:19:00 +02:00
shcizo a8524e5ff4 feat(discovery): add SwarmDiscovery to find opted-in Swarm services by image
Verified the Docker SDK v28.5.2+incompatible ServiceList signature via go doc
before implementing: the options type is swarm.ServiceListOptions, not
types.ServiceListOptions as the brief assumed. Everything else (Service.ID,
Service.Spec via embedded Annotations for Name/Labels, TaskTemplate.ContainerSpec.Image)
matched the brief exactly.

Reuses discovery.ImagesMatch and discovery.HasOptIn rather than duplicating
matching/opt-in logic. Opt-in label is read from the service spec's own
labels since Swarm services have no local compose file to anchor a
STACKS_ROOT path check against (unlike Compose mode).
2026-07-04 20:12:44 +02:00
shcizo 9cf484e164 feat(discovery): add Image and ServiceID fields to Job for swarm support 2026-07-04 20:09:39 +02:00
shcizo 1002bb7b96 feat(config): add MODE env var to select compose or swarm operation 2026-07-04 20:06:50 +02:00
shcizo 8f999c69bd docs: add docker swarm support implementation plan 2026-07-04 20:05:02 +02:00
shcizo df54bbaacf docs: add CLAUDE.md with project context for Claude Code
Captures design intent, gotchas, and references that aren't obvious
from code alone — single-worker queue rationale, defense-in-depth
security model, the not-yet-wired selfupdate package, and pointers
to the design spec and implementation plan in docs/superpowers/.
2026-05-22 14:27:28 +02:00
shcizo 476945722b Merge pull request 'ci: docker build + push workflow on v* tags' (#2) from feat/ci-docker-build into main
Docker Build / docker (push) Successful in 51s
2026-05-22 14:09:02 +02:00
shcizo 2494e71969 ci: docker build + push on v* tags 2026-05-22 14:02:45 +02:00
shcizo 46ef00c742 Merge pull request 'Initial implementation: package-updater v1' (#1) from feat/initial-implementation into main 2026-05-22 13:59:37 +02:00
shcizo e43868bf41 build: bump Dockerfile to golang:1.26-alpine to match go.mod 2026-05-22 13:56:23 +02:00
shcizo 9a8b171198 feat: docker-compose.local.yml for local build/smoke-test 2026-05-22 13:53:49 +02:00
shcizo 3abd1bf9af feat(metrics): wire instrumentation through queue, handlers, middleware 2026-05-22 13:17:09 +02:00
shcizo ef9c0f1313 chore: gitignore stray ./server binary from go build 2026-05-22 13:05:43 +02:00
shcizo b866f6d47c feat(cmd): wire config, discovery, queue, http server, metrics 2026-05-22 13:04:44 +02:00
shcizo 952189eb09 feat(metrics): Prometheus collectors and /metrics handler 2026-05-22 12:59:53 +02:00
shcizo 2be2fdf325 docs: real README with quick start, endpoints, observability 2026-05-22 12:09:37 +02:00
shcizo 33a8b8689f feat(action): reusable Gitea composite action for /update 2026-05-22 12:08:12 +02:00
shcizo 804b69b67c docs: docker-compose.example.yml for end-user deploy 2026-05-22 12:07:47 +02:00
shcizo 00bada890b build: multi-stage Dockerfile with build info ldflags 2026-05-22 12:07:36 +02:00
shcizo e88f105490 feat(selfupdate): defer self-replacement until response flushed 2026-05-22 12:06:07 +02:00
shcizo 857f504d36 feat(api): /update, /healthz, /version handlers 2026-05-22 12:03:56 +02:00
shcizo df2dc30cea feat(api): auth, request_id, and request-logging middleware 2026-05-22 11:59:34 +02:00
shcizo c9b1af42e5 feat(updater): FIFO queue with single worker and per-job results 2026-05-22 11:57:09 +02:00
shcizo a275f208c4 feat(updater): ComposeExecutor shells out to docker compose 2026-05-22 11:54:03 +02:00
shcizo 058b9d8c07 feat(updater): Executor interface 2026-05-22 11:53:42 +02:00
shcizo db784ad303 feat(discovery): orchestrate match, opt-in filter, dedup, path-check 2026-05-22 11:51:07 +02:00
shcizo 897093ed1c feat(discovery): STACKS_ROOT path safety check 2026-05-22 11:45:56 +02:00
shcizo 13ccb79ef5 feat(discovery): parse Compose-managed labels 2026-05-22 11:44:10 +02:00
shcizo cff6c1baff feat(discovery): tag-agnostic image name normalisation 2026-05-22 11:41:33 +02:00
shcizo e665b6cd7e feat(logging): structured JSON logger with request_id context 2026-05-22 11:38:07 +02:00
shcizo 883f3cfecf chore: tidy go.sum for testify transitive deps 2026-05-22 11:35:33 +02:00
shcizo 9832b2b190 docs: complete se.enocsson to se.shcizo rename in spec 2026-05-22 11:35:33 +02:00
shcizo 4f2a19c663 chore: gitignore Serena MCP workspace 2026-05-22 11:35:33 +02:00
shcizo 2b7f7d863a feat(config): load and validate env-var config 2026-05-22 11:24:29 +02:00
shcizo 1ca9f5296a chore: scaffold Go project layout 2026-05-22 11:16:23 +02:00
shcizo ef14465314 docs: remove dead strconv import from main.go in plan 2026-05-22 11:13:40 +02:00