feat(dist): M1 Docker distribution, update checking, release tags (BUNYIP-3) #6

Merged
nrupard merged 3 commits from feat/m1-distribution-BUNYIP-3 into main 2026-05-22 16:30:36 +02:00
Owner

Completes the BUNYIP-3 (M1) deliverables that were unbuilt when the issue was previously marked Done. The image build/publish pipeline already existed (PR #2 / DEV-331); this fills the remaining scope.

What was missing vs. acceptance criteria

  • AC "operator can docker compose up against a published image and reach a working instance": only dev compose existed (build-from-source). Now there is a production compose.yml.
  • AC "the instance reports whether an update is available": no version/update-check endpoint existed. Now GET /version reports it.
  • AC "applying an update is a deliberate operator action": documented, no auto-update.

Changes

  • compose.yml + oci-build/Caddyfile: production reference deployment running the published images behind an edge Caddy. The edge serves the SPA at ${BUNYIP_HOST} and routes msp-api.${BUNYIP_HOST} to the API, matching the SPA's runtime backend derivation in bunyip-web/src/stores/config.rs. TLS via Let's Encrypt.
  • GET /version (new version module + route): reports running version, build revision (BUNYIP_GIT_SHA baked into the API image), and update_available. Polls BUNYIP_UPDATE_CHECK_URL (Forgejo releases/latest shape) at most hourly, cached. Empty URL disables checking. Unit tests cover semver compare + tag parsing.
  • build.yml: multi-arch capability via the BUNYIP_BUILD_PLATFORMS CI variable (default linux/amd64); also triggers on v* tags so released versions publish version-tagged images. bunyip-web/oci-build/Dockerfile is now arch-portable (TARGETARCH-driven binstall).
  • create-release.yml (new): the workflow the just create-release recipe already references. On a Cargo.toml version bump merged to main it tags vX.Y.Z and publishes a Forgejo release, re-triggering build.yml.
  • README.md + .env.example: full self-host and update-flow docs.

Notes for review

  • No local Rust toolchain was available, so the API code was not compiled here; CI (cargo fmt/clippy/check) is the gate. Cargo.lock was not regenerated for the new direct reqwest dep (all rustls crates were already present transitively); CI's non-locked cargo run resolves the edge.
  • create-release.yml is untested in CI and assumes PSA_SYSTEMS_PRIVATE_PACKAGE_PAT has repository content read/write scope (the auto-injected token cannot push a tag that re-triggers another workflow).
  • Multi-arch defaults to amd64 on purpose: the single amd64 runner would do emulated arm64 Rust builds (slow). Flip BUNYIP_BUILD_PLATFORMS once a native arm64 runner exists.
Completes the BUNYIP-3 (M1) deliverables that were unbuilt when the issue was previously marked Done. The image build/publish pipeline already existed (PR #2 / DEV-331); this fills the remaining scope. ## What was missing vs. acceptance criteria - AC "operator can `docker compose up` against a published image and reach a working instance": only dev compose existed (build-from-source). Now there is a production `compose.yml`. - AC "the instance reports whether an update is available": no version/update-check endpoint existed. Now `GET /version` reports it. - AC "applying an update is a deliberate operator action": documented, no auto-update. ## Changes - `compose.yml` + `oci-build/Caddyfile`: production reference deployment running the published images behind an edge Caddy. The edge serves the SPA at `${BUNYIP_HOST}` and routes `msp-api.${BUNYIP_HOST}` to the API, matching the SPA's runtime backend derivation in `bunyip-web/src/stores/config.rs`. TLS via Let's Encrypt. - `GET /version` (new `version` module + route): reports running version, build revision (`BUNYIP_GIT_SHA` baked into the API image), and `update_available`. Polls `BUNYIP_UPDATE_CHECK_URL` (Forgejo `releases/latest` shape) at most hourly, cached. Empty URL disables checking. Unit tests cover semver compare + tag parsing. - `build.yml`: multi-arch capability via the `BUNYIP_BUILD_PLATFORMS` CI variable (default `linux/amd64`); also triggers on `v*` tags so released versions publish version-tagged images. `bunyip-web/oci-build/Dockerfile` is now arch-portable (`TARGETARCH`-driven binstall). - `create-release.yml` (new): the workflow the `just create-release` recipe already references. On a `Cargo.toml` version bump merged to main it tags `vX.Y.Z` and publishes a Forgejo release, re-triggering `build.yml`. - `README.md` + `.env.example`: full self-host and update-flow docs. ## Notes for review - No local Rust toolchain was available, so the API code was not compiled here; CI (`cargo fmt`/`clippy`/`check`) is the gate. `Cargo.lock` was not regenerated for the new direct `reqwest` dep (all rustls crates were already present transitively); CI's non-locked `cargo` run resolves the edge. - `create-release.yml` is untested in CI and assumes `PSA_SYSTEMS_PRIVATE_PACKAGE_PAT` has repository content read/write scope (the auto-injected token cannot push a tag that re-triggers another workflow). - Multi-arch defaults to amd64 on purpose: the single amd64 runner would do emulated arm64 Rust builds (slow). Flip `BUNYIP_BUILD_PLATFORMS` once a native arm64 runner exists.
feat(dist): M1 Docker distribution, update checking, release tags (BUNYIP-3)
All checks were successful
build / Build and push OCI images (pull_request) Has been skipped
build / Lint and type-check (pull_request) Successful in 38s
e5108b2d56
Complete the BUNYIP-3 deliverables that were missing when the issue was marked Done.

Adds a production reference deployment (compose.yml) that runs the published OCI images behind an edge Caddy. The edge serves the SPA at ${BUNYIP_HOST} and routes msp-api.${BUNYIP_HOST} to the API container, matching the SPA's runtime backend derivation in bunyip-web/src/stores/config.rs. Operators deploy with `docker compose up`; applying an update is a deliberate `docker compose pull && docker compose up` action (no auto-update).

Adds operator-facing update checking: GET /version reports the running version, build revision (BUNYIP_GIT_SHA baked into the API image), and whether a newer release is published. The checker polls BUNYIP_UPDATE_CHECK_URL (Forgejo releases/latest shape) at most hourly and caches the result; unset URL disables checking.

Wires multi-arch capability into build.yml via the BUNYIP_BUILD_PLATFORMS CI variable (defaults to linux/amd64 to avoid forcing emulated arm64 Rust builds on the single amd64 runner). The web Dockerfile is now arch-portable (TARGETARCH-driven cargo-binstall bootstrap).

Adds the create-release.yml workflow that the justfile create-release recipe already references: on a Cargo.toml version bump merged to main it tags vX.Y.Z and publishes a Forgejo release, which re-triggers build.yml to publish version-tagged images. build.yml now also triggers on v* tags.

Documents the full self-host and update flow in README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(dist): address PR review findings (BUNYIP-3)
All checks were successful
build / Build and push OCI images (pull_request) Has been skipped
build / Lint and type-check (pull_request) Successful in 27s
3d53c8aed0
Correct the compose image defaults to the private psa-systems-private registry where CI actually publishes (was psa-systems, would 404 on pull); document the required `docker login dev.a8n.run` step.

Fix cross-origin auth: bunyip-api now mirrors the request origin/method/headers and allows credentials instead of CorsLayer::permissive(). The SPA at bunyip.<host> sends credentialed requests to the API at msp-api.<host>; `Allow-Origin: *` is incompatible with credentials, so the prior permissive layer blocked login. Matches mokosh-server's posture.

Cache failed update checks for 60s instead of the full hour so a transient blip doesn't suppress rechecks. Build the reqwest client once (with timeout + User-Agent) instead of per poll.

Make create-release.yml release publishing idempotent and recoverable: it now checks whether the release already exists and tolerates non-2xx via --allow-errors, so a run that pushed the tag but failed before creating the release recovers on re-run.

Align COOKIE_SECRET guidance to 64 chars to match the config doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs: record M1 distribution work + reserve v* tags for release flow (BUNYIP-3)
All checks were successful
build / Build and push OCI images (pull_request) Has been skipped
build / Lint and type-check (pull_request) Successful in 28s
22f1e6a288
Document the BUNYIP-3 completion across the project docs and reconcile the re-added `tags: v*` build trigger with the existing "only main creates packages" policy.

build.yml / create-release.yml: clarify in comments that v* tags are produced exclusively by the create-release automation (humans never push them by hand), so the policy intent holds while versioned images build directly off the release tag.

dev-docs/milestone-1-handoff.md: add a "Distribution completed (PR #6, BUNYIP-3)" section covering the self-host compose.yml, GET /version update checking, create-release automation, multi-arch capability, and the bunyip-api CORS change; amend the Image tag policy section to supersede the old "tag main HEAD, next main-push applies the version" workaround; add the new files to the reference table.

README.md: fix the stale Contributing section (main is the merge target now, not chore/initial-setup; branch naming is feat/fix/chore) and document the release recipe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nrupard deleted branch feat/m1-distribution-BUNYIP-3 2026-05-22 16:30:37 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/bunyip!6
No description provided.