7cc17813a9
Single public deliverable per tag: a multi-arch server image, with cross-compiled agent binaries + install scripts + the systemd unit baked under /opt/restic-manager/dist/. The /agent/binary and /install/* handlers fall back from <DataDir>/... to that read-only path so a fresh container Just Works without first-run staging; operators can still drop a custom build into <DataDir>/ to override per-host. Architecture rationale: agent distribution already routes through the running server, so the release surface mirrors that — there's no second source of truth to keep in sync. Workflow .gitea/workflows/release.yml triggers on v*.*.* tag-push (fan-out :vX.Y.Z / :X.Y / :X, plus :latest once MAJOR>=1) and workflow_dispatch (snapshot tag only). Pushes to the Gitea container registry on this instance. Both binaries grow main.commit + main.date ldflag targets. Makefile and Dockerfile fill them; release workflow forwards from gitea.sha plus a UTC timestamp. Spec : docs/superpowers/specs/2026-05-05-p5-03-docker-only-release.md Plan : docs/superpowers/plans/2026-05-05-p5-03-docker-only-release.md
132 lines
5.0 KiB
Markdown
132 lines
5.0 KiB
Markdown
# P5-03 implementation plan — Docker-only release
|
|
|
|
Spec: `docs/superpowers/specs/2026-05-05-p5-03-docker-only-release.md`.
|
|
|
|
Branch: `p5-03-docker-release`. Do not auto-open a PR (see CLAUDE.md
|
|
memory: CI runs are expensive on the self-hosted cluster).
|
|
|
|
---
|
|
|
|
## Slice 1 — Server config + handler fallback
|
|
|
|
**Goal:** server can serve agent binaries / install scripts from a
|
|
read-only "bundled assets" path when `<DataDir>` doesn't have them.
|
|
|
|
1. `internal/server/config/config.go` (or wherever `Cfg` lives) gains
|
|
a `BundledAssetsDir string` field, defaulting to
|
|
`/opt/restic-manager/dist`. Wire from `RM_BUNDLED_ASSETS_DIR` env
|
|
var, mirroring the existing env-var conventions.
|
|
2. `internal/server/http/agent_assets.go`:
|
|
- `handleAgentBinary`: try `<DataDir>/agent-binaries/<name>`
|
|
first; on `os.Stat` ENOENT, try
|
|
`<BundledAssetsDir>/agent-binaries/<name>`; on second ENOENT,
|
|
existing 404.
|
|
- `handleInstallAsset`: same dual-path, with `install/` subpath.
|
|
3. Tests in `internal/server/http/agent_assets_test.go` (new file):
|
|
- DataDir hit serves DataDir bytes.
|
|
- DataDir miss + bundled hit serves bundled bytes.
|
|
- DataDir hit shadows bundled.
|
|
- Both miss → 404 + existing error envelope.
|
|
- Path-traversal still rejected for `install/*` (regression check).
|
|
|
|
**Verify:** `go vet ./...` + `go test ./internal/server/http/...`.
|
|
|
|
---
|
|
|
|
## Slice 2 — Version ldflags on both binaries
|
|
|
|
1. `cmd/server/main.go`: keep `var version`, add
|
|
`var commit = "none"` and `var date = "unknown"`. Surface via
|
|
existing version-log line.
|
|
2. `cmd/agent/main.go`: same three vars. Agent already reports
|
|
`agent_version` in the WS hello — extend to include commit if
|
|
it's already plumbed through `internal/api`; otherwise leave the
|
|
commit out of the wire and just log it on startup.
|
|
3. `Makefile`: extend the `make build` `-ldflags` to set all three
|
|
from `git describe --tags --always` + `git rev-parse HEAD` +
|
|
UTC timestamp. Source-build users get real values, not "dev".
|
|
4. `deploy/Dockerfile.server`: add `ARG COMMIT=none` and
|
|
`ARG DATE=unknown`; pass through `-ldflags`.
|
|
|
|
**Verify:** `make build && ./bin/restic-manager-server -version`
|
|
(or whatever the existing flag is) prints non-`dev` values.
|
|
|
|
---
|
|
|
|
## Slice 3 — Dockerfile bakes agents + install assets
|
|
|
|
1. Build stage cross-compiles three agents:
|
|
|
|
```dockerfile
|
|
RUN go build -trimpath -ldflags="-s -w \
|
|
-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" \
|
|
-o /out/agent/restic-manager-agent-linux-amd64 ./cmd/agent
|
|
ENV GOARCH=arm64
|
|
RUN go build ... -o /out/agent/restic-manager-agent-linux-arm64 ./cmd/agent
|
|
ENV GOOS=windows GOARCH=amd64
|
|
RUN go build ... -o /out/agent/restic-manager-agent-windows-amd64.exe ./cmd/agent
|
|
```
|
|
|
|
(Reset `GOOS`/`GOARCH` between layers via `ENV`. Server build
|
|
stays at `GOOS=linux GOARCH=$TARGETARCH`.)
|
|
|
|
2. Final stage `COPY --from=build`:
|
|
- `/out/restic-manager-server` → `/usr/local/bin/`
|
|
- `/out/agent/*` → `/opt/restic-manager/dist/agent-binaries/`
|
|
- `deploy/install/install.sh` →
|
|
`/opt/restic-manager/dist/install/install.sh`
|
|
- `deploy/install/install.ps1` →
|
|
`/opt/restic-manager/dist/install/install.ps1`
|
|
- `deploy/install/restic-manager-agent.service` →
|
|
`/opt/restic-manager/dist/install/restic-manager-agent.service`
|
|
|
|
3. Set `--chmod=0755` on the agent binaries and `install.sh`,
|
|
`--chmod=0644` on the unit file and `install.ps1`. Distroless
|
|
final stage runs as `nonroot`; bundled assets are readable by
|
|
anyone (mode `o+r`), so the user switch doesn't break reads.
|
|
|
|
**Verify:**
|
|
```sh
|
|
docker build -f deploy/Dockerfile.server -t rm:dev .
|
|
docker run --rm -d -p 18080:8080 \
|
|
-e RM_LISTEN=:8080 -e RM_DATA_DIR=/data \
|
|
-e RM_BASE_URL=http://127.0.0.1:18080 \
|
|
-v rm-test:/data rm:dev
|
|
curl -fsSL "http://127.0.0.1:18080/agent/binary?os=linux&arch=amd64" | wc -c
|
|
curl -fsSL "http://127.0.0.1:18080/install/install.sh" | head -1
|
|
```
|
|
|
|
Both should succeed against a fresh volume (no operator staging).
|
|
|
|
---
|
|
|
|
## Slice 4 — Release workflow
|
|
|
|
`.gitea/workflows/release.yml` per the spec. Two jobs:
|
|
|
|
1. **`image`**: checkout → setup-qemu → setup-buildx → login → compute
|
|
tags → buildx build+push.
|
|
2. (Future) `release-notes`: stub left as a TODO comment for now.
|
|
Operator can hand-write release notes via the Gitea UI on first
|
|
cut.
|
|
|
|
The `compute tags` shell step is the only non-trivial bit; tested
|
|
inline by running the script with mocked `GITHUB_REF_TYPE` /
|
|
`GITHUB_REF_NAME` env vars before committing.
|
|
|
|
**Verify on first dispatch:** trigger `workflow_dispatch` from the
|
|
Gitea UI, check the runner produces `:snapshot-<sha>` and pushes
|
|
multi-arch.
|
|
|
|
---
|
|
|
|
## Slice 5 — Tasks.md + commit + push
|
|
|
|
1. `tasks.md`: tick P5-03; add a one-line note that goreleaser was
|
|
dropped in favour of Docker-only after a 2026-05-05 design pass
|
|
(link the spec).
|
|
2. `git add -A && git commit -m "p5-03: docker-only release path"`
|
|
(no Co-Authored-By trailer — CLAUDE.md rule).
|
|
3. `git push -u origin p5-03-docker-release`.
|
|
4. **Stop.** Do not open a PR. Wait for operator review.
|