Commit Graph

23 Commits

Author SHA1 Message Date
dependabot[bot]
68f1bc0981 chore(deps): bump the github-actions group with 3 updates (#2994)
Bumps the github-actions group with 3 updates: [actions/checkout](https://github.com/actions/checkout), [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `actions/checkout` from 6.0.2 to 6.0.3
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](de0fac2e45...df4cb1c069)

Updates `astral-sh/setup-uv` from 8.1.0 to 8.2.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](08807647e7...fac544c07d)

Updates `github/codeql-action` from 4.36.0 to 4.36.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](7211b7c807...87557b9c84)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-version: 4.36.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-07 07:22:21 +02: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
d9ebc8051c chore(build): upgrade to Debian Trixie + Python 3.13, drop Balena base images (#2779)
* chore(build): upgrade to Debian Trixie + Python 3.13, drop Balena base images

Move every container off `balenalib/raspberrypi*-debian:bookworm` (Balena
hasn't published a `trixie` tag on any of those repos and last refreshed
in May 2025) onto vanilla `debian:trixie`. Pi 1 and 32-bit Pi 4 are
retired at the same time — Pi 1 has no `linux/arm/v6` variant in upstream
Debian, and Pi 4 always has a 64-bit path that avoids the messy
`libssl1.1` / `libgst-dev` / `libsqlite0-dev` Qt 5 deps. Surviving build
matrix: pi2, pi3, pi4-64, pi5, x86.

For the surviving 32-bit boards (pi2, pi3) the legacy Broadcom userland
(libraspberrypi0 → /opt/vc/lib/{libbcm_host,libmmal,libvchiq_arm}) is
still required at runtime by the Qt 5 webview. Trixie's
archive.raspberrypi.org/debian/main no longer ships those packages
(replaced by raspi-utils + libdtovl0, which actively break
libraspberrypi0), so Dockerfile.base.j2 conditionally writes Deb822
.sources entries pointing at archive.raspberrypi.org/debian trixie main
and archive.raspbian.org/raspbian trixie firmware (where the legacy
Raspbian builds of libraspberrypi0 still live, armhf only). The
.deb-form raspberrypi-archive-keyring + raspbian-archive-keyring packages
are extracted with `dpkg-deb -x` (their bundled keys carry trixie-policy-
compliant binding signatures, unlike the standalone .public.key files
which fail Sequoia/sqv's post-2026-02-01 SHA-1 ban). Architectures: armhf
on each .sources file keeps apt from querying the Pi mirrors for the
arm64 / x86 builds.

Trixie package renames also fixed: libgles2-mesa → libgles2,
ttf-wqy-zenhei → fonts-wqy-zenhei, libpng16-16 → libpng16-16t64 (time64
transition; armhf has no `Provides:` fallback like amd64 does), and the
Qt 5-only libgst-dev / libsqlite0-dev / libsrtp0-dev / libssl1.1 are
dropped (libgstreamer1.0-dev, libsqlite3-dev, libsrtp2-dev, libssl3 take
their place — first added explicitly, the rest already in the main
list). The transitional `git-core` is gone in trixie; `git` covers it.

Python 3.13 (Trixie's default) replaces the 3.11 pin everywhere:
pyproject.toml requires-python and mypy python_version, ruff.toml
target-version, .python-version, uv.lock (regenerated; only diff is
async-timeout dropped — its marker was python<3.11), uv-builder.j2's
UV_PYTHON, Dockerfile.dev's FROM, bin/install.sh's host check, and every
CI workflow's setup-python pin.

Cleanup that falls out: drop the cache_scope / device_type / version_suffix
`pi4 + arm64 → pi4-64` re-mapping (board is now self-identifying), drop
the `c_rehash` workaround in Dockerfile.base.j2 (specific to a Balena
curl bug, not vanilla Debian), drop the dead arm/v6 + arm/v8 branches in
uv-builder.j2 (only arm/v7 remains as the 32-bit ARM target), retire the
old build_qt5.sh `pi1`/`pi4` branches, and delete docker/Dockerfile.celery
(left behind from the celery-image removal in 5e00c8ba).

Out-of-band prereq before merging anything that depends on a viewer
build: cut a new `WebView-v*` release with
webview-{ver}-trixie-{board}.tar.gz (and qt5-5.15.14-trixie-{pi2,pi3}.tar.gz)
for the surviving boards, then bump WEBVIEW_VERSION in
tools/image_builder/utils.py:143. The webview Dockerfiles already point
at debian:trixie, so triggering build-webview.yaml on the new tag should
produce the artifacts.

Verification (proven via real `docker buildx --platform=...` runs):
- x86 server image: full build, runs Debian 13.4 + Python 3.13.5; Django
  5.2.13, channels 4.3.1, uvicorn 0.32.1 all import.
- x86 redis image: Redis 8.0.2 on trixie.
- pi3 (linux/arm/v7 under qemu) server image: full build green — Pi
  apt sources bootstrap works, libraspberrypi0 installs from
  raspbian/firmware/armhf with /opt/vc/lib/* present.
- pi3 (linux/arm/v7 under qemu) viewer image: 147s apt layer green
  end-to-end through libpulse-dev, libgstreamer1.0-dev, libsdl2-dev,
  libpng16-16t64, etc.; build proceeds through uv-builder + main stages
  and stops only at the WebView qt5 tarball fetch (the trixie artifacts
  haven't been cut yet — that's the prereq above).
- ruff check + ruff format --check on tools/image_builder/: clean.

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

* fix(ci): replace distutils.strtobool (3.12+ removal); satisfy SC2129

Two CI failures from the Trixie/3.13 bump fall out of stdlib & lint:

- `lib/utils.py:8` imported `from distutils.util import strtobool`,
  which is gone in Python 3.12+. mypy on 3.13 flagged it as
  import-not-found. Inline the original truthy/falsy table directly in
  `string_to_bool` so every caller keeps accepting the same
  y/yes/t/true/on/1 / n/no/f/false/off/0 set.
- actionlint/shellcheck SC2129 on `.github/workflows/docker-build.yaml`
  in the `Set Docker tag` step I added — three sequential
  `>> "$GITHUB_ENV"` redirects collapse into one `{ ...; } >> $GITHUB_ENV`
  block.

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

* fix(security): HTTPS + SHA256-pin Pi keyring fetch; nuke libcec-dev typo

Address Copilot's review on PR 2779.

- docker/Dockerfile.base.j2 + webview/Dockerfile: switch the Pi/Raspbian
  keyring downloads (and the resulting Deb822 `URIs:` for both apt
  archives) from `http://` to `https://`. Both archives serve TLS
  cleanly today (verified with curl --proto '=https' --tlsv1.2). The
  keyring .deb is the trust anchor for everything fetched after it, so
  the .deb hash is now also pinned via `sha256sum -c -` before
  `dpkg-deb -x` extracts it — TLS alone wouldn't catch an upstream
  archive-side swap. Hashes match the
  raspberrypi-archive-keyring_2025.1+rpt1_all.deb and
  raspbian-archive-keyring_20120528.4_all.deb files served at the time
  this commit lands; bumping either filename is the signal to refresh
  the pin too.
- tools/image_builder/__main__.py: trim the trailing space from
  `'libcec-dev '` in `base_apt_dependencies`. apt is forgiving about it
  but it produces extra whitespace in the rendered Dockerfile and is
  easy to miss in diffs.

Verified by re-running the keyring bootstrap end-to-end on a fresh
debian:trixie linux/arm/v7 container: both .debs pass sha256sum -c, apt
update fetches over HTTPS, and libraspberrypi0 installs from
archive.raspbian.org/raspbian trixie/firmware as before.

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

* fix(sonar): declare USER root explicitly in webview/Dockerfile builder

SonarCloud's docker:S6471 hotspot was already flagging this file on
master (the implicit-root warning lives on every `FROM debian:*` line
without a `USER` directive); my Trixie change shifted the original line
107 to 131 and Sonar re-emitted it as a "new in PR" finding. Resolve
with the rule's recommended escape hatch — declare the user explicitly,
which converts the implicit-default into an acknowledged choice and
silences the rule.

Both stages stay on `USER root`: the builder stage's `dpkg-deb -x` /
`dpkg --purge libraspberrypi-dev` and the runtime stage's writes to
/sysroot, /opt/vc, /root/.pyenv, /usr/local/bin all require root. This
image is a CI-local Qt 5 cross-compile builder that produces the
WebView tarball as a release artifact — it is never deployed, so the
"don't run as root" guidance behind S6471 doesn't apply in the way it
would for a published runtime image.

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

* docs: fix two Copilot-flagged comment inaccuracies

- Dockerfile.base.j2: comment said libraspberrypi0 comes from
  archive.raspbian.org's `rpi` component, but the Deb822 source
  below correctly declares `Components: firmware`. Verified via
  Packages.gz on archive.raspbian.org/dists/trixie/firmware/
  binary-armhf — that's the only component shipping
  libraspberrypi0 on trixie/armhf. Comment now matches reality.

- image_builder/utils.py: Qt 5 branch comment claimed the modern
  equivalents (libgstreamer1.0-dev, libsqlite3-dev, libsrtp2-dev)
  for the dropped trixie packages were "pulled by the main viewer
  apt list above". libsqlite3-dev / libsrtp2-dev are indeed in
  that list, but libgstreamer1.0-dev is Qt 5-only and is added by
  the extend() call right below — corrected the comment to point
  there instead.

Both are pure comment changes; behavior unchanged.

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

* ci(webview): adopt registry-cache backend, mirror docker-build.yaml

Both Docker-build steps in build-webview.yaml had ad-hoc caching that
left the bulk of layer state on the floor:

* `build-docker-image` (Pi 1-4 / Qt 5 builder) used
  `--cache-from screenly/ose-qt-builder:latest`, which is the
  image-tag-as-cache trick — only reuses the final manifest, never the
  apt-install + Qt cross-build intermediate layers, and silently no-ops
  the first time after a Dockerfile reorder invalidates the tag.
* `compile-webview-part-2` (Qt 6 / pi5+pi4-64+x86) shipped with
  `docker compose build` and zero cache config, so every PR rebuilt the
  per-board Qt 6 builder image cold.

Switch both to BuildKit's registry cache backend, identical pattern to
docker-build.yaml's `buildx` job: cache pushed to
`ghcr.io/screenly/anthias-webview-qt5-builder:buildcache` (Qt 5) and
`ghcr.io/screenly/anthias-webview-qt6-builder:buildcache-<board>`
(Qt 6, scoped per-board because the three Dockerfiles share almost
nothing). `mode=max,image-manifest=true` because GHCR rejects the
legacy standalone-cache manifest format on `ghcr.io/screenly/*`, same
constraint that bit the main workflow.

Auth-side details:

* Both jobs gain `permissions: { contents: read, packages: write }`,
  scoped per-job so other jobs don't inherit GHCR push.
* New "Login to GitHub Container Registry" step on each, gated on
  `event_name != 'pull_request'`. Fork PRs hand out a read-only
  GITHUB_TOKEN — cache-to would 401 mid-build — so `cache-to` is
  pushed-only-on-push, while `cache-from` runs unconditionally and
  warm-starts PRs off the latest master cache once the buildcache
  package is flipped public (same convention as anthias-server etc.).

Qt 6 build step had to switch from `docker compose build` to
`docker buildx bake -f docker-compose.yml --load --set <target>.cache-*`
because compose's YAML can't carry env-var-conditional cache_to without
emitting an empty list entry that buildx rejects. To keep the
subsequent `docker compose run` happy, the three Qt 6 services in
webview/docker-compose.yml gain explicit `image:` tags
(`webview-builder-{x86,pi5,pi4-64}`) so bake's `--load` puts the image
under a name compose looks up by tag rather than rebuilding it.

The Qt 5 job's old `Set buildx arguments` step (which assembled a
quoted string in $GITHUB_OUTPUT) is gone — build args inline in the
final `docker buildx build` invocation now, no GITHUB_OUTPUT
round-trip.

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

* fix(webview): trixie apt rename + adopt GHCR for Qt 5 builder image

Two intertwined fixes in webview/Dockerfile + the workflow that
publishes/consumes its image. CI never caught either because the
Docker-build step in build-webview.yaml is gated to push events, so
this Trixie-targeted Dockerfile has not yet built on master.

apt: drop the renamed-on-Trixie packages
  Stage 1 (armhf sysroot, archive.raspbian.org + deb.debian.org):
  * libgst-dev          → gone, libgstreamer1.0-dev (already listed)
                          replaces it
  * libsqlite0-dev      → gone, libsqlite3-dev (already listed) replaces
  * libsrtp0-dev        → gone in deb.debian.org/main; libsrtp2-dev
                          (already listed) is the trixie default
  * libpng16-16         → renamed libpng16-16t64 under the time_t
                          transition; old name is fully gone
  Stage 2 (amd64 runtime/builder, deb.debian.org):
  * libpng16-16         → libpng16-16t64
  Verified by GET on
  {deb.debian.org,archive.raspbian.org,archive.raspberrypi.org}/dists/
  trixie/main/binary-{armhf,amd64}/Packages.gz: every removed name is
  MISSING, every replacement is FOUND. Without this fix the first
  master push would die in stage 1's apt-get install.

GHCR migration: screenly/ose-qt-builder → ghcr.io/screenly/anthias-...
  Move the published Qt 5 builder image off Docker Hub and into the
  same GHCR namespace as the rest of the anthias-* artifacts. New ref
  is ghcr.io/screenly/anthias-webview-qt5-builder:latest (image) +
  :buildcache (cache, set up in eadd83d1) — one repo, two tags, same
  auth flow.
  * build-docker-image: drop the Docker Hub login step, retag the
    push target to the GHCR ref via an IMAGE_REF env var.
  * compile-webview-part-1: declare permissions: { contents: read,
    packages: read }, add the GHCR login (gated on non-PR), point the
    `docker run` at the GHCR ref.
  Migration window: the GHCR package is created private on first push
  and needs to be flipped public so fork-PR runners (no GHCR auth) can
  pull. Same one-shot operational step as the existing anthias-*
  packages.

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

* docs: fix second `rpi` vs `firmware` comment in image_builder

5e289198 fixed the same stale wording in docker/Dockerfile.base.j2
but missed the analogous comment block in
tools/image_builder/__main__.py — flagged by Copilot's second-pass
review.

The comment was a self-referential pointer to the apt-source bootstrap
in Dockerfile.base.j2, claiming libraspberrypi0 lives in
archive.raspbian.org's `rpi` component when in fact it ships under
`firmware` on trixie/armhf (the Deb822 entry written by the same code
correctly says `Components: firmware`). Reword to match reality and
add a note that this was verified against Packages.gz so a future
maintainer doesn't redo the lookup.

Pure comment change.

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

* ci(webview): build Qt 5 builder inline, drop the publish job

a9b9522d migrated the Qt 5 builder image from
screenly/ose-qt-builder:latest (Docker Hub) to
ghcr.io/screenly/anthias-webview-qt5-builder:latest (GHCR), but the
publish step (`build-docker-image`) is gated to push events. On PR
runs the GHCR image therefore never exists, and the consumer
(compile-webview-part-1) blew up trying to `docker pull` it:

    Error response from daemon: Head ...manifests/latest: denied

The image is a CI-internal build artifact — only consumed by the next
step in the same workflow, never deployed, never pulled by any
external user. Publishing it as a registry artifact is just inventory
the workflow has to manage. So instead:

* Delete the `build-docker-image` job entirely.
* Move the build into compile-webview-part-1 as a step that runs on
  every event (PR + push), produces the image with `--load`, and tags
  it locally as `webview-qt5-builder:latest` for the subsequent
  `docker run` to consume.
* Keep the registry-cache backend on
  ghcr.io/screenly/anthias-webview-qt5-builder:buildcache so cold
  builds remain fast: `cache-from` always, `cache-to` only on
  push events (fork PRs have a read-only GITHUB_TOKEN and would 401
  on cache write — same gating as docker-build.yaml).

Side benefits:
* Removes the chicken-and-egg of "PR can't run because GHCR image
  doesn't exist; GHCR image only gets pushed on master".
* Drops the cross-job artifact handoff (and the auth dance to read
  the published image), so fork PRs work without any GHCR public-flip
  step.
* Two matrix runners (pi2, pi3) build in parallel from the same
  registry cache — second-onward runs hit cache for everything once
  the first push to master warms it.

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

* ci(webview): drop registry cache plumbing, simpler is fine

eadd83d1 added BuildKit registry-cache backends to both webview build
steps; 3dc0a04a kept them when moving the Qt 5 build inline. The
caching is purely a speed optimization — none of it is load-bearing
for correctness, fork PRs can't write cache anyway, and the per-job
GHCR login + permissions block is real surface area in exchange for
saving a few minutes on warm runs.

Strip it all back out:

* compile-webview-part-1: drop the GHCR login + `permissions:
  packages: write`. The "Build Qt 5 builder image" step is a plain
  `docker buildx build --load` now — same inline-build architecture
  from 3dc0a04a, just no `--cache-from` / `--cache-to`.
* compile-webview-part-2: drop the GHCR login + `permissions:`,
  revert "Build Docker Image" from `docker buildx bake -f
  docker-compose.yml --load --set <target>.cache-*` back to plain
  `docker compose build`. COMPOSE_BAKE=true stays so compose still
  uses the bake builder under the hood — no behavior change beyond
  removing the cache flags.

webview/docker-compose.yml's explicit `image:` tags from eadd83d1
stay in place: they happen to match the compose default
(`<project>-<service>`) so plain `docker compose build` produces
the same image names the previous bake invocation did, and `compose
run` finds them either way.

Cold pi2/pi3 builds will be ~9 min on every run instead of getting
fast on warm runs. That's fine for now.

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

* Revert "ci(webview): drop registry cache plumbing, simpler is fine"

This reverts commit 1284a5ebd9.

* chore(webview): add bin/rebuild_qt5_toolchain.sh helper

build_webview.yaml's pi2/pi3 jobs fetch a pre-built Qt 5
cross-compile toolchain from a `WebView-v*` GitHub release
(webview/build_webview_with_qt5.sh:21 pins QT5_TOOLCHAIN_TAG to
WebView-v0.3.5). The trixie-targeted tarballs
qt5-5.15.14-trixie-{pi2,pi3}.tar.gz don't exist on any release yet —
the original Trixie commit (65311092) called out cutting them as an
out-of-band prereq. Until they exist, pi2/pi3 CI fails with
`sha256sum: no properly formatted checksum lines found` because curl
falls back to a 404 HTML page on the missing .sha256 URL.

This helper produces those tarballs locally:

* Builds webview/Dockerfile (the same image CI's
  compile-webview-part-1 builds inline) once, --load only.
* Runs build_qt5.sh inside that image once per requested board (pi2
  by default, pi3 by default, or whichever boards are passed on the
  command line). Sequential because Qt 5 + QtWebEngine peaks at ~16
  GB RAM per build and the Linaro cross-compile toolchain extracted
  into .qt5-toolchain-build/src/ is shared between boards.
* Drops outputs at .qt5-toolchain-build/release/qt5-5.15.14-trixie-
  {pi2,pi3}.tar.gz (+ .sha256), ready to upload via
  `gh release upload`.

Idempotent: existing release/<tarball>.tar.gz short-circuits the run
for that board. ccache state is preserved across runs at
.qt5-toolchain-build/ccache/. BUILD_WEBVIEW=0 in the env skips the
bonus webview-* tarball that build_qt5.sh otherwise produces (the
Dockerfile defaults BUILD_WEBVIEW=1 so the helper inherits that
default for parity with the previous CI flow).

The .qt5-toolchain-build/ directory is intentionally hidden + at
the repo root rather than ~/tmp so it's discoverable to whoever
runs this next without grep'ing scrollback for a path.

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

* fix(webview): make Qt 5 cross-build Dockerfile produce working tarballs on trixie

The webview/Dockerfile in this repo wasn't actually exercised end-to-end
before — master CI uses screenly/ose-qt-builder from Docker Hub, and the
inline-build path introduced for trixie only ran build_webview_with_qt5.sh
(which downloads prebuilt qt5 toolchains). Rebuilding those toolchains for
trixie surfaced four real bugs:

* python interpreter never on PATH for non-interactive shells. The pyenv
  block only wired itself up via ~/.bashrc, which doesn't load when the
  rebuild script does `docker run /webview/build_qt5.sh`. Replace pyenv
  with apt-pinned python2.7 from archive.debian.org bullseye (trixie main
  dropped py2 entirely; bullseye archive still ships 2.7.18). Pin only
  python2.7 + its libpython runtime libs, leave everything else on trixie.
  Symlink /usr/local/bin/python -> python2.7 so QtWebEngine's
  `/usr/bin/env python` resolves.

* QtWebEngine configure silently rejected fontconfig because the sysroot
  was missing /usr/share/pkgconfig/bzip2.pc. The Dockerfile only copies
  /lib, /usr/include, /usr/lib from the builder stage; on trixie's
  libbz2-dev the .pc file lives in /usr/share/pkgconfig (arch-indep),
  so freetype2.pc's `Requires.private: bzip2` failed to resolve, which
  cascaded into fontconfig: no, which silently dropped QtWebEngine from
  the build. Add the missing COPY.

* Several QtWebEngine-required dev libs missing from the sysroot
  (libharfbuzz-dev, liblcms2-dev, libre2-dev, libxml2-dev). Same libs
  also need to be installed on the *host* runtime stage because chromium
  pdfium evaluates `harfbuzz_from_pkgconfig` in the host toolchain
  context, where Qt's host_pkg_config="/usr/bin/pkg-config" drops the
  sysroot args from chromium's pkg_config template.

* `make -j$(nproc)+2` OOMs on >8-core hosts. cc1plus under qemu-arm
  peaks at ~3-4 GB during chromium compile, so the default formula
  needs ~50 GB on a 16-core box. Make MAKE_CORES env-overridable in
  build_qt5.sh and have rebuild_qt5_toolchain.sh cap at min(nproc, 8).

Also: -webengine-proprietary-codecs in the configure args so the
resulting QtWebEngine supports H.264/AAC/MP3 (matches what Debian
qt6-webengine ships).

Verified on a 16-core/22GB+32GB-swap host: produces
qt5-5.15.14-trixie-{pi2,pi3}.tar.gz (88M, 98M) with 251 webengine entries
each, plus the matching webview-*.tar.gz apps.

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

* chore(webview): bump QT5_TOOLCHAIN_TAG to WebView-v2026.04.1

Trixie qt5-5.15.14-trixie-{pi2,pi3} toolchain tarballs are published on
the new WebView-v2026.04.1 release; the previous WebView-v0.3.5 only
ships the bookworm tarballs and is now unreachable for trixie pi2/pi3 CI.

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

* docs(webview): refresh stale tag reference in rebuild_qt5_toolchain.sh hint

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

* fix(ci): pass full SHA for GIT_HASH; keep short SHA only in GIT_SHORT_HASH

Both `.github/workflows/build-webview.yaml` and `bin/rebuild_qt5_toolchain.sh`
were populating the GIT_HASH build arg with the *short* hash, making
GIT_HASH and GIT_SHORT_HASH identical and stripping the unambiguous
SHA needed by `lib/diagnostics.py:os.getenv('GIT_HASH')` for downstream
traceability. Pass `git rev-parse HEAD` for GIT_HASH and reserve
`--short HEAD` for GIT_SHORT_HASH (which is already what
`tools/image_builder/__main__.py` does for the main service images).

Caught in Copilot review of #2779.

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

* fix(docker): exclude Qt 5 toolchain build dir + caches from COPY

The viewer image's `COPY . /usr/src/app/` was slurping in 1.6 GB of
local Qt 5 cross-build state (`.qt5-toolchain-build/`) plus 69 MB of
`.mypy_cache/`, inflating every viewer/server image by ~1.7 GB even
though the build needs none of it. Add those plus `.ruff_cache`,
`.idea`, `.cursor`, `.claude`, `.cache`, and tighten the existing
`*.git` / `*.github` globs (which match files ending in `.git` /
`.github` but not the directories themselves on most matchers) to
the literal directory names.

Caught while validating the trixie 5-board matrix: x86 viewer was
6.28 GB and pi5 viewer 2.23 GB; both had the same 1.76 GB COPY layer
that's mostly `.qt5-toolchain-build/`. Fixed image should be ~5 MB
for COPY and ~1.5 GB for the viewer overall.

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-04-30 18:30:59 +01:00
Viktor Petersson
470a37caf4 chore(host): unify host Python install on uv, clean up ansible roles (#2750)
* chore(ansible): fix all ansible-lint violations and remove skip list

Drives the 19 deferred violations from the previous skip list to zero
and deletes .ansible-lint. The roles now pass the ansible-lint
'production' profile (previously 'min').

- var-naming[no-role-prefix] (17): rename register/set_fact vars in
  network/screenly/splashscreen/system roles to use the role's prefix
  (e.g. config_path -> system_config_path, x_service -> screenly_x_service).
- risky-shell-pipe (1): add 'set -o pipefail' + bash executable to the
  /etc/timezone shell task in system role.
- no-free-form (1): switch swapoff to keyword form (cmd:/removes:).

Also resyncs uv.lock with pyproject.toml's ansible-core==2.19.9 pin
(drift left over from #2749).

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

* chore(ansible): fix bugs, simplify, drop Debian <= 7 support

Bug fixes
---------
- Fix /etc/sudoers.d/screenly_overrides: was copied verbatim, so the
  literal "${USER}" was written into sudoers (sudo doesn't expand it)
  and the rule pointed at the renamed upgrade_screenly.sh. Convert to
  a Jinja template using {{ anthias_user }}, point at upgrade_anthias.sh,
  and add `validate: visudo -cf %s` so a syntax error blocks install.
- Fix double-prefix var name screenly_screenly_x_service_exists left over
  from the lint-cleanup replace_all cascade.

Simplification
--------------
- site.yml: lift env('USER') into anthias_user play var; assert
  USER + DEVICE_TYPE are set/valid in pre_tasks. Replace ~30 inline
  lookups across roles with {{ anthias_user }}.
- system role: drop `lsb_release -cs` and `getconf LONG_BIT` shellouts;
  use ansible_distribution_release and ansible_userspace_bits facts.
  Collapse two near-duplicate "add user to docker group" tasks into one
  task using `append: true` plus a conditional gpio entry for ARM.
- system role: replace the /etc/timezone shell pipe with a non-shell
  `command: readlink` + `copy: content:` pair (no pipefail dance needed).
- system role: drop the unconditional `rpi-update` install on ARM; that
  package ships experimental kernels and shouldn't be run unattended.
- screenly role: move anthias-host-agent.service template from the
  non-standard tasks/templates/ to the conventional roles/<role>/templates/.

Debian 11 path
--------------
- Bump pinned versions in the Debian 11 pip branch to current release
  where Python 3.9 still supports them (ansible-core 2.15.13, redis
  7.4.0, requests 2.33.1, tenacity 9.1.2, getmac 0.9.5). Drop the
  unused docker==6.0.0 pin. Comment why ansible-core stays on 2.15.x.

Drop Debian <= 7
----------------
- splashscreen role: remove four Jessie/Wheezy tasks and the
  `ansible_distribution_major_version|int > 7` guards on every
  remaining task. Delete unused files/asplashscreen.

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

* chore(ansible): normalize *_exist to *_exists for consistency

system_cdefs_exist was the lone singular outlier among
network_manager_exists / screenly_x_service_exists.

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

* chore(systemd): harden anthias-host-agent and wifi-connect units

Both units now follow modern systemd best practice:
- explicit Type= (simple / oneshot)
- explicit dependency on docker.service via Requires= (was just After=)
- Documentation= URL + SyslogIdentifier= for journalctl filtering
- structured sandboxing: PrivateTmp, PrivateDevices, ProtectSystem=full,
  ProtectHome=read-only, ProtectKernel{Tunables,Modules,Logs},
  ProtectControlGroups, ProtectClock, ProtectHostname, ProtectProc=invisible,
  RestrictRealtime, RestrictSUIDSGID, RestrictNamespaces,
  RestrictAddressFamilies (per-service: AF_UNIX for wifi-connect,
  AF_UNIX/AF_INET/AF_INET6 for host-agent), LockPersonality, UMask=0027.

wifi-connect (no privilege escalation, just talks to docker.sock)
goes further with NoNewPrivileges, CapabilityBoundingSet=,
SystemCallFilter=@system-service minus @privileged/@resources, and
Type=oneshot + RemainAfterExit so systemd records "active (exited)"
after `docker compose up -d` returns.

host-agent is left more permissive because host_agent.py shells out
to `sudo systemctl reboot|poweroff` which needs setuid + CAP_SYS_BOOT
+ the reboot() syscall — those would all be blocked under the tighter
profile. The unit calls this out in a comment.

Also makes start_wifi_connect_service.sh executable so the unit can
invoke it directly instead of via a `bash` wrapper.

systemd-analyze security score:
  anthias-host-agent: 7.5 EXPOSED -> 4.9 OK
  wifi-connect:       7.3 MEDIUM  -> 1.1 OK

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

* chore(host): bootstrap installer_venv with uv, drop requirements.host.txt

uv manages its own Python independent of the system Python, which lets
both Debian 11 and Debian 12+ use the same dependency set from
pyproject.toml's `host` group. The install flow no longer needs:

- the requirements/requirements.host.txt frozen snapshot
- the Debian 11 ansible-core==2.15.x special case in install.sh
- the parallel Debian 11 / Debian 12+ pip tasks in the screenly role
- the python3-pip / python3-venv / python3-full apt packages
- the cryptography==38.0.1 wheel-build workaround
- the python-lint.yaml drift check on requirements.host.txt
- the long-stale `supervisor` pip-removal migration tasks

bin/install.sh changes:
- new `clone_repo` step (runs before install_ansible) so we have
  pyproject.toml in place
- install_ansible now: curl|sh the official uv installer, then
  `uv sync --no-default-groups --group host --no-install-project`
  with UV_PROJECT_ENVIRONMENT=/home/$USER/installer_venv
- run_ansible_playbook uses the venv's ansible-playbook directly
  instead of relying on PATH activation

ansible/roles/screenly/tasks/main.yml: replace both pip tasks with a
single `uv sync` task (run as the anthias user, with
UV_PROJECT_ENVIRONMENT pointed at installer_venv) so the venv stays
in sync if ansible-playbook is rerun standalone.

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

* fix(install): replace pre-uv installer_venv on upgrade

Existing Anthias hosts have an installer_venv created by
`python3 -m venv`, which uv won't recognize on upgrade. Detect by the
absence of the `uv = <version>` line in pyvenv.cfg and rm -rf it so
`uv sync` rebuilds cleanly.

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

* fix(systemd): allow AF_NETLINK in anthias-host-agent

host_agent.py uses the netifaces C extension which opens AF_NETLINK
(NETLINK_ROUTE) sockets to enumerate interfaces during the
set_ip_addresses pubsub command. The previous RestrictAddressFamilies
list (AF_UNIX/AF_INET/AF_INET6) blocked this with EAFNOSUPPORT.

Verified by tracing netifaces under strace and by running a
test service unit with the same restrictions.

Audit: only three pubsub commands ever target the hostcmd channel —
reboot, shutdown, set_ip_addresses. CEC lookups happen inside the
anthias-celery Docker container, not on the host.

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

* chore(install): apply audit fixes from review

- Bug: upgrade_containers.sh was always pulled from master regardless
  of the user's selected ref, so a tagged install got master's upgrade
  script. Now uses ${BRANCH}.
- Pin uv to UV_PIN_VERSION (=0.9.17, matching docker/uv-builder.j2)
  and reinstall if the local uv is a different version. Avoids drift
  between the host bootstrap and the docker image build.
- Apply --ask-become-pass on every arch when the NOPASSWD sudoers
  file is missing (was x86_64 only — could hang on Pi if the sudo
  timestamp expired mid-playbook). Also tell the user a prompt is
  coming.
- Drop the stale "Please reboot and run upgrade" branch in
  post_installation; both branches told the user to reboot anyway.
  Add an SSH-detection notice so the user knows their session will
  drop on reboot.
- Use git -C in write_anthias_version so it doesn't rely on cwd.
- Drop redundant `-e` from shebang (set -euo pipefail two lines down
  is stricter).
- Drop `apt update -y` (-y is meaningless for update); standardize on
  apt-get.
- Quote ${USER} interpolations everywhere; replace `A && B || C`
  pseudo-if-else with proper if-then-else (shellcheck SC2015);
  fix array expansion `${VERSION_PROMPT}` -> `${VERSION_PROMPT[*]}`
  (SC2128); split `local FOO=$(...)` into separate decl/assign
  (SC2155). shellcheck now clean.

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

* chore(ansible): drop dead migration helpers and split system role

Migration helpers
-----------------
Dropped tasks that only existed to clean up cruft from pre-Anthias
(Screenly OSE, mid-2024-) installs. They have been idempotent no-ops
on every modern install for many releases.

screenly role:
- Remove screenly_utils.sh (renamed long ago)
- Remove cron entry "Cleanup screenly_assets" (state: absent of a
  cron job no Anthias release has created)
- Remove old upgrade_screenly.sh (renamed to upgrade_anthias.sh)
- Remove screenly_usb_assets.sh and the autoplay udev rule
- Remove plymouth-quit-wait.service / plymouth-quit.service deletion
  (Plymouth handling is now done by the splashscreen role)
- Drop the entire screenly_deprecated_systemd_units list and the
  three tasks that used it (X.service, screenly-celery,
  screenly-web, screenly-websocket_server_layer, screenly-viewer,
  matchbox, screenly-host-agent, udev-restart, wifi-connect — all
  deprecated unit names from the Screenly era)
- Remove the ngrok binary cleanup

network role:
- Drop the screenly_net_mgr.py / screenly_net_watchdog.py removal
  pipeline (the whole stat -> set_fact -> stop -> rm chain). Modern
  installs use NetworkManager directly.

upgrade-script integrity
------------------------
Documented why /usr/local/sbin/upgrade_anthias.sh is fetched from
GitHub without a checksum: the URL is meant to track upstream master
so users can pull in fixes without reinstalling, and integrity is
bounded by HTTPS to githubusercontent. If we ever ship signed
release assets, we should switch to fetching the signed asset.

system role split
-----------------
~370-line system/tasks/main.yml split into focused includes:
  - boot.yml         — /boot/{config,cmdline}.txt edits
                       (raspberry-pi + touches-boot-partition tags
                        applied at the include level)
  - packages.yml     — libc6-dev cdefs.h fix, Anthias apt deps,
                       and removal of pre-Anthias / distro-Docker
                       packages
  - docker.yml       — Docker repo, install, and user-to-docker-group
  - timezone.yml     — /etc/timezone backfill from /etc/localtime
  - dist_upgrade.yml — apt upgrade dist + autoremove
                       (system-upgrade tag at the include level)
  - misc.yml         — rc.local, dpkg 01_nodoc, swap removal
main.yml is now the orchestrator. Tags moved from per-task to per-include.

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

* fix(install): pin upgrade_anthias.sh URL to the installed ref

The Ansible task that wrote /usr/local/sbin/upgrade_anthias.sh
hardcoded raw.githubusercontent.com/.../master/bin/install.sh. So a
tag-pinned install (e.g. v0.20.4) silently received master's
install.sh as its upgrade entry point, defeating the version pin.

The mirror bug in install.sh::upgrade_docker_containers (which fetched
upgrade_containers.sh from master regardless of selected ref) was
fixed earlier in this PR; this commit closes the same gap on the
upgrade-script side.

- site.yml: add `anthias_branch` play var (env ANTHIAS_BRANCH,
  defaulting to 'master' if unset for safety on standalone
  ansible-playbook runs); include it in the pre_tasks assertion.
- screenly role: render the URL with `{{ anthias_branch }}`. Reword
  the comment + task name so they actually match the behaviour.
- install.sh::run_ansible_playbook: export ANTHIAS_BRANCH=${BRANCH}
  so the playbook can pick up the user's selected ref.

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

* chore(ansible): suppress sonarcloud S2612 on intentional system modes

SonarCloud's ansible:S2612 ("granting access to others") fires on
seven file/directory modes in the new system role task files. Each
matches a Debian/Raspbian convention and the file *needs* to be
world-readable to function:

- /etc/timezone (0644) — libc/locale paths read it
- /etc/dpkg/dpkg.cfg.d/01_nodoc (0644) — dpkg honours per any caller
- /etc/apt/keyrings (0755) and /etc/apt/keyrings/docker.asc (0644) —
  per Debian's own apt-secure docs; the _apt user reads them
- /etc/rc.local (0755) — systemd-rc-local-generator runs it
- cmdline.txt and its .orig backup (0755) — Pi imager / NOOBS / pi-config
  ship 0755; deviating breaks raspi-config tooling

Suppressing per-line with `# NOSONAR` and a one-liner reason rather
than weakening the modes (which would actually break the system).

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

* fix(install): point ansible at the venv python; pre-create netdev group

Two issues found by running bin/install.sh end-to-end against a
privileged Debian 12 container with systemd + DinD.

1. Ansible needs Python on the target. Since this PR drops the system
   python3 install in favour of uv-managed Python under installer_venv,
   gather_facts (and every subsequent module) failed with:
       "/usr/bin/python3 not found"
   Fix: export ANSIBLE_PYTHON_INTERPRETER=$installer_venv/bin/python
   in install.sh::run_ansible_playbook so Ansible uses the venv we
   just provisioned with uv sync.

2. The `netdev` group is created by network-manager, but Anthias can
   be installed with MANAGE_NETWORK=No, in which case the package
   isn't pulled in and the group doesn't exist. The user-membership
   task then failed with "Group netdev does not exist". Pre-create
   it with `ansible.builtin.group` (system:true) so the membership
   task is safe regardless of MANAGE_NETWORK.

Both bugs were latent on Raspberry Pi OS (which always pre-installs
NetworkManager) but bite on minimal Debian 12 / x86 images.

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

* fix(ansible): pre-create all required groups, not just netdev

Previous fix only handled netdev. Minimal Debian/x86 images can also
be missing input, plugdev, video, dialout (anything that udev or
desktop-package postinsts would have created). Pre-create the whole
list with ansible.builtin.group + state: present.

Also dedupe the base/pi group lists between the create task and the
membership task using YAML anchors so they can't drift.

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

* fix(install): use curl instead of wget; add gettext-base

End-to-end test in a privileged Debian 12 container surfaced two
post-Ansible-playbook bash issues:

- upgrade_docker_containers used wget, but the install_packages step
  only installs curl. Switch to `curl -fsSL ... -o`.
- bin/upgrade_containers.sh uses `envsubst` (gettext-base). Add
  gettext-base to APT_INSTALL_ARGS so it's present.

Both surfaced because we slimmed the apt list when migrating the
host bootstrap to uv. They were latent on full RPi OS images.

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

* fix(systemd): keep host-agent sudo path working under systemd <= 252

Copilot's review caught a real regression: nearly all the Protect*/
Restrict* directives we added implicitly enable NoNewPrivileges on
systemd <= 252 (Debian 11/12), which blocks sudo's setuid escalation.
host_agent.py shells out to `sudo systemctl reboot|poweroff`, so the
hardened unit silently broke reboot/shutdown via the API on those
distros.

Reproduced inside a Debian 12 + systemd 252 container: every direct-
ive flagged "implies NNP=yes" in systemd.exec(5) actually does so on
that version. Setting `NoNewPrivileges=false` does *not* override the
implication — the only fix is to drop the offending directives.

Strip anthias-host-agent.service down to the directives empirically
verified to keep NNP=0 on systemd 252:

  PrivateTmp, ProtectSystem=full, ProtectHome=read-only,
  ProtectControlGroups, ProtectProc=invisible, UMask=0027,
  CapabilityBoundingSet (narrowed), AmbientCapabilities=

This still gives meaningful sandboxing (capability bounding set
limits even sudo's child processes) without breaking reboot.

wifi-connect.service is unaffected — it doesn't escalate.

systemd-analyze security on a real Debian 12 systemd 252:
  anthias-host-agent: 6.5 MEDIUM (was claimed 4.9 OK on Ubuntu 24.04
                                   host, where the implication
                                   doesn't fire)

Also addressing other Copilot review comments
---------------------------------------------
- screenly_overrides: tighten the systemctl rule to the specific
  verbs host_agent.py uses (`reboot`, `poweroff`). Granting blanket
  `/bin/systemctl` was overly broad — could start/stop/mask any
  unit on the system.
- packages.yml: gate the cdefs.h / libc6-dev workaround on
  ansible_architecture == 'armv7l'. The header path is armhf-
  specific; on aarch64/x86 the task always hit "missing" and
  reinstalled libc6-dev unnecessarily on every run.
- screenly/tasks/main.yml: assert that uv exists before `uv sync`
  with a clear error message pointing at install.sh, instead of
  letting the task fail deep inside command-not-found.

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

* fix(ansible): skip dhcpcd disable when service is not installed

On stock Debian/Ubuntu x86 (and any host without dhcpcd installed),
the unconditional systemd stop/disable failed the playbook with
"Could not find the requested service dhcpcd". Gather service facts
first and only run the disable when dhcpcd.service is actually
present. Pi OS Buster/Bullseye still ship dhcpcd by default, so the
existing behavior there is unchanged; Bookworm (12+) is already
excluded by the version gate.

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

* chore(install): mention x86 alongside Raspberry Pi in intro banner

The intro banner only warned about losing the Pi's desktop
environment; reword so it reflects that Anthias also runs on x86
and that the host is repurposed regardless of platform.

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

* fix(host-agent): retry redis connect quietly on first-boot startup

On a fresh install the host_agent.service unit starts before the redis
docker container is accepting connections, so it crashed on every
boot with a 50-line ConnectionRefusedError traceback, was restarted
by systemd 10s later, and repeated until redis came up — typically
~5 minutes of journal noise per cold boot.

Wrap the redis connect+subscribe in a tenacity Retrying matching the
pattern already used by set_ip_addresses: retry only on
redis.exceptions.ConnectionError, 5s fixed wait, up to 60 attempts,
and use before_sleep_log so each retry logs a single WARN line
instead of a traceback. After the bounded retry budget, the
exception is re-raised and systemd's normal restart policy applies.

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

* chore(ansible): title-case Anthias in splashscreen task name

The "Set plymouth default theme to anthias" task name was the only
user-facing message in the playbook that didn't title-case Anthias.
Audit covered task names, msg/fail_msg/debug fields, and systemd
unit Description= templates; everything else was already correct.
The command argument and changed_when comparison stay lowercase
because they reference the literal on-disk theme identifier.

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

* style(host-agent): collapse before_sleep_log call to satisfy ruff format

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

* fix(sudoers): match host_agent.py's actual systemctl path

host_agent.py invokes /usr/bin/systemctl, but the rule listed
/bin/systemctl. On usrmerged Debian sudo matches by inode so this
worked in practice, but the rule should match what the agent
actually calls so it doesn't depend on /bin -> /usr/bin staying a
symlink. Also drops /sbin/shutdown — the agent never invokes it.

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

* fix(host-agent): surface retry exceptions and support ens interfaces

Two related fixes turned up while validating reboot/shutdown end-to-end
on a multipass-launched Debian 13 VM:

1. set_ip_addresses() swallowed every retry's exception under the bare
   `except RetryError`, leaving only a generic "Unable to connect"
   warning. Add before_sleep_log(..., exc_info=True) so the actual
   requests.* exception is logged on each attempt, and put a 5s timeout
   on requests.get() so a hung connect can't stretch one attempt out.
2. SUPPORTED_INTERFACES missed `ens` (systemd "slot" naming used by
   QEMU/multipass and many cloud images), so get_ip_addresses() returned
   an empty list on those hosts even when the NIC was up.

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

* chore(host-agent): hoist internet probe URL to a named constant

Pulls the 1.1.1.1 anycast literal out of set_ip_addresses() into
INTERNET_PROBE_URL and silences the SonarCloud python:S1313 hotspot
on the constant with a comment explaining the IP is Cloudflare's
public anycast probe (not a private address). The previous commit's
edit to the same line tripped the quality gate.

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-04-26 07:48:12 +01:00
dependabot[bot]
dde9916983 chore(deps): bump astral-sh/setup-uv in the github-actions group (#2748)
Bumps the github-actions group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 7.6.0 to 8.1.0
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](37802adc94...08807647e7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 07:01:35 +01:00
Viktor Petersson
ee12387b06 chore(deps): manage Python deps via uv dependency-groups (#2744)
* chore(deps): manage Python deps via uv dependency-groups

Replaces the six service-scoped requirements*.txt files with
PEP 735 dependency-groups in pyproject.toml and rebuilds every
Docker image as a two-stage build: a uv-builder stage (using the
official ghcr.io/astral-sh/uv image, with a pip fallback for
armv6) produces /venv via `uv sync --group <svc>`, which the
runtime stage copies in. uv.lock becomes authoritative for all
services. requirements/requirements.host.txt is kept as a
committed, auto-generated artifact (`uv export --group host`) so
bin/install.sh and the Ansible role keep working; a python-lint
CI step enforces it stays in sync.

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

* chore(deps): bump Django, cryptography, pyOpenSSL, and 5 others

- Django 4.2.29 → 4.2.30 (latest 4.2 LTS)
- cryptography 3.3.2 → 46.0.7 (capped by pyOpenSSL 26's `cryptography<47`;
  cryptography 47 is incompatible with the latest pyOpenSSL)
- pyOpenSSL 19.1.0 → 26.0.0 (required by newer cryptography ABI —
  pyOpenSSL 19 crashed at import against cryptography ≥ ~3.4)
- requests 2.32.5 → 2.33.1 (aligned across every group, including
  docker-image-builder and local)
- pyasn1 0.6.2 → 0.6.3
- redis 7.1.0 → 7.4.0
- Cython 3.2.3 → 3.2.4
- sh 1.8 → 2.2.2 (major bump; usages in celery_tasks.py, bin/wait.py,
  lib/utils.py stick to the stable `sh.<cmd>` + `sh.ErrorReturnCode_N`
  API — verified still works)
- python-vlc 3.0.20123 → 3.0.21203

`mako` and `flatted` were requested but skipped: `mako` was already
removed from the project (9535745e), and `flatted` is an npm dep in
`package-lock.json`, not a Python dep.

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

* chore(deps): bump wheel from 0.38.1 to 0.46.2

Closes Dependabot PR #2651.

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

* fix: adapt sh 2.x API changes in wait.py and viewer

Two real breakages uncovered by auditing every `sh.*` call site
against the sh 1.x → 2.x API:

- bin/wait.py: `sh.grep(sh.route(), 'default')` no longer pipes
  in sh 2.x — the inner command stringifies to its stdout and
  becomes a literal argument to grep, producing
  `grep '<route_output>' default` and an ErrorReturnCode_2. Use
  the idiomatic `sh.grep('default', _in=sh.route())` instead.

- viewer/__init__.py: `browser.process.alive` is gone in sh 2.x
  (`OProc` no longer exposes it). Use `browser.process.is_alive()[0]`,
  which returns the `(alive_bool, exit_code)` tuple.

Plus two review nits:
- Add trailing newline to docs/migrating-assets-to-screenly.md
- Use `diff -u` in the requirements.host.txt CI drift check so
  failures print a readable unified diff.

Verified against sh==2.2.2 inside the rebuilt server image:
- `sh.grep('default', _in=sh.echo('…'))` pipes correctly
- `cmd.process.is_alive()` → `(True, None)` while running,
  `(False, 0)` after wait()
- `cmd.process.stdout.decode('utf-8')` still works on `_bg=True`
  processes

83/83 unit tests + 12/12 integration tests still pass.

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

* fix(docker): serialize apt cache access with sharing=locked

The multi-stage uv-builder + runtime layout means two RUN steps can
race on BuildKit's shared `/var/cache/apt` cache mount. apt requires
an exclusive lock on /var/cache/apt/archives, so a concurrent
apt-get in the sibling stage causes the build to fail with
`E: Could not get lock /var/cache/apt/archives/lock`.

BuildKit's default cache mount sharing mode is `shared` (unrestricted
concurrent access). Switching to `sharing=locked` makes BuildKit
serialize access across stages, matching apt's locking model.

Discovered while cross-compiling `pi4-64` under QEMU, where the
slower emulated apt-get in stage 1 overlapped with the host-speed
apt-get in stage 2.

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

* ci: fix ansible-lint and sbom workflows

**ansible-lint** (broken since 2026-04-08, #2732):
- `ansible-community/ansible-lint-action@main` repo is gone (404),
  so every run failed with "Unable to resolve action".
- Rewrite the workflow to use setup-uv + `uv run ansible-lint` from
  a new `ansible-lint==26.4.0` entry in the `dev-host` dependency
  group — matches the uv-based pattern already used by
  `python-lint.yaml`.
- Add `.ansible-lint` config with a skip list covering 19
  pre-existing violations in `ansible/` roles
  (`var-naming[no-role-prefix]`, `risky-shell-pipe`, `no-free-form`)
  so the workflow can go green today; follow-up PRs should drive
  the skip list down.
- Extend the path triggers to fire on config, workflow, and lock
  changes — not just `ansible/**`.

**sbom** (broken since 2026-04-02):
- The `sbomify/github-action` renamed `SBOM_FILE` to `LOCK_FILE` for
  lockfile inputs. Every run has been failing with "`uv.lock` is a
  lock file, not an SBOM. Please use LOCK_FILE instead of SBOM_FILE."
- Rename both `SBOM_FILE` envs (`package-lock.json` and `uv.lock`)
  to `LOCK_FILE`.

Verified locally: `uv run ansible-lint ansible/` passes (0
failures, 0 warnings).

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

* ci: SHA-pin all external GitHub Actions

Addresses SonarCloud rule githubactions:S7637 ("Use full commit SHA
hash for this dependency") and brings the repo in line with the
hardened CI guidance from OpenSSF, CISA, and GitHub itself: tag refs
like @v7 or @master are mutable and can be retargeted by the action
owner or via compromise. Pinning to a full commit SHA removes that
supply-chain risk.

Every `uses:` reference to an external action across all 13 workflow
files is now pinned by SHA, with the original tag preserved as an
inline comment so the intent remains readable:

    uses: actions/checkout@de0fac2e45 # v6

Dependabot's github-actions ecosystem (already configured in
.github/dependabot.yml) recognises this `<SHA> # <tag>` format and
will update both the SHA and the comment together on future version
bumps, so we don't lose automated update coverage.

Scope: 21 distinct external actions × 73 total use sites across
ansible-lint, build-balena-disk-image, build-webview, codeql-analysis,
deploy-website, docker-build, generate-openapi-schema, javascript-lint,
lint-workflows, python-lint, sbom, and test-runner. Local workflow
references (./.github/workflows/...) left untouched.

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

* fix(viewer): use RunningCommand.is_alive() instead of OProc tuple

OProc.is_alive() returns (bool, exit_code); RunningCommand.is_alive()
wraps that and returns just the bool. The wrapper is clearer than
indexing into the tuple.

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-04-25 06:48:36 +01:00
Nico Miguelino
8ee205d055 chore(deps): bump actions/checkout from v5 to v6 (#2703)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:23:55 -07:00
dependabot[bot]
b22f610a2c chore(deps-dev): bump ruff from 0.14.3 to 0.14.10 (#2629)
* chore(deps-dev): bump ruff from 0.14.3 to 0.14.10

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.3 to 0.14.10.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.3...0.14.10)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.14.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: update `uv.lock`

* chore: trigger Python linting workflow when `pyproject.toml` or `uv.lock` changes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nico Miguelino <nicomiguelino2014@gmail.com>
2026-01-05 10:48:27 -08:00
Nico Miguelino
29ae072514 chore: replace Poetry with uv for managing host dependencies (#2611) 2025-12-16 05:03:27 -08:00
Nico Miguelino
6705477e5b chore: update Ruff linting rules formatting (#2570) 2025-11-12 10:21:49 -08:00
dependabot[bot]
9fa0da6528 chore(deps): bump the github-actions group with 2 updates (#2490)
Bumps the github-actions group with 2 updates: [actions/setup-python](https://github.com/actions/setup-python) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-python` from 5 to 6
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

Updates `actions/setup-node` from 4 to 5
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 11:00:38 -07:00
dependabot[bot]
2e97c7de86 chore(deps): bump the github-actions group with 4 updates (#2444)
* chore(deps): bump the github-actions group with 4 updates

Bumps the github-actions group with 4 updates: [actions/checkout](https://github.com/actions/checkout), [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance), [actions/download-artifact](https://github.com/actions/download-artifact) and [ncipollo/release-action](https://github.com/ncipollo/release-action).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

Updates `actions/attest-build-provenance` from 1 to 2
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v1...v2)

Updates `actions/download-artifact` from 4 to 5
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

Updates `ncipollo/release-action` from 1.11.2 to 1.18.0
- [Release notes](https://github.com/ncipollo/release-action/releases)
- [Commits](https://github.com/ncipollo/release-action/compare/v1.11.2...v1.18.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/attest-build-provenance
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: ncipollo/release-action
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>

* temporary: disable Docker image pushes for testing purposes

* chore(ci): revert temporary changes to the build workflow

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: nicomiguelino <nicomiguelino2014@gmail.com>
2025-08-19 16:29:21 -07:00
Nico Miguelino
40100b86f7 chore(ci): bump runner OS to Ubuntu 24.04 for the remaining workflows (#2205) 2025-01-21 19:00:29 -08:00
Nico Miguelino
490051585f Replace flake8 with ruff (#2092) 2025-01-14 06:47:52 -08:00
Nico Miguelino
e67c996b8f chore(ci): bump action/setup-python to v5 (#2138) 2024-11-20 13:42:45 -08:00
Nico Miguelino
ba653abfeb chore(ci): bump actions/checkout to v4 (#2137) 2024-11-20 11:46:09 -08:00
Nico Miguelino
70afe9ba49 chore(cleanup): remove workflow related to the experimental branch (#2101) 2024-10-22 09:08:37 -07:00
Nico Miguelino
29d4c24fb2 chore(ci): replace --with with --only when installing specific deps (#2063) 2024-09-10 08:29:47 -07:00
Nico Miguelino
d5f5c63e1e Use Poetry for the Python linter (#2042) 2024-08-29 11:29:59 -07:00
Nico Miguelino
390c40d9e8 fix: trigger test and linter check on push and/or PR to experimental branch (#2012) 2024-08-06 07:27:01 -07:00
Nico Miguelino
591357d1bc chore: bumps Python version used by linter from 3.7 to 3.11 (#1988) 2024-07-23 15:29:05 -07:00
nicomiguelino
6e87511237 chore/fix: increase scope of Python linter 2024-07-02 16:08:19 -07:00
Nico Miguelino
87e2d493ce Adds Python linting in CI (#1939) 2024-06-21 09:11:14 -07:00