b562e84b88
Action lives at gitea.shcizo.se/shcizo/package-updater-action now.
85 lines
3.9 KiB
Markdown
85 lines
3.9 KiB
Markdown
# package-updater
|
|
|
|
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
|
|
|
|
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](https://gitea.shcizo.se/shcizo/package-updater-action) 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.
|