feat(balena): unpinner also rolls OS + supervisor updates - #2984
Merged
Conversation
With debug logging removed (#2977), two pre-existing startup warnings became visible in the viewer logs. Both are fixed here: - migrate_legacy_paths.sh: `set -euo pipefail` + the bare `${USER}` in USER_HOME aborted the script when $USER is unset — which is the case in-container (DATA mode, via migrate_in_container_paths.sh). On a legacy (screenly→anthias) device that would skip the in-container migration entirely. Fall back to `id -un` so it resolves without tripping `set -u`. - AnthiasViewer: the MainWindow ctor called showFullScreen() AND main() called window->show(), showing the window twice. Under cage/wayland the double surface-commit tripped wlroots' "A configure is scheduled for an uninitialized xdg_surface" warning at startup. Show once, in main(), after construction. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
bin/balena_unpin_devices.py cleared the app-release pin but left unpinned devices on whatever old balenaOS / supervisor they booted. Add two optional, cloud-API-only phases so the one hourly job brings the fleet fully current: - --os-update: start a resinhup toward the latest balenaOS for the device type (POST actions/<base>/v2/<uuid>/resinhup, base resolved from /config). Online-only, skips OS < 2.14.0 (single-hop HUP floor per balena-hup-action-utils), bounded to --os-percent (default 5%) of the eligible population per run so the backlog ramps instead of stampeding. - --supervisor: point devices at the newest supervisor release for their CPU architecture (PATCH should_be_managed_by__release). Restricted to devices already on the target OS so the supervisor/OS pairing stays compatible, and tranche-bounded like the OS phase. Both phases honour the anthias_keep_pinned opt-out, stay dry-run by default, and keep the aggregate-only public-log output. The hourly workflow now passes --os-update --supervisor. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
Extends the existing balena “unpinner” job so that pinned/stale devices can be brought fully current via the cloud API: unpin app release (always), optionally trigger balenaOS HUPs in a bounded tranche, and optionally bump supervisors (also tranche-bounded) for devices already on the target OS.
Changes:
- Added
--os-updatephase to trigger per-deviceresinhupactions toward the latest balenaOS for each fleet’s device type (online-only, tranche-limited). - Added
--supervisorphase to bulk-patchshould_be_managed_by__releaseto the latest supervisor for the device architecture (only when already on the target OS, tranche-limited). - Updated the hourly GitHub Actions workflow to run the new OS-update and supervisor phases (still dry-run on manual dispatch by default).
Reviewed changes
Copilot reviewed 1 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
bin/balena_unpin_devices.py |
Adds OS-update and supervisor-refresh phases (cloud API), plus shared version parsing, tranche selection, and updated reporting. |
.github/workflows/balena-unpin-devices.yaml |
Updates the hourly job to invoke the script with --os-update --supervisor (and --apply on schedule). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
getSupervisorReleasesForCpuArchitecture returns the newest supervisor for an arch regardless of OS. On a device type frozen on a legacy balenaOS line that's unsafe: raspberry-pi2 tops out at balenaOS 5.1.x (devices run supervisor 15.x), and pointing them at 17.x would likely break them. Only run the supervisor phase when the fleet's target OS is on the calendar-versioned line (major >= 2025); legacy-OS fleets keep their OS-matched supervisor. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The device-actions host (actions.balena-devices.com) is behind Cloudflare, which 403s the default Python-urllib User-Agent as a banned client signature (error 1010) — so every OS-update POST failed. Send a descriptive User-Agent on all requests; the resinhup action then triggers normally (HTTP 202). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The per-fleet line printed the tranche size regardless of how many resinhup calls actually succeeded. Track started/failed per fleet and print both, so a few transient busy/offline failures are visible instead of hidden behind the planned count. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
5 tasks
vpetersson
added a commit
that referenced
this pull request
Jun 7, 2026
- CalVer (YYYY.0M.MICRO); still June 2026, micro 1 -> 2 - Ships the Qt 6 video audio fix (#3001) — PulseAudio in the viewer container; videos were silent on pi4-64/pi5/x86/arm64 since the QtMultimedia migration - Adds the arm64/Qt6 pi3-64 board and the Rock Pi 4 fleet (#2985) - Page-load watchdog so a stalled fetch can't freeze the display (#3003), Sentry error tracking for the Django services (#3007) - Redis data persisted to the mounted volume so device identity survives recreation (#2983); unpinner also rolls OS + supervisor updates (#2984) - Streamed backup downloads (#3005), 12-hour AM/PM asset times (#3002), BuildKit frontend via mirror.gcr.io (#3008) Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Issues Fixed
No tracked issue. Follow-up to #2980 (the hourly unpinner): clearing the app-release pin alone left those devices on whatever old balenaOS / supervisor they were flashed with, so they tracked the latest app but never the OS or supervisor.
Description
Extends
bin/balena_unpin_devices.pyso the one hourly cloud-API job brings pinned/stale devices fully current, in three independent, dry-run-by-default phases:is_pinned_on__release.--os-update— starts a host OS update (resinhup) toward the latest balenaOS for the device type. Replicates the SDK'sstartOsUpdate:POST https://actions.<deviceUrlsBase>/v2/<uuid>/resinhup(base resolved at runtime from/config, nothing hardcoded). Online-only; skips devices on balenaOS< 2.14.0(the single-hop HUP floor frombalena-hup-action-utilsactionsConfig— none of our device types are jetson, so the only constraints that apply are source≥ 2.14.0/ target≥ 2.16.0). Bounded to--os-percent(default 5%) of the eligible population per run so the backlog ramps instead of every device downloading + rebooting at once.--supervisor— points devices at the newest supervisor release for their CPU architecture (PATCH should_be_managed_by__release, resolved via the same query as the SDK'sgetSupervisorReleasesForCpuArchitecture). Restricted to devices already on the target OS so the supervisor/OS pairing stays within balena's compatibility window — devices still mid-HUP get their matching supervisor from the OS update itself. Tranche-bounded like the OS phase.Both new phases honour the
anthias_keep_pinnedopt-out, stay pure cloud-API (no balena CLI — the workflow still runs with justBALENA_TOKEN), and keep the aggregate-only output (no per-device uuids in the world-readable CI logs). The hourly workflow now passes--os-update --supervisor.bin/balena_fleet_maintenance.py(the CLI roller) does the same OS-update + unpin but can't see the pinned population; this keeps that division of labour and just lets the cloud-API job do the OS + supervisor too.Checklist
Validation:
ruff check+ruff format --checkclean. Dry-run (no--apply, zero mutations) against the live fleet exercised all three phases end-to-end — e.g.anthias-pi4:os-update -> 2026.1.0: online=857 eligible=230 tranche=12,supervisor -> 17.8.5 (aarch64): behind-and-on-target=627 tranche=32. Verified the/config→ actions-host, latest-host-OS, CPU-arch, and supervisor-release queries all return correctly with the production token.🤖 Generated with Claude Code