Commit Graph

20 Commits

Author SHA1 Message Date
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