Commit Graph

5 Commits

Author SHA1 Message Date
Viktor Petersson
10c68b26cc feat(viewer,build,balena): add arm64/Qt6 pi3-64 board and the Rock Pi 4 fleet; keep 32-bit pi3 as legacy (#2985)
* feat(viewer,build): add arm64/Qt6 pi3-64 board; keep 32-bit pi3 as legacy

Revises issue #2906 Phase 2. The original plan (delete the Qt 5 toolchain,
force Pi 2/Pi 3 onto Qt 6) is abandoned: Qt 5 was fixed up on master and
stays. Instead, add a NEW board target `pi3-64` — a 64-bit (arm64) Qt 6
viewer image for Raspberry Pi 3 hardware on a 64-bit OS — as its own image
stream, disk image, and balena fleet. The legacy 32-bit armhf/Qt5 `pi3`
board is left untouched and flagged as legacy/maintenance.

pi3-64 mirrors the existing `pi4-64` path (Qt 6, eglfs_kms; video played
in-process by AnthiasViewer's QtMultimedia pipeline — QMediaPlayer + the
ffmpeg/libavcodec backend with V4L2 HW decode, no external player).
VideoCore IV is H.264-only HW decode. Board selection is by `uname -m`: a
Pi 3 on a 64-bit OS gets `pi3-64`, a 32-bit OS keeps `pi3` (the model
string is identical on both arches).

- image_builder: pi3-64 build params (arm64) + is_qt6; constants.
- Dockerfile.viewer.j2 + start_viewer.sh: pi3-64 shares the pi4-64 eglfs
  KMS path; renamed board-agnostic eglfs-kms-pi4.json -> eglfs-kms.json.
- Detection: install.sh / upgrade_containers.sh (aarch64 Pi 3 -> pi3-64).
- Runtime: media_player force_mpv set (selects MPVMediaPlayer, the
  QtMultimedia D-Bus shim); processing codec grid {'h264'}.
- CI: docker-build matrix + mirror-latest-tags.
- Balena (fleet screenly_ose/anthias-pi3-64, device type raspberrypi3-64):
  disk-image + manual-deploy workflows, balena_ota_deploy.sh,
  balena_fleet_maintenance.py, balena_unpin_devices.py, deploy_to_balena.sh,
  balena-host-config.json.
- Pi Imager: SUPPORTED_BOARDS += pi3-64 (non-maintenance); pi3 stays legacy.
- Docs + tests.

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

* docs(website): link the Pi 3 (64-bit) bullet like its siblings

Copilot review: the list is introduced as 'links to the images', so the
new pi3-64 entry should be navigable like the surrounding bullets. Link
the label to the release-images section.

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

* feat(balena): add the Rock Pi 4 fleet (screenly_ose/anthias-rockpi4)

Wires the anthias-rockpi4 balena fleet (device type rockpi-4b-rk3399)
into the OTA deploy + disk-image pipeline. The fleet has no
board-specific image build: it runs the generic arm64 containers, so
bin/balena_ota_deploy.sh / bin/deploy_to_balena.sh map the rockpi4
board to the <short-hash>-arm64 image tags (and strip the /dev/vchiq
mount — no VideoCore on RK3399), and the disk-image preflight verifies
the arm64 images exist.

Root-cause fix for the fleet's codec gate: balena ships no
anthias_host_agent service, so host:board_subtype was never published
and resolve_device_key() stayed 'arm64' — whose HW-decode set is empty,
rejecting every video upload. The model-string → subtype table moves to
the dependency-free anthias_common.device_helper.detect_board_subtype
(single source, imported by host_agent), and
anthias_common.board.get_board_subtype now falls back to reading
/proc/device-tree/model in-container when Redis has no value. The
device tree is kernel-global — the same mechanism get_device_type has
always used for Pi detection — so the rockpi4 fleet resolves its
{h264, hevc} envelope without a host-side daemon, and compose installs
whose host_agent died self-heal too.

- build-balena-disk-image.yaml: rockpi4 in both matrices, fleet +
  rockpi-4b-rk3399 image cases, arm64 images in the preflight check.
- deploy-balena-manual.yaml: rockpi4 board option.
- balena-host-config.json: rockpi4 declared {} (config.txt is
  RPi-only; the reconcile hard-fails on a missing key).
- balena_fleet_maintenance.py / balena_unpin_devices.py: fleet added.
- tests: get_board_subtype Redis-first + device-tree-fallback order;
  detect_board_subtype patch targets follow the move.
- docs: board-enablement, balena-fleet-host-config,
  installation-options.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 07:49:12 +02:00
Viktor Petersson
7f8bbe43d7 feat(install): generic-arm64 best-effort support (Armbian SBCs) (#2879)
* feat(install): generic-arm64 best-effort support (Armbian on Rock Pi, Orange Pi, …)

Wires up a `generic-arm64` device_type so the installer recognises any
aarch64 host that isn't a Raspberry Pi and runs the same Anthias stack
on it. Closes #2849 (Tier 1).

* `bin/install.sh::set_device_type` + `bin/upgrade_containers.sh` get
  an `aarch64` fallback branch, INTRO_MESSAGE / unsupported-message
  copy refreshed, raspberry-pi-tagged ansible tasks skipped on
  generic-arm64 (same as x86), vchiq strip extended.
* ansible: validated set in `site.yml`, `docker_arch_by_device_type`
  gains `generic-arm64: arm64`. `docker-buildx-plugin` added to the
  apt-install list — required for MODE=build with `--platform=`
  Dockerfiles, harmless on pull-mode boards. Pre-existing host_agent
  service unit hardcoded `~/installer_venv/bin/python` (an ephemeral
  tmpdir post-#2843); split into a persistent `~/.anthias-venv` that
  ansible syncs before installing the unit.
* image_builder: `generic-arm64` build target, Qt6 + cage + wayland
  like x86; `va-driver-all` deliberately *not* shipped — Rockchip /
  Allwinner / Amlogic mainline hwdec goes through V4L2 M2M /
  request API, not VAAPI, so mesa-va-drivers would be dead weight.
* viewer: `start_viewer.sh` reuses the x86 cage path for
  generic-arm64; `media_player.py` routes generic-arm64 to MPV (the
  `device_helper.get_device_type()` fallback returns 'pi1' on
  non-Pi aarch64 hosts, so the proxy needs the DEVICE_TYPE env
  override that pi4-64 already uses). New test added.
* host_agent: `SUPPORTED_INTERFACES` gains `end` prefix —
  Rockchip GMAC etc. surface as `end0` on systemd predictable
  naming, which was previously filtered out, leaving the splash
  page stuck on "Detecting network…".
* CI: docker-build matrix + mirror-latest-tags publish
  `latest-generic-arm64` alongside the existing per-board tags.
* Docs: README, marketing site supported-hardware table, and FAQ
  get a plain-language "Yes, on a best-effort basis" entry that
  spells out the software-decode trade-off, the SoCs known to work
  well (RK3399 / RK35xx / Allwinner H6 / Amlogic GXBB-GXL-GXM /
  S905X3), and the boards to avoid (Allwinner H616 / H618). Per-SoC
  hardware decode (`rkmpp`, `cedrus`, `meson-vdec`) is the planned
  Tier-2 follow-up.

Validated end-to-end on a Rock Pi 4B (Armbian trixie, RK3399, 1GB
RAM) via build-on-device: install completes, web UI reachable, all
four asset types (image, H.264 1080p60, H.265 1080p60, webpage)
cycle through the viewer cleanly, mpv pure-decode benchmark shows
0 dropped frames over the full 60s of each clip.

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

* fix(ansible-lint): pair become with become_user on .anthias-venv sync task

ansible-lint's partial-become rule fires on `become_user:` without a
matching `become:` at the same level, even when the play-level become
already covers it. Explicit pairing keeps lint quiet without changing
runtime behaviour.

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

* fix: address Copilot review feedback on generic-arm64 PR

- ansible: drop `creates:` guard on the runtime venv sync — `uv sync`
  is idempotent (sub-second resolver check when nothing changed), so
  re-running unconditionally means dependency updates from a
  pyproject.toml / uv.lock change actually land on upgrade instead of
  silently skipping. Idempotency surfaced via `changed_when` keyed on
  uv's `+/-/~` package-action prefix so steady-state runs stay `ok`.
- ansible: rework docker-buildx-plugin comment to justify the
  install on its own merits (any MODE=build run needs it because of
  `FROM --platform=$BUILDPLATFORM` in Dockerfiles) rather than tying
  it to generic-arm64 lacking published tags — that explanation
  becomes stale the moment this PR merges and CI publishes them.
- viewer: `get_alsa_audio_device()` short-circuits on
  `DEVICE_TYPE=generic-arm64` before the Pi-firmware dispatch, since
  the Rock Pi / Orange Pi / Banana Pi class of board has none of the
  `vc4hdmi*` or `Headphones` ALSA cards. Defers to ALSA's `default`
  device; operators with a non-standard sink can override via
  `~/.asoundrc` (already bind-mounted into the viewer container).
- tests: new assertions that generic-arm64 routes mpv through
  `--vo=gpu --gpu-context=wayland` and `--audio-device=alsa/default`.

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

* fix(website): disambiguate Debian release codenames in supported-hardware copy

Copilot flagged the previous wording — "running Raspberry Pi OS, Debian, or
Armbian (Trixie or Bookworm)" — as misleading: the parenthetical reads as
if Raspberry Pi OS and Armbian are themselves "Trixie or Bookworm", but
those are Debian codenames, and Armbian builds can also be Ubuntu-based.
Split the sentence so the codenames are tied explicitly to Debian.

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

* fix(ansible): derive is_raspberry_pi from device_type, not architecture

Copilot caught that the `is_raspberry_pi` helper in docker.yml was
defined as `ansible_architecture in ['aarch64', 'armv7l', 'armv6l']`,
which is also true on generic-arm64 (Rock Pi / Orange Pi / …). That
silently applied the Pi-only `gpio` group to non-Pi SBCs.

device_type is the authoritative discriminator and is validated
upstream in ansible/site.yml's pre_tasks, so use it directly.

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

* refactor: rename device_type generic-arm64 → arm64 (parallel to x86)

Per review feedback: `generic-arm64` was the original working name for
the new aarch64 non-Pi fallback. `arm64` is shorter and parallels `x86`
— both are architecture-generic device_types that catch any host
without a board-specific image, sitting alongside the per-board labels
(pi2 / pi3 / pi4-64 / pi5). User-facing prose still says "generic
64-bit ARM" or "Armbian on Rock Pi / Orange Pi / …" for context.

Mechanical s/generic-arm64/arm64/ across install scripts, ansible,
image_builder, viewer / start_viewer, host_agent, tests, CI matrix,
mirror-latest-tags, Dockerfile.viewer.j2, README.

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

* review polish on arm64 PR

- viewer: get_alsa_audio_device's arm64 short-circuit now logs the
  registered ALSA cards (from /proc/asound/cards — aplay isn't in
  the viewer image) once per process when DEVICE_TYPE=arm64, so an
  operator reporting "no HDMI audio" carries enough breadcrumbs in
  journalctl alone to pick the right ~/.asoundrc override.
- ansible: rewrite the docker-buildx-plugin size claim — 15 MB
  download / 67 MB extracted, from the deb metadata on arm64.
- viewer: MediaPlayerProxy.get_instance comment block split into a
  two-bullet rationale, calling out the pi4-64 and arm64 cases
  separately so a future reader doesn't mistake the lead sentence
  for "pi4-64-only".
- install.sh / upgrade_containers.sh: spell out that the aarch64
  catch-all in set_device_type is intentional — a future Pi model
  whose model string drifts past the regexes lands here too,
  trading software decode + no Pi-boot tweaks for a louder fail.
- README + FAQ: tighten the Plymouth caveat from "few seconds of
  black" to "kernel boot log scrolls until the viewer takes over",
  which is what actually happens on most U-Boot ARM SBCs.
- ansible: rename the docker.yml var from `is_raspberry_pi` to
  `device_is_pi` now that it's derived from device_type rather
  than `ansible_architecture`, so the name matches what it does.

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

* docs: narrow arm64 support to Debian-based Armbian (call out Ubuntu)

Copilot flagged that "Armbian" in the new docs is ambiguous —
Armbian builds come in both Debian-based (Bookworm/Trixie) and
Ubuntu-based (Jammy/Noble) flavours. The installer's ansible role
wires Docker's apt repo under
download.docker.com/linux/debian/{{ ansible_distribution_release }},
which 404s on the Ubuntu codenames, so an Ubuntu-Armbian user
following the current docs would hit a broken install at the very
first `apt update`.

Narrowing the wording in README, the marketing site's
supported-hardware blurb, and the FAQ to "Debian-based Armbian" so
users pick the right image. Extending the installer/playbook to
handle Ubuntu-based Armbian is a separate follow-up.

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-13 10:05:52 +01:00
Viktor Petersson
409117f24b fix(ci): symlink bunx alongside bun in install script (#2865)
* fix(ci): symlink bunx alongside bun in install script

The pinned-version install script only laid down /usr/local/bin/bun,
but bun dispatches on argv[0] and the upstream bun.sh/install script
also creates a `bunx` symlink. Without it, scripts that invoke `bunx`
(e.g. website/package.json's css:build calling
`bunx @tailwindcss/cli`) fail with "bunx: command not found", which
has been breaking the Anthias.screenly.io deploy since #2854 landed.

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

* fix(ci): correct comment — bunx symlink comes from setup-bun

The previous commit message and inline comment attributed the bunx
symlink to bun.sh/install. That's wrong: the upstream installer only
lays down `bun`. The shim came from oven-sh/setup-bun, the action
this script replaced. Same fix, accurate provenance.

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 11:57:38 +01:00
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
Viktor Petersson
83a1224044 ci(docker): split latest-tag mirror per registry, soft-fail Docker Hub (#2800)
Master pushes have been red-Xing because Docker Hub's free-tier pull
rate limit periodically 429s the manifest GETs that imagetools create
issues. GHCR is the canonical primary; failing the whole publish job
because the parallel Docker Hub mirror flaked is needless noise.

Split the single Mirror step into two: GHCR (hard-fail) followed by
Docker Hub (continue-on-error). Extract the shared mirror logic into
.github/workflows/scripts/mirror-latest-tags.sh, parameterized on a
NAMESPACE env var so each step runs the same preflight + retag
sequence against its own registry.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:07:48 +01:00