fix(steamdeck): fix Steam OOBE 'unable to download required update' error #572

Open
lytedev wants to merge 6 commits from fix/steamdeck-oobe-update into main
Owner

Problem

Steam's OOBE on Jovian NixOS fails with "unable to download the required update" and setup cannot complete.

Root causes (four, found iteratively)

  1. jupiter-initial-firmware-update check exits 127. The script escalates via pkexec before checking its sentinel file, and the gamescope session has no polkit agent, so the day-1 firmware OOBE screen can never complete.

  2. The sentinel never exists on Jovian NixOS. On SteamOS, /etc/jupiter-ran-initial-firmware-update is created after the day-1 firmware flow; on Jovian NixOS firmware is Nix-managed so the flow never runs. Additionally, the steam-runtime pressure-vessel container mounts its own /etc/ (host /etc/ is invisible inside), so the sentinel must live under /run/ (shared with the host).

  3. /usr/bin/steamos-polkit-helpers/ doesn't exist. Steam hardcodes this path for OS/BIOS update checks. Jovian renamed the package to holo-polkit-helpers with no compat path, so every check returned 127. Upstream Jovian does not handle this (verified against the development branch).

  4. The Jovian gamescope-session hardcodes the steam binary path at build time, so programs.steam.extraPackages additions never reached the running session. Also /etc/systemd/user/ is read-only on NixOS, so the service override must be a drop-in under ~/.config/systemd/user/.

A fifth gotcha: the container maps host /usr/bin/ → container /bin/, and the host had no /usr/bin/sh — so the container had no /bin/sh and every #!/bin/sh script exited 127 regardless of being present.

Fix

  • Sentinel at /run/jovian/jupiter-ran-initial-firmware-update via tmpfiles (visible in-container)
  • High-priority jupiter-initial-firmware-update wrapper that checks the sentinel before pkexec
  • steamos-polkit-helpers compat dir (host tmpfiles symlink + FHS package via programs.steam.extraPackages) with steamos-update/steamos-select-branch calling jovian-stubs directly and a jupiter-biosupdate exit-0 stub
  • steamos-update/steamos-update-rauc stubs in systemPackages
  • /usr/bin/sh symlink so the container gets /bin/sh
  • steam-launcher drop-in at ~/.config/systemd/user/ pointing at config.programs.steam.package (plus ownership fixes for the tmpfiles unsafe-path-transition rejection)

Verified

  • jupiter-initial-firmware-update check → 0 (was 127)
  • /usr/bin/steamos-polkit-helpers/steamos-update check → 7/8 (was 127)
  • Steam OOBE now successfully downloading a real client update (1780352834 → 1781041600) that previously failed

🤖 Generated with Claude Code

## Problem Steam's OOBE on Jovian NixOS fails with **"unable to download the required update"** and setup cannot complete. ## Root causes (four, found iteratively) 1. **`jupiter-initial-firmware-update check` exits 127.** The script escalates via pkexec *before* checking its sentinel file, and the gamescope session has no polkit agent, so the day-1 firmware OOBE screen can never complete. 2. **The sentinel never exists on Jovian NixOS.** On SteamOS, `/etc/jupiter-ran-initial-firmware-update` is created after the day-1 firmware flow; on Jovian NixOS firmware is Nix-managed so the flow never runs. Additionally, the steam-runtime pressure-vessel container mounts its own `/etc/` (host `/etc/` is invisible inside), so the sentinel must live under `/run/` (shared with the host). 3. **`/usr/bin/steamos-polkit-helpers/` doesn't exist.** Steam hardcodes this path for OS/BIOS update checks. Jovian renamed the package to `holo-polkit-helpers` with no compat path, so every check returned 127. Upstream Jovian does not handle this (verified against the development branch). 4. **The Jovian gamescope-session hardcodes the steam binary path at build time**, so `programs.steam.extraPackages` additions never reached the running session. Also `/etc/systemd/user/` is read-only on NixOS, so the service override must be a drop-in under `~/.config/systemd/user/`. A fifth gotcha: the container maps host `/usr/bin/` → container `/bin/`, and the host had no `/usr/bin/sh` — so the container had **no `/bin/sh`** and every `#!/bin/sh` script exited 127 regardless of being present. ## Fix - Sentinel at `/run/jovian/jupiter-ran-initial-firmware-update` via tmpfiles (visible in-container) - High-priority `jupiter-initial-firmware-update` wrapper that checks the sentinel before pkexec - `steamos-polkit-helpers` compat dir (host tmpfiles symlink + FHS package via `programs.steam.extraPackages`) with `steamos-update`/`steamos-select-branch` calling jovian-stubs directly and a `jupiter-biosupdate` exit-0 stub - `steamos-update`/`steamos-update-rauc` stubs in systemPackages - `/usr/bin/sh` symlink so the container gets `/bin/sh` - steam-launcher drop-in at `~/.config/systemd/user/` pointing at `config.programs.steam.package` (plus ownership fixes for the tmpfiles unsafe-path-transition rejection) ## Verified - `jupiter-initial-firmware-update check` → 0 (was 127) - `/usr/bin/steamos-polkit-helpers/steamos-update check` → 7/8 (was 127) - Steam OOBE now successfully downloading a real client update (1780352834 → 1781041600) that previously failed 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chore(stalwart): overlay stalwart + stalwart-cli to 0.16.x
Some checks failed
/ check-format (push) Has been cancelled
/ build (push) Has been cancelled
9a862869f0
Bump stalwart to 0.16.8 and stalwart-cli to 1.0.0 in the flake overlay.
stalwart-cli moved to its own repo (stalwartlabs/cli) in 0.16, so its
cargoDeps must be fetched from the new repo rather than inherited from
the server package.
fix(steamdeck): fix Steam OOBE 'unable to download required update' error
Some checks failed
/ check-format (push) Successful in 3m27s
/ build (push) Failing after 14m43s
c87210e842
Three issues prevent Steam's initial setup from completing on Jovian NixOS:

1. jupiter-initial-firmware-update check escalates via pkexec before checking
   the sentinel file. With no polkit agent in the gamescope session, pkexec
   returns 127 and Steam shows a 'Day one firmware update' OOBE screen that
   cannot complete.

2. /etc/jupiter-ran-initial-firmware-update (the sentinel) never exists on
   Jovian NixOS, so even if pkexec worked the update screen would still appear.
   On SteamOS proper this sentinel is created after the day-1 firmware update
   runs; on Jovian NixOS firmware is managed by Nix derivations so the day-1
   flow should always be considered done.

3. Steam calls /usr/bin/steamos-polkit-helpers/steamos-update at a hardcoded
   path for OS-update checks. In newer Jovian the package was renamed to
   holo-polkit-helpers but no compat path was created, so all OS update checks
   return 127 (path not found).

Fix:
- Persist the sentinel via environment.etc so it always exists.
- Provide a high-priority wrapper for jupiter-initial-firmware-update that
  checks the sentinel before touching pkexec (a plain file-existence check
  needs no root), short-circuiting the whole firmware-update OOBE path.
- Create /usr/bin/steamos-polkit-helpers/ via systemd.tmpfiles pointing at
  a compat directory whose scripts call the jovian stubs directly (no pkexec),
  so steamos-update check returns 7 (no update) or 8 (reboot needed) cleanly.
lytedev force-pushed fix/steamdeck-oobe-update from c87210e842
Some checks failed
/ check-format (push) Successful in 3m27s
/ build (push) Failing after 14m43s
to 8bed47521b
Some checks failed
/ check-format (push) Has been cancelled
/ build (push) Has been cancelled
2026-06-09 18:46:27 -05:00
Compare
fix(steamdeck): fix Steam OOBE 'unable to download required update' error
Some checks failed
/ check-format (push) Successful in 7s
/ build (push) Failing after 43s
699c892bad
The steam-runtime runs in a pressure-vessel container that mounts its own /etc/
but shares /run/ with the host. Four issues block Steam's initial setup:

1. jupiter-initial-firmware-update escalates via pkexec before checking the
   sentinel. No polkit agent runs in the gamescope session, so pkexec exits 127
   and Steam shows a 'Day one firmware update' OOBE screen that cannot complete.

2. The sentinel /etc/jupiter-ran-initial-firmware-update never exists on Jovian
   NixOS (firmware is managed by Nix, not the SteamOS update flow). Even with a
   polkit agent, the update screen would appear.

3. Steam checks /usr/bin/steamos-polkit-helpers/steamos-update at a hardcoded
   path. In newer Jovian the package was renamed to holo-polkit-helpers with no
   compat symlink, so all OS update checks return 127.

4. Steam checks /usr/bin/steamos-polkit-helpers/jupiter-biosupdate similarly;
   also missing.

Fix:
- Create /run/jovian/jupiter-ran-initial-firmware-update as the sentinel via
  systemd.tmpfiles (visible inside the container; /etc/ is not).
- Provide a high-priority jupiter-initial-firmware-update wrapper that checks
  the /run/ sentinel before touching pkexec; exits 0 cleanly for 'check'.
- Create /usr/bin/steamos-polkit-helpers/ via systemd.tmpfiles pointing at a
  compat dir whose scripts call jovian stubs directly (no pkexec):
  steamos-update, steamos-select-branch (stubs exit 7/8), jupiter-biosupdate
  (stub exits 0 = no update).
fix(steamdeck): add steamos-update and steamos-update-rauc stubs to system path
Some checks failed
/ check-format (push) Successful in 7s
/ build (push) Failing after 44s
dd73085081
The OOBE apply job calls steamos-update and steamos-update-rauc by PATH lookup
(not hardcoded path). SYSTEM_PATH includes jovian-stubs but subprocess execution
inside the steam-runtime container doesn't reliably pick it up.

Adding stubs to environment.systemPackages places them in
/run/current-system/sw/bin/ which is bind-mounted into the steam-runtime
container and found regardless of how subprocesses inherit PATH.

steamos-update: copy of the jovian stub (exits 7 = no update, 8 = reboot needed)
steamos-update-rauc: stub that prints '-- No entries --' and exits 0 (no RAUC
update pending, matching what RAUC would say on a non-RAUC NixOS system)
fix(steamdeck): add /usr/bin/sh so container has /bin/sh for #!/bin/sh scripts
Some checks failed
/ check-format (push) Successful in 8s
/ build (push) Failing after 44s
749a76d115
The steam-runtime pressure-vessel container maps the host's /usr/bin/ to /bin/
inside the container. The host has /bin/sh but not /usr/bin/sh, so the container
ends up with no /bin/sh.

This causes every script with a #!/bin/sh shebang (jovian stubs, polkit helpers,
and anything else relying on POSIX sh) to fail with exit 127 when called from
inside the container. This is why steamos-update returned: 127 despite the jovian
stub being accessible at /usr/bin/steamos-update in the container.

Add /usr/bin/sh -> bash via systemd.tmpfiles so the container gets /bin/sh.
fix(steamdeck): inject steamos-polkit-helpers into Steam FHS container
Some checks failed
/ check-format (push) Successful in 7s
/ build (push) Failing after 42s
1fbe0a72b9
The Steam OOBE apply job calls /usr/bin/steamos-polkit-helpers/steamos-update
at a hardcoded path inside the Steam FHS pressure-vessel container.

The path was missing because:
1. The Jovian gamescope-session steam-launcher hardcodes the steam binary path
   at build time — programs.steam.extraPackages built a new steam package with
   our steamos-polkit-helpers-fhs extras, but the gamescope-session kept calling
   the old binary, so the new FHS rootfs was never used.

2. /etc/systemd/user/ is read-only on NixOS; dropping a systemd override there
   via environment.etc fails.

Fix:
- Add steamosPolkitHelpersFHS to programs.steam.extraPackages so the new steam
  binary includes /usr/bin/steamos-polkit-helpers/ in its FHS rootfs.
- Create a steam-launcher.service drop-in at
  ~/.config/systemd/user/steam-launcher.service.d/99-fhs-override.conf via
  systemd.tmpfiles that overrides ExecStart to use the updated steam binary
  (config.programs.steam.package) instead of the Jovian-hardcoded one.
- Fix ~/.config/systemd/ and user/ directory ownership (NixOS creates them as
  root, which triggers tmpfiles unsafe path transition rejection).
Some checks failed
/ check-format (push) Successful in 7s
Required
Details
/ build (push) Failing after 42s
Required
Details
This pull request has changes conflicting with the target branch.
  • lib/overlays/default.nix
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/steamdeck-oobe-update:fix/steamdeck-oobe-update
git switch fix/steamdeck-oobe-update
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lytedev/nix!572
No description provided.