Conversation
Found 12.1 GiB of already-expired-but-uncollected artifacts via the API, deleted them manually, and logged the recurring fix (delete CI artifacts after they land in a release) as a housekeeping item. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Card was single-width at 17px monospace, which wraps mid-octet on a full IPv4 address (word-break:break-all). Matches the existing Hostname card's span-2 treatment for another field with unpredictable length. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
companion-dashboard's main.js checks process.argv.includes('--kiosk-mode'),
not '--kiosk' -- the wrong flag meant kiosk mode (fullscreen + the
auto-started :80 web server) never actually triggered. Confirmed against
upstream source, not guessed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Depends on #13's fix landing first -- companion-dashboard only auto-starts its /control web server (port 80) when real kiosk mode is detected. Link only shown while the kiosk service is actually active (dashboard_enabled() == svc_active), same gating as the existing Toggle Fullscreen button. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
toggle_net()/pin_static() -- the functions the deck's NET key drives -- called write_networkd_config() unconditionally, writing systemd-networkd files and restarting systemd-networkd. On Raspberry Pi OS, which ships NetworkManager by default, that does nothing durable: NetworkManager keeps managing the interface and reasserts its own connection profile's DHCP setting on next boot, which is exactly the "has to be reselected every boot" behavior reported. The manual /network web form already branched on nmcli_available() vs networkd correctly -- the deck-driven functions never got the same treatment. Added write_nmcli_config() (nmcli connection modify, which persists straight to the on-disk profile) and an apply_net_config() dispatcher, and pointed toggle_net()/pin_static() at the dispatcher instead of the networkd-only function directly. get_current_net_mode() in dpx-deck-splash.py already reads live kernel state via `ip addr`, so it correctly reflects whichever backend actually applied the change -- no read-side fix needed, this was write-path only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
#11, #12) Two related gaps, one mechanism, per the design decision to solve them together rather than as separate bolted-on fixes: - #11: nothing brought dpx-deck-splash.service back once a mode service's own Restart=on-failure exhausted its StartLimitBurst -- the deck just went dark/stale forever. Fixed with OnFailure=dpx-deck-splash.service drop-ins on all three mode units (as .service.d/ overrides, not direct edits, since they ship from vendor .deb packages). OnFailure= only fires once a unit's ActiveState actually reaches "failed" -- systemd holds it in "activating (auto-restart)" between individual retries -- so this is inherently once-per-real-outage, not once-per-retry. Event-driven, no polling. - #12: dpx-deck-splash.service and the persisted mode service were both WantedBy=multi-user.target, racing at boot with Conflicts= picking whichever won -- confirmed nondeterministic on hardware ("sometimes splash wins and blocks it without a GO press"). Fixed with a new dpx-mode-select.service oneshot that reads /etc/dpx-mode at boot and starts exactly that one service, falling back to the splash only if nothing's persisted or the target refuses to start. Splash is no longer auto-enabled on its own, so there's nothing left to race. Not live-verified yet -- no device access this pass. Needs a real boot-cycle test and a forced-permanent-failure test once hardware is available; also worth rechecking the "GO does nothing" symptom against this fix, since execute_staged()'s existing mode_dead check already looks correct on paper and may have been a downstream effect of the same race rather than its own bug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two layers: release-action.yaml's release job now deletes its own run's CI artifacts right after they land in the GitHub Release (they're redundant once the .img.gz is a real release asset). New weekly artifact-sweep.yaml workflow catches everything else -- stray feature- branch/debug builds that never go through the release job -- by deleting anything already past its own expires_at, since GitHub's own cleanup can lag by weeks in practice (confirmed this session: found and manually cleared 12.1GiB that was up to three weeks overdue). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both image builds ran on ubuntu-latest (x86_64) and emulated the whole chroot provisioning stage via qemu-aarch64-static -- every apt-get/dpkg call inside the mounted arm64 image ran through QEMU user-mode translation, which is commonly 5-10x slower than native for that kind of CPU-bound work. Packer's arm-image plugin already supports skipping this: it only adds the QEMU/binfmt steps when `!ImageArch.IsNative()` (pkg/builder/builder.go). Set image_arch = "arm64" in the shared dpx-buttonode.pkr.hcl and switched both builder workflows to ubuntu-24.04-arm (free, GitHub-hosted, native arm64) -- with host arch matching image_arch, the plugin skips QEMU entirely on its own, no custom logic needed. Removed the now-unnecessary qemu-user-static install steps. Verified before touching CI: Packer itself installs via HashiCorp's own apt repo (multi-arch, resolves correctly on arm64), and packer-plugin-arm-image v0.2.7 publishes a linux_arm64 build. Isolated on its own branch off main, not mixed into the action-plan hardware-fix branch -- this is unverified until a real CI run confirms it, and a broken build here shouldn't be confused with a regression in the already-tested fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Removing it broke the build outright: packer-plugin-arm-image's Prepare() unconditionally resolves a qemu_binary path via exec.LookPath, regardless of whether the emulation is actually needed later -- only its separate Run()-time IsNative() check decides that. Without the binary present, Prepare() falls back to an "embedded qemu" feature that's amd64-only and fails outright on arm64: embedded qemu is not available - currently, embedded qemu is only available for linux amd64. please download qemu-user-static manually Confirmed via a live test build (33982012810) that failed exactly this way. Re-added the install -- it's a fast package install, not the slow part we're trying to avoid, and the plugin's own IsNative() check still skips actually invoking it during the chroot provisioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…10) Root cause found via live SSH investigation on dpx-buttonode-2199 (rockpi-s), confirmed with three isolated tests: 1. udev_retrigger() alone (what switch_mode() actually called) does NOT recreate /dev/hidraw* once a libusb consumer (Buttons/Satellite/ deck-splash) has detached the kernel driver to claim the device -- verified by running it in isolation and confirming hidraw stayed missing. 2. The full unbind/bind (usb_power_cycle(), already built and already wired into the manual /power-cycle-deck button) DOES recreate it -- verified live. 3. With hidraw present, Companion picks up the Stream Deck cleanly at startup ("Surface panel ready: streamdeck:..."). Companion's surface module only scans once at startup and never retries, so if hidraw is missing at that exact moment, Companion silently finds nothing -- this is why the reported symptom looked intermittent/mode-switch- specific rather than a permissions problem. Fix: switch_mode() now calls the same usb_power_cycle() fallback /power-cycle-deck already used manually, instead of the gentle-only udev_retrigger(). usb_power_cycle() already tries the gentle retrigger first and only escalates to the disruptive unbind/bind if that alone wasn't enough, so this is a safe drop-in with no added cost in the common case. Verified end-to-end on real hardware: forced hidraw missing via deck-splash, ran the actual --apply-mode companion production path with the fix applied, hidraw came back automatically, Companion started and opened the surface panel successfully. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Requested live during #10 testing. Deliberately distinct from switch_mode(): stop_current_mode() doesn't touch /etc/dpx-mode or any enable/disable state, it just stops whichever mode service is currently running and starts dpx-deck-splash. Since dpx-mode-select.service only runs once at boot (see #11/#12's fix), a manual systemctl stop with nothing to bring splash back would otherwise leave the deck dark -- this starts it explicitly instead. Verified live: clicked it via the web UI, confirmed /etc/dpx-mode unchanged, the mode service stopped, and dpx-deck-splash came up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ng to fit Requested live: the single-line render shrank a 10-char password down to ~9-10px to fit the key width, making similar characters (5 vs S, 0 vs O) genuinely hard to tell apart on the deck's small screen -- confirmed by directly causing a transcription error reading one off during this same session. render_password_key() stacks the password into 4-char lines instead, so each line only needs to fit 4 characters and the font can stay much larger (18px vs ~9-10px in this test). Verified visually: rendered the actual key image on real hardware and pulled it back to look at it, not just checked it doesn't crash. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ally autostarts (#12) Confirmed live on hardware: `systemctl is-enabled dpx-dashboard` says "enabled", but the actual symlink is /etc/systemd/system/graphical.target.wants/dpx-dashboard.service -- while this system's default target is multi-user.target (`systemctl get-default`), forced there deliberately by this same script (line 43) to work around an unrelated boot-hang bug where a stray xserver-xorg postinst flips the default target. graphical.target itself is confirmed `inactive`, i.e. never reached at boot, so Dashboard's enablement symlink lives somewhere that's never visited -- it stays correctly "enabled" (systemd's own persisted state is fine) but that enablement never actually triggers a start. This is why toggling Dashboard on via the web UI worked in the moment (systemctl enable --now starts it immediately regardless of target) but didn't survive a reboot, exactly as reported on #12: "restarting does go right back into Companion... [but Dashboard] doesn't remember the status." Fix: WantedBy=graphical.target -> WantedBy=multi-user.target, matching the target this system actually reaches. Also dropped the now-stale `graphical.target` from After= (harmless as an ordering constraint on a never-started target, but confusing to leave in). dpx-dashboard.service launches X directly via xinit on tty7 itself -- it never depended on graphical.target's own display-manager machinery to begin with, so this doesn't change how it starts, only when systemd is willing to pull it in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only rendered when dashboard_installed() -- same gating as the Devices tab section. Verified live: correctly shows "inactive" while dpx-dashboard is crash-looping (activating/auto-restart, not active) on hardware without a real display attached. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Requested directly: dpx-mode-select.service (from #11/#12's original fix) always starts the persisted mode at boot with no opt-out. Some setups want to land on the deck splash every boot instead and switch modes manually. Existence-based marker (/var/lib/dpx-mode-autostart-disabled), same convention as /var/lib/dpx-hostname-set -- absent by default, so a fresh image behaves exactly as before (checkbox defaults to checked). dpx-mode-select.sh checks it first and goes straight to splash if present, skipping the mode-service lookup entirely. Verified live: toggled off via the web UI, confirmed the marker file appears/disappears correctly, re-enabled to restore default state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…x/action-plan-batch
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.
Summary
Implements everything from ACTION-PLAN.md that's verifiable without live hardware:
--kiosk→--kiosk-mode(confirmed root cause against upstream source)/controllink in web UI (depends on feat: Dashboard should start full screen #13)toggle_net()/pin_static()were networkd-only, added an nmcli-aware dispatcherOnFailure=drop-ins, event-driven per design decisiondpx-mode-select.servicepicks exactly one, deterministicallyNot done here (need live device access)
Test plan
py_compile,bash -n) — done/controllink resolves after feat: Dashboard should start full screen #13 (feat: Dashboard should start full screen #13, feat: Dashboard link in DPX web interface #15)🤖 Generated with Claude Code