From 2be2fdf325851e2e1ceb1c054a828ed527ad7ae8 Mon Sep 17 00:00:00 2001 From: Samuel Enocsson Date: Fri, 22 May 2026 12:09:37 +0200 Subject: [PATCH] docs: real README with quick start, endpoints, observability --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0195c12..b481e41 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,84 @@ # package-updater -Webhook-driven Docker Compose service updater. See [design spec](docs/superpowers/specs/2026-05-22-package-updater-design.md) for full design. +Webhook-driven Docker Compose service updater. Fills the gap between Watchtower (polling, no CI integration) and full GitOps (Argo CD, Flux) for a self-hosted, single-host environment. + +**Trigger flow:** + +1. Gitea workflow builds and pushes a new image to your registry. +2. Workflow calls `POST /update` on this service with the image name. +3. Service finds the matching Compose-managed container(s) on the host via Docker labels. +4. Runs `docker compose pull` + `up -d` for the relevant service(s). + +See [design spec](docs/superpowers/specs/2026-05-22-package-updater-design.md) and [implementation plan](docs/superpowers/plans/2026-05-22-package-updater-implementation.md) for full design and rationale. + +## How it finds the right stack + +The service queries the Docker socket and reads the labels Compose itself attaches to every container: + +- `com.docker.compose.project` +- `com.docker.compose.service` +- `com.docker.compose.project.working_dir` +- `com.docker.compose.project.config_files` + +A container is eligible for update only if it has **both**: + +- An image name matching the request (tag-agnostic), AND +- The opt-in label `se.shcizo.auto-update=true`. + +Defense in depth: a valid bearer token AND the opt-in label must both be present before any container is touched. ## Quick start -(TBD — filled in by final task) +1. Build and push the image (e.g. via your own CI). +2. Copy `docker-compose.example.yml` to `/home/shcizo/self-hosted/package-updater/docker-compose.yml`. +3. Create `.env` next to it: `UPDATER_API_KEY=$(openssl rand -hex 32)`. +4. Point your reverse proxy (NPM/Traefik/Caddy) at `package-updater:8080`. NPM should handle TLS. +5. `docker compose up -d`. +6. Add the opt-in label `se.shcizo.auto-update: "true"` to each service you want auto-updated. +7. Use the [Gitea composite action](gitea-action/README.md) in your repos to call `/update` after a build. + +## Configuration + +All via environment variables. + +| Variable | Required | Default | Purpose | +|---|---|---|---| +| `UPDATER_API_KEY` | **yes** | — | Bearer token. Service refuses to start without it. | +| `STACKS_ROOT` | no | `/home/shcizo/self-hosted` | Required parent for any stack eligible to update. | +| `PORT` | no | `8080` | HTTP listen port. | +| `LOG_LEVEL` | no | `info` | `debug` / `info` / `warn` / `error`. | +| `UPDATE_TIMEOUT` | no | `5m` | Per-job timeout (Go duration). | +| `OPT_IN_LABEL` | no | `se.shcizo.auto-update` | Label name to check; value must equal `"true"`. | + +## Endpoints + +| Endpoint | Auth | Purpose | +|---|---|---| +| `POST /update` | Bearer token | Trigger pull + restart for matching services | +| `GET /healthz` | none | Liveness + Docker socket reachability | +| `GET /version` | none | Build info | +| `GET /metrics` | none | Prometheus exposition | + +`/healthz`, `/version`, and `/metrics` are intentionally unauthenticated — they're internal-network only behind the reverse proxy. + +## Observability + +- **Logs**: JSON to stdout, picked up by Promtail/Alloy → Loki. +- **Metrics**: Prometheus exposition on `/metrics`. Notable: `package_updater_update_jobs_total{project,service,status}`, `package_updater_last_update_timestamp{project,service}`, `package_updater_docker_ping_up`. + +## Development + +```bash +go test ./... +go build ./cmd/server +docker build -t package-updater:dev . +``` + +## Known v1 gaps + +These are tracked in the spec's section 2 and section 15 as deliberate out-of-scope: + +- **Self-update wiring**: `internal/selfupdate.Wrapped` exists and is unit-tested but is not wired into the live queue. The HTTP response flush ordering for self-replacement is a future enhancement; for now, expect to manually rerun `docker compose up -d` on the host if pushing a new image of `package-updater` itself causes a mid-response interruption. +- **No rollback**: Compose's "keep old container if new fails to start" is the only safety net. +- **Single host only**. +- **No per-repo API keys**: a single shared bearer token is used.