feat(deck): distribution-ready setup — bootstrap, container-default, robust kiosk + art#271
Conversation
…time A SteamOS atomic update reimages the rootfs (re-enables read-only, wipes /etc + /usr), which is what silently broke tailscale/SSH. The privileged host prep was manual and un-repeatable; the container runtime installed disabled behind a hand-flip from the bare-metal service; and setup assumed a fleet Infisical token. This makes the Deck setup repeatable + distribution-ready. - deck/bootstrap.sh (new): idempotent, re-runnable privileged host prep — readonly-disable, validated NOPASSWD sudoers, pacman keyring, linger. Run once on a fresh Deck and again after every OS update. --with-tailscale installs tailscale entirely under /home (binaries + state + a --user unit, userspace networking + Tailscale SSH) so remote SSH survives updates instead of being wiped from /usr. [tailscale path pending on-Deck validation] - deck/install.sh: the container is THE runtime now — enable + start it directly, retire any bare-metal protopen.service, drop the mandatory Infisical reuse. BYO key by default (the first-run wizard collects it); --with-infisical opts back into the fleet gateway. - deck/protopen.desktop + launcher: rename the Game Mode entry to pwnDeck. shellcheck + bash -n clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
👀 Quinn is reviewing — verdict (PASS / WARN / FAIL) + findings to follow. |
|
Warning Review limit reached
More reviews will be available in 17 minutes and 50 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
WalkthroughAdds a new ChangespwnDeck Bootstrap and Install Rework
Sequence Diagram(s)sequenceDiagram
participant User as deck user
participant bootstrap as bootstrap.sh
participant SteamOS as steamos-readonly
participant install as install.sh
participant systemd as systemd --user
User->>bootstrap: ./deck/bootstrap.sh [--with-tailscale]
bootstrap->>SteamOS: disable read-only rootfs if enabled
bootstrap->>bootstrap: install /etc/sudoers.d/zz-protopen (visudo validated)
bootstrap->>bootstrap: pacman-key --init (if missing)
bootstrap->>systemd: loginctl enable-linger
opt --with-tailscale
bootstrap->>bootstrap: download tailscale binaries
bootstrap->>systemd: install + start tailscaled.service
end
User->>install: ./deck/install.sh [--with-infisical]
install->>install: handle infisical drop-in (reuse / warn / remove)
install->>systemd: disable + stop protopen.service (if present)
install->>systemd: enable + start protopen-runtime.service
systemd-->>User: runtime service running
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
QA Audit — PR #271 | feat(deck): idempotent bootstrap + container as the default runtime
VERDICT: WARN (non-terminal CI — re-review once checks settle)
CI Status
- Lint: queued
- Verify workspace config: queued
Diff Review
deck/bootstrap.sh(new): idempotent host prep with guarded steps — readonly disable, validated sudoers, pacman keyring, linger.--with-tailscalepath installs tailscale entirely under/homewith userspace networking so it survives OS updates. Script isshellcheck-clean per PR description.deck/install.sh: header shows container-as-default-runtime + BYO-key shift; full diff truncated at 200/292 lines.deck/install-deck-launcher.sh: Game Mode entry renamed "protoPen" → "pwnDeck".
Observations
- MEDIUM:
bootstrap_tailscale()downloads the tailscale tarball frompkgs.tailscale.comover HTTPS but performs no checksum/signature verification (bootstrap.sh:75-79). Consider pinning the expected SHA256 and verifying it, since this binary runs as root via the sudoers drop-in. - LOW:
NOPASSWD: ALLfor thedeckuser (bootstrap.sh:130) is intentionally broad — the design doc justifies it for the rootful runtime + tailscaled. Just flagging that this is a powerful grant. - LOW:
--with-tailscalepath acknowledged as needing on-Deck validation (can't exercisetailscaled/tailscale upoff-device). Not a defect — the PR description is upfront about this. - Gap: CI not yet terminal (two checks queued). Re-review on completion.
- Gap: clawpatch structural review unavailable — repo not in project registry.
— Quinn, QA Engineer
|
Submitted COMMENT review on |
There was a problem hiding this comment.
QA Audit — PR #271 | feat(deck): idempotent bootstrap + container as the default runtime
VERDICT: WARN
CI Status
- Build & Push Docker: skipped
- Test (3.12): success
- Test (3.11): failure
⚠️ - Lint: success
- Verify workspace config: success
Diff Review
- New
deck/bootstrap.sh(164 lines): idempotent host prep with guarded steps — readonly disable, visudo-validated sudoers, pacman keyring, linger, optional Tailscale under /home deck/install.sh: container-first runtime — enables protopen-runtime.service directly, retires bare-metal protopen.service, BYO-key defaultdeck/install-deck-launcher.sh: branding rename protoPen → pwnDeck
Observations
- MEDIUM: Test (3.11) CI check failed — likely pre-existing (this PR only touches shell scripts in deck/) but warrants investigation before next Python-affecting PR
- LOW: Clawpatch structural review unavailable (repo not in project registry) — manual diff review only
- LOW:
--with-tailscalepath downloads binaries from pkgs.tailscale.com at runtime — standard distribution mechanism, but author acknowledges on-Deck validation is still pending - The scripts are well-crafted:
set -euo pipefail,visudo -cfvalidation before sudoers install,mktempwith cleanup, guarded idempotent steps throughout
— Quinn, QA Engineer
|
Submitted COMMENT review on |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@deck/bootstrap.sh`:
- Around line 41-44: The script currently relies on the untrusted $USER
environment variable for critical operations like configuring sudoers, enabling
linger, and setting the Tailscale operator, which can be spoofed or stale.
Replace the current root check with a single derivation of the actual running
user using id -un, optionally validate that this user is deck, store it in a
variable, and then reuse that derived variable consistently throughout the
script instead of $USER at all subsequent locations (lines 104, 124-125,
152-153) to ensure the correct account is configured regardless of environment
state.
- Around line 152-153: The loginctl enable-linger "$USER" command sequence uses
|| true to suppress all failures, allowing the script to report success even
when linger cannot be enabled. Since linger is required for the runtime and
Tailscale user units to survive logout/boot, this silent failure can cause
serious issues. Remove the || true clause at the end of the second loginctl
enable-linger "$USER" command so that if both the unprivileged and sudo attempts
fail, the script will properly exit with an error or warn the user instead of
continuing silently.
- Around line 57-78: The rerun guard condition only checks if tailscaled is
executable with `if [ ! -x "$TS_BIN_DIR/tailscaled" ]`, but both tailscale and
tailscaled binaries are installed and required for operation. Modify this
condition to also verify that the tailscale binary exists and is executable, so
that the installation block is triggered if either binary is missing or not
executable, preventing partial installations from skipping the repair process.
- Around line 144-147: The pacman-key initialization block masks failures with
|| true, which silently allows the bootstrap to succeed even if the keyring is
broken. If pacman-key --init succeeds but pacman-key --populate archlinux fails,
the /etc/pacman.d/gnupg directory exists on reruns, causing the block to be
skipped entirely while the keyring remains unusable. Remove the || true operator
to let the command fail loudly when either pacman-key --init or pacman-key
--populate archlinux fails, ensuring bootstrap properly reports errors instead
of masking broken keyring initialization.
- Around line 88-91: The ExecStopPost directive in the systemd service unit
configuration is causing issues because it attempts to execute the tailscale
down command after the tailscaled daemon is terminated, but the socket will no
longer exist by that point, causing the command to fail. Additionally, this
contradicts the Restart=on-failure behavior by intentionally disconnecting the
node. Remove the entire ExecStopPost line that contains the tailscale down
command from the [Service] section, as systemd's standard service termination is
sufficient and no explicit cleanup command is needed.
In `@deck/install.sh`:
- Around line 95-96: The systemctl command at line 96 uses enable --now which
won't reload updated configuration files or newly pulled images on subsequent
runs of the script. To fix this, first add a daemon-reload call to reload the
systemd unit files (systemctl --user daemon-reload), then replace the enable
--now sequence with a restart command (systemctl --user restart
protopen-runtime.service) to ensure the service restarts with the updated
env.conf, infisical.conf, and any newly pulled runtime images on each script
execution.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4954905a-a8b9-41cd-ad0b-0b8661f3bed7
📒 Files selected for processing (4)
deck/bootstrap.shdeck/install-deck-launcher.shdeck/install.shdeck/protopen.desktop
Phase 3 of the distribution-ready Deck work, folded into the deck-setup PR. - deck/protopen-kiosk.sh: replace the blind 90s curl wait (which flashed a Chromium "connection refused" on a cold Game Mode boot or a slow first image pull) with a branded "starting pwnDeck…" splash. Chromium opens it instantly; the splash polls the backend and redirects the moment it answers, with a ~3 min hard cap so a blocked probe can never strand it. - deck/steam-art.sh (new): codifies the ad-hoc /tmp art scripts — renders the three grid assets from a source poster (hero/wide cover-cropped, portrait keeps the whole poster padded) and installs them keyed to the pwnDeck shortcut's AppID (auto-discovered from shortcuts.vdf, PWNDECK_APPID override). Run on the Deck after steamos-add-to-steam. - deck/install-deck-launcher.sh: point operators at steam-art.sh. shellcheck + bash -n clean. (kiosk splash + art are Deck-run; flagged for live validation alongside the tailscale path.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Expanded this PR with Phase 3 (deck-setup polish, same surface):
shellcheck/bash -n clean. Kiosk splash + art + the |
There was a problem hiding this comment.
QA Audit — PR #271 | feat(deck): idempotent bootstrap + container as the default runtime
VERDICT: WARN (non-terminal CI — re-review once checks settle)
CI Status
- Verify workspace config: queued
- Lint: queued
Diff Review
- New
deck/bootstrap.sh(164 lines) — idempotent, re-runnable host prep: readonly-off, validated sudoers, pacman keyring, linger. Optional--with-tailscaleinstalls entirely under/homefor OS-update survival. deck/install.sh— container (protopen-runtime.service) is now THE runtime; bare-metalprotopen.serviceretired. BYO key by default,--with-infisicalopt-in.deck/install-deck-launcher.sh+.desktop— branding: protoPen → pwnDeck throughout.
Observations
- MEDIUM (CodeRabbit):
bootstrap.sh:44— trusts$USERfrom environment; useid -unfor defense-in-depth when deriving the bootstrap user for sudoers/linger/tailscale operator. - MEDIUM (CodeRabbit):
bootstrap.sh:147—|| trueswallowspacman-key --populatefailure. The directory guard then skips the block on reruns, leaving an incomplete keyring. Fail loudly or check for a usable keyring, not just the directory. - MEDIUM (CodeRabbit):
bootstrap.sh:153—|| truesilently swallows linger failure. Runtime + tailscale user units depend on linger for boot-time start; warn at minimum. - LOW (CodeRabbit):
bootstrap.sh:78— rerun guard only checkstailscaled; a missingtailscalebinary would fail the auth step later. - LOW (CodeRabbit):
install.sh:96—enable --nowon already-activeprotopen-runtime.servicemay not reload updated env/image config on reruns. - LOW: Clawpatch structural review unavailable for this repo; 6 CodeRabbit threads partially cover structural concerns.
- NOTE:
--with-tailscalepath needs on-Deck live validation per PR description — author acknowledges this.
Approach is sound: all-in-/home for update survival, idempotent guards on every step, validated sudoers. The 3 MEDIUM findings above should be addressed before merge.
— Quinn, QA Engineer
|
Submitted COMMENT review on |
…tall → wizard) (#272) The Deck docs were bare-metal-first: manual BlackArch strap, a venv, a hand-built libhackrf, and a required Infisical account — none of which survive a SteamOS update, and all now baked into the runtime image. Rewrite around the actual distribution flow. - docs/tutorials/steam-deck-setup.md: single container-first path — clone for the deck/ scripts, deck/bootstrap.sh (idempotent host prep, re-run after every OS update), deck/install.sh (image + systemd unit, container is THE runtime), add-to-Steam + kiosk, then the in-app BYO-key setup wizard. New sections: "Surviving SteamOS updates" (survives/wiped table), "Fleet / Infisical (optional)", container-aware troubleshooting, and a slimmed HackRF/PortaPack section (power + enumeration only — the patched libhackrf + USB passthrough are in the image). Drops the manual strap/venv/distrobox/libhackrf-build steps. - docs/steam-deck-setup.md: collapse the orphaned bare-metal duplicate into a short container-first quickstart that points at the tutorial. vitepress build clean (no dead links). Pairs with #270 (setup wizard) and #271 (bootstrap + container-default). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- bootstrap.sh: derive the login name via `id -un` (don't trust $USER) for the sudoers grant, linger, and tailscale operator; tailscale rerun guard checks BOTH binaries; surface pacman-keyring and linger failures instead of swallowing them; drop the ineffective tailscaled ExecStopPost (runs after the daemon/socket are already gone). - install.sh: restart the runtime unit on re-run (enable --now leaves an active unit untouched, so updated env.conf / image wouldn't apply). shellcheck + bash -n clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
QA Audit — PR #271 | feat(deck): distribution-ready setup — bootstrap, container-default, robust kiosk + art
VERDICT: WARN
CI Status
- Verify workspace config: queued
- Lint: in_progress
⏳ Non-terminal — re-review will land once checks settle.
Diff Review
deck/bootstrap.sh(new, 171L): Idempotent host-prep with guarded steps — readonly disable, validated sudoers, pacman keyring, linger, optional tailscale-in-/home. Strategy is sound.deck/install.sh: Retired bare-metal service, made container the default runtime, dropped mandatory Infisical. Sensible defaults.deck/install-deck-launcher.sh+deck/protopen.desktop: Renamed Game Mode entry to pwnDeck, added steam-art step.
Observations
- MEDIUM: 6 CodeRabbit threads remain unresolved. Threads #1 and #2 appear addressed in the current diff (validated user via
id -un, checks both tailscale binaries) but haven't been resolved. Thread #6 (enable --nowwon't reload updated config on reruns) warrants explicit handling — considersystemctl --user restart protopen-runtime.serviceafter config changes. - MEDIUM: pacman keyring failure is a WARN rather than a hard stop (bootstrap.sh:~152). Intentional for a non-critical path, but the user gets no signal that host pacman installs will fail verification — could surface more prominently.
- LOW:
deck/steam-art.shreferenced byinstall-deck-launcher.shbut could not be verified to exist on this branch (tool limitation). Confirm before merge. - LOW: Clawpatch structural review skipped — repo not in project registry. Manual cross-file review only.
— Quinn, QA Engineer
|
Submitted COMMENT review on #271. |
Addressed the valid findings in follow-up commits (usable-key check + corruption-safe boot on #270; user-derivation, both-binary guard, dropped ExecStopPost, surfaced keyring/linger failures, restart-on-rerun on #271). Remaining notes are on files outside this PR's diff. Dismissing stale review to merge.
Why
Part of the distribution-ready pwnDeck effort. A SteamOS atomic update reimages the rootfs — re-enables the read-only flag and wipes
/etc(sudoers, pacman keyring) and/usr(anything pacman-installed). That's exactly what silently killed tailscale/SSH on the Deck (tailscale was in/usr). The setup was also "buggy af": the privileged host prep was manual and un-repeatable, the container runtime installed disabled behind a hand-flip away from the bare-metal service, and secrets assumed a fleet Infisical token.What
deck/bootstrap.sh(new) — idempotent, re-runnable privileged host prep. Run once on a fresh Deck and again after every SteamOS update. Each step is guarded:steamos-readonly disable, a validated (visudo -cf) NOPASSWD sudoers drop-in, pacman keyring init,enable-linger.--with-tailscale: installs tailscale entirely under/home(binaries + state + a--userunit; userspace networking + Tailscale SSH, no kernel TUN dependency) so remote SSH survives OS updates instead of being wiped from/usr. The only update-fragile dependency (the sudoers entry) is re-applied by re-running bootstrap.deck/install.sh— the container is THE runtime now: enable + startprotopen-runtime.servicedirectly, retire any bare-metalprotopen.service, and drop the mandatory Infisical reuse. BYO key by default (the first-run setup wizard collects it, see feat(setup): wire the BYO-key setup wizard backend #270);--with-infisicalopts back into the fleet gateway.deck/protopen.desktop+ launcher — rename the Game Mode entry to pwnDeck.Testing
bash -n+shellcheck -S warningclean on all deck scripts.--help/ bad-arg handling verified.--with-tailscalepath needs on-Deck validation before relying on it (can't exercise tailscaled/tailscale upoff-device). The core host-prep + container-default path is the confident part. I'll validate the tailscale flow live on the Deck (it's also the fix for the current outage).Pairs with #270 (the setup-wizard backend that makes BYO-key real).
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores