Files
Anthias/docker
Viktor Petersson 861a29d38d refactor(ci): release flow per #2769 (master = testing, releases = stable) (#2854)
* refactor(ci): release flow per #2769 (master = testing, releases = stable)

Master push now publishes container images only. Balena cloud deploy
and disk-image build move to a release-triggered workflow so existing
fleet devices update on cut releases instead of every merge to master.
rpi-imager.json is generated once per release and shipped as a release
asset; the website fetches it at build time instead of regenerating
from the GitHub API on every deploy.

- docker-build.yaml: drop the balena: job
- build-balena-disk-image.yaml: trigger on release.published, add
  balena-cloud-deploy job (replaces deprecated deploy-to-balena-action),
  bump balena-cli 22.4.15 -> 25.1.3, install via bun, two-phase release
  upload so build_pi_imager_json sees per-board snippets
- deploy-website.yaml: drop rpi-imager.json regeneration + test job;
  fetch it from the latest release instead
- build_pi_imager_json.py: honour RELEASE_TAG env to bypass
  /releases/latest (which excludes prereleases by design)

Also strips third-party action dependencies from new code (manual
docker login, bun install, balena-cli install).

Refs #2769

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): address Copilot review on PR #2854

- deploy-website: download rpi-imager.json by tag on release-triggered
  runs (previously: always default-latest, which can skip prereleases
  and may not match the just-published release)
- deploy-website: drop the now-stale prerelease comment
- build-balena-disk-image: pin Bun via BUN_VERSION env so disk-image
  builds and balena deploys are reproducible
- generate-openapi-schema: accept an optional `ref` input via
  workflow_call and check that out, so the schema attached to a
  release matches the release commit (not the default branch)
- python-lint: run rpi-imager generator tests so the package keeps a
  PR-time CI gate after the deploy-website test job was removed
- build_pi_imager_json: reword RELEASE_TAG-override comment

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): address Copilot round-2 review on PR #2854

- build-balena-disk-image: capture BUILD_DATE once at the top of the
  packaging step so a midnight-spanning run can't reference different
  filenames produced earlier
- build-balena-disk-image: workflow_dispatch now fails loudly when
  the input tag has no existing GitHub release, matching the input
  contract; release event always satisfies it on its own trigger
- bun install: extract to .github/workflows/scripts/install-bun.sh,
  which downloads the pinned release archive + SHASUMS256.txt and
  verifies SHA-256 instead of piping a remote shell script to bash
- deploy-website: re-introduce the strong jq -e validations on
  rpi-imager.json (os_list array, required fields, numeric sizes,
  https URLs, no pi1) so a malformed release asset fails fast
- resolve-context: drop the unused `commit` output

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): address Copilot round-3 review on PR #2854

- install-bun.sh: append \$HOME/.bun/bin to GITHUB_PATH so globally-
  installed CLIs (e.g. balena-cli via \`bun install -g\`) resolve in
  subsequent steps. Without this, the disk-image workflow's balena
  invocations would fail with command-not-found.
- deploy-website: distinguish "release exists but lacks
  rpi-imager.json" (transition fallback) from transient errors
  (auth/rate-limit/network). Probe via gh release view --json assets
  before download; only fall back when the asset is genuinely
  missing. Other gh failures now propagate instead of silently
  shipping an empty os_list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): address Copilot round-4 review + tighten path triggers

- build-balena-disk-image: pin git rev-parse to --short=7 so the
  resolved short hash always matches the 7-char tag format that
  docker-build.yaml writes (a longer abbreviation would silently
  reference image tags that never exist)
- deploy-website: drop the `release: published` trigger. The disk-
  image workflow now ends with `gh workflow run deploy-website.yaml`
  after rpi-imager.json has been uploaded to the release, so the
  deploy is guaranteed to see the asset and won't ship an empty
  os_list during the upload-step window
- deploy-website: add `.github/workflows/scripts/install-bun.sh` to
  the path triggers so changes to the bun installer also redeploy
  the site (it's a runtime dep)
- docker-build / generate-openapi-schema: exclude
  `tools/raspberry_pi_imager/**` and the bun installer script from
  triggers — neither workflow uses those files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): name release artefacts \`anthias-<board>\` so the imager regex matches

build_pi_imager_json.get_board_from_url's regex
\`-(pi\d(?:-\d+)?)\.img\.zst\$\` only matches a hyphen before \`piN\`.
The disk-image workflow had been writing artefacts as
\`raspberrypi3.img.zst\` / \`raspberrypi4-64.img.zst\` (no hyphen
between \`raspberry\` and \`pi\`), so all boards except pi2 silently
failed to be picked up by the consolidation step — likely the root
of the broken rpi-imager.json the user flagged.

Renames the per-board release artefacts to
\`<date>-anthias-<board>.img.zst\` (and matching \`.sha256\` /
\`.json\`) so the existing regex picks them up. Tests already
covered the \`anthias-piN\` shape, so they pass without changes.
Updates the upload-artifact + attestation glob patterns
accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): address Copilot round-6 review on PR #2854

- Move expression substitutions in resolve-context to env vars and
  switch the dispatch-tag read from `inputs.tag` to
  `github.event.inputs.tag`, so the `inputs` context is only consulted
  on workflow_dispatch where it's actually populated.
- Add `actions: write` permission to build-rpi-imager-json so its
  `gh workflow run deploy-website.yaml` fan-out has the Actions API
  scope it needs to dispatch the website deploy.
- Split the openapi-schema checkout ref resolution into a dedicated
  step that uses env vars + `if -n` rather than the inline
  `${{ inputs.ref || github.ref }}` expression, so the inputs lookup
  is co-located with its fallback in one readable shell block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(ci): fix stale install-bun.sh header comment

The header described the runners as linux/amd64-only and asked
maintainers to extend the platform detection if that changed, but the
arch case below already covers both x86_64 and aarch64 Linux. Reword
the comment so it matches the script's actual behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): drop hard-coded --repo from deploy-website gh calls

`gh release view/download` default to the runtime repository when
`--repo` is omitted, so explicitly pinning Screenly/Anthias was making
the workflow needlessly less portable to forks (or a future repo
rename) without buying anything. Match the rest of the workflow,
which already relies on the runtime repo context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(ci): address Copilot round-9 review on PR #2854

- Gate build-balena-disk-image.yaml's release trigger to Anthias-core
  tags (`v<version>`). build-webview.yaml publishes its own
  `WebView-v<version>` GitHub releases on tag pushes; without this
  guard, every webview release would have spuriously fanned out to
  balena OTA deploys + disk-image builds. Filter is on resolve-context
  so the entire downstream pipeline cascades-skips via `needs:`.
- Cache sha256 + size of each multi-GB image once and reuse for both
  the .sha256 sidecar and the per-board JSON snippet, instead of
  re-hashing the same files inside jq's --arg expansions. Roughly
  halves the wall-clock of the package step.
- Add `tools/raspberry_pi_imager` to .dockerignore. The directory is
  build-time-only (CI generator for rpi-imager.json) but
  Dockerfile.{server,viewer}.j2 do `COPY . /usr/src/app/`, so without
  this entry it baked into runtime images. With docker-build.yaml's
  matching path-trigger exclusion in place, this keeps the two
  filters semantically honest: a tools-only commit truly cannot
  change image content, so skipping the container rebuild is correct
  rather than a footgun.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): write the .sha256 sidecar against user-facing filenames

The uncompressed-image line previously referenced
\`\$BALENA_IMAGE.img\` (e.g. \`raspberrypi5.img\`), the CI-local
intermediate name. That file never ships in the release asset, so
\`sha256sum -c\` against the downloaded sidecar fails to find it.
Switch to \`\$ARTIFACT.img\` — the filename a user gets after
\`zstd -d <ARTIFACT>.img.zst\` — so both lines match files they
actually have on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): call .venv/bin/pytest directly in python-lint job

\`uv run --group website pytest …\` implicitly syncs the project
venv with the default group set, which pulls in the \`dev\` group
(pytest-django==4.12.0). pytest-django then auto-activates as a
plugin, reads \`DJANGO_SETTINGS_MODULE\` from pyproject.toml, and
fails to bootstrap Django because the curated dev-host + website
install doesn't ship pytz / channels / the other transitive bits
the settings module imports.

Invoke the venv binary directly so the minimal hand-curated env
above is what the rpi-imager unit tests actually run against. The
tests don't need Django at all — this keeps the gate fast and the
dependency surface honest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): pass -p no:django to the rpi-imager pytest invocation

The previous attempt — calling \`.venv/bin/pytest\` directly instead
of \`uv run\` — assumed the dependency-installation step bounded the
venv contents. It doesn't: the earlier \`uv run ruff check\` step
implicitly syncs the project venv with the default \`dev\` group,
which ships pytest-django==4.12.0 + playwright + etc. By the time
the rpi-imager step runs, pytest-django is sitting in .venv as an
auto-loading pytest plugin, reads \`DJANGO_SETTINGS_MODULE\` from
pyproject.toml, and crashes trying to bootstrap Django (pytz,
channels, etc. are missing in this minimal env).

The rpi-imager unit tests don't need Django at all, so disable the
plugin with \`-p no:django\`. Verified locally: 22/22 pass with
pytest-django installed in the venv as long as the plugin is
disabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(x86): support balenaOS x86 fleets via Wayland (#2857)

* feat(x86): support balenaOS x86 fleets via Wayland (#2075)

Brings x86 to feature parity with Pi for balenaOS deployments.
balenaOS x86 doesn't expose /dev/fb0, so Qt's linuxfb plugin (used on
Pi) has nothing to draw to and there's no host display server. Run Qt
under Wayland via `cage`, a kiosk wlroots compositor that talks
directly to KMS — no X server, no DISPLAY juggling, single-app by
design.

- bin/deploy_to_balena.sh accepts -b x86 and strips /dev/vchiq from
  the rendered compose (same conditional that already covers pi5).
- docker/Dockerfile.viewer.j2 sets QT_QPA_PLATFORM=wayland on x86;
  every other board keeps linuxfb.
- tools/image_builder/utils.py adds cage + qt6-wayland to the x86
  viewer apt list.
- bin/start_viewer.sh wraps the viewer launch in `cage --` on x86;
  WAYLAND_DISPLAY is added to sudo's --preserve-env so it survives
  the env scrub when dropping to the viewer user.
- .github/workflows/build-balena-disk-image.yaml extends the
  release-driven preflight, balena-cloud-deploy, and
  balena-build-images jobs to include x86 (fleet anthias-x86, balena
  device type genericx86-64-ext). build-rpi-imager-json is
  unchanged: the .img.zst regex is Pi-only, so x86 ships on the
  release without polluting the Raspberry Pi Imager JSON.

Supersedes the stale draft PR #2409. The orphaned changes there
(home.tsx deviceModel fetch with no consumer, viewer/media_player.py
x86 audio table, silent removal of sha256sum -c on the webview
tarball) are intentionally not carried forward.

Closes #2075

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(x86): note x86 wayland exception in viewer apt comment

Address Copilot review on PR #2857. The earlier comment in
get_viewer_context claimed "nothing wayland-related here" — that's
no longer true once x86 pulls in cage + qt6-wayland a few lines
down. Rewrite to call out x86 as the one board that breaks the rule
so future cleanup doesn't try to drop the wayland deps thinking they
were a mistake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:54:37 +01:00
..