How it works - 60plus/SteamOS_Nvidia_Installer GitHub Wiki

Manual home

How it works

This page explains the implementation for contributors. For installation steps, use Build the USB image and Install and first boot.

What the project changes

The starting point is Valve's recovery image. The builder makes a separate copy, adds NVIDIA support and project tools, and adjusts the recovery installer. SteamOS, Steam, Gamescope and the A/B partition layout remain their upstream components. This project does not provide a separate Linux distribution or its own replacement OS updater.

The original image-building and self-healing approach comes from 28allday/steamos-nvidia-installer. This project extends it with desktop tools, validation, device handling and tests.

Building the USB image

steamos-nvidia-installer.sh coordinates the build. It mounts the image through loop devices, reads the exact SteamOS kernel version and resolves a driver package version. The selected package URLs are pinned in driver.conf so a later OS repair does not silently select a different NVIDIA release.

The builder uses an isolated build root to compile NVIDIA's open kernel modules against that kernel. It copies the required userspace libraries into the image, checks them with the target's loaders and generates the boot files. The 32-bit checks are for 32-bit games and their runtime dependencies on a 64-bit OS. --trim-cuda removes compute libraries; it does not switch the OS to another architecture. Package caches speed up repeated builds, but cached output still has to match the selected inputs.

The build adds installation shortcuts and records image provenance. The source image is retained. A successful shell syntax check is not proof that an image boots or a game renders correctly; those are separate tests.

Installation and USB disks

scripts/install-target.py discovers candidate disks and excludes the running installer's disk. It distinguishes external USB targets from internal disks and checks the selected target before handing work to the recovery installer. Fresh installation erases the selected disk. Reinstallation preserving home expects an existing compatible SteamOS layout.

scripts/patch-repair.py adjusts the recovery script. The narrow installer permission helper allows only the installation operation that requires it; ordinary installed-system administration still uses the user's password. External USB installation uses the same OS layout. Moving that installation between unrelated PCs is a separate compatibility question.

The A/B slots

SteamOS maintains two system partition sets, A and B. Each has rootfs, EFI and var partitions. The shared home partition holds user data, including games and saves. The boot configuration selects which set will run next.

Term Meaning
Active slot The system currently running
Inactive slot The other system set, available for preparation
Selected slot The set requested for the next boot; it can differ from active
Shared home User data shared across boots of either set

A and B are interchangeable. Code uses SteamOS partset links such as self and other, rather than assuming that B is always the update target. Device checks must resolve to different block devices before writing.

An update replaces the inactive system. It does not create an unlimited history of snapshots. A later OS update or driver change can overwrite the previous system. Switching slots does not revert shared home data, saves or every user setting. Valve's migration also handles settings between the system sets.

Updates installed by Steam

The hook supports the inspected steamos-bootconf activation formats and the SteamOS 3.9 Preview holo-bootconf format, retaining Valve's activation mode. Repair runs before activation, with the inactive slot marked invalid and its retry counters cleared. Unknown formats are rejected rather than patched by approximate matching. Channel selection is separate: normal builds keep the original channel; --experimental-beta and --experimental-preview explicitly select their respective channels.

In the inspected Preview handler, Valve tries its supported finalizer names before activating the slot. Its shutdown service subsequently catches up changes in /etc and /var and completes configuration migration. The NVIDIA hook preserves that sequence and repairs /usr before activation. Preview's steamos-update alias points to holo-update; the installer saves a regular copy of that native executable before installing its wrapper.

Valve downloads and stages the new OS. The shared RAUC completion hook invokes repatch.sh, generated by the builder, before the update can be accepted. The repair builds the pinned NVIDIA driver for the new kernel, installs its libraries and restores project integrations in the new root.

Validation includes kernel/module compatibility, project file checksums, userspace library dependencies and available space. A completion marker records an accepted result. Failures keep the staged system disabled instead of treating a downloaded OS as ready merely because the download finished.

Before the final free-space check, the repair helper can reclaim unused Btrfs metadata allocation in the inactive slot. It runs only below the 256 MiB reserve, with at most four filtered passes of one metadata block group each. It preserves files and redundancy profiles, synchronizes the filesystem and measures space after each pass, including partial failures. An unrecovered shortage still stops slot activation. See the Btrfs balance documentation.

The hook uses a separate mount namespace with one-way (slave) propagation. Systemd automounts, including EFI and ESP, remain visible inside it. Mount changes made by the hook do not propagate back to the host. Temporary noatime settings protect Valve's migration from writes to a frozen filesystem. These settings are not a permanent change to the user's filesystem mount policy. The steamos-update wrapper and shared hook cooperate so supported update entry points receive the same repair step.

Changing the NVIDIA driver

The desktop driver manager separates selection from preparation:

  1. driver-manager.py presents available releases and GPU compatibility hints.
  2. driver-change.py resolves the selection, checks OS/update state and records a private transaction request.
  3. driver-stage.sh copies the current system into a temporary Btrfs image, gives it a new filesystem identity and validates the intended target.
  4. The inactive boot entry is disabled before its root filesystem is overwritten.
  5. Valve's migration and the repair hook prepare and validate the selected driver.
  6. Only success permits selection of the prepared slot for restart.

A compatibility hint is not a completed test on every GPU. The manager uses NVIDIA's open kernel driver path; GTX 10-series and older architectures are outside that path. The tool does not convert the install to a legacy proprietary kernel driver.

Updating installer tools from releases

SteamOS NVIDIA Installer Update applies project integration files without a new USB installation. It is included only when the builder receives a release source configuration. Public builds are intended to use GitHub releases with a production signing key. A source must be configured explicitly; an unavailable server does not cause a switch to another source.

The source configuration contains an API URL, download origin, channel policy and public verification key. The private signing key is not included. The release packer creates a bundle, a manifest and an Ed25519 signature. The manifest names the integration version, tested SteamOS versions and SHA-256 file hashes. From updater 0.1.6, the oldest listed SteamOS version is the minimum accepted; unlisted versions at or above it pass the version check with a warning. Older updaters require an exact version match. Preparation runs driver and addon checks, but those checks do not establish full runtime compatibility with an untested OS.

The updater verifies the signature, archive checksum and file hashes, rejects unexpected paths and links, and limits download size. It accepts a fixed list of project files rather than arbitrary filesystem replacements. Confirmation pins the manifest digest so a changed release must be checked again.

That list belongs to the tool already installed, not to the release, so a release can only place files the installed version already knows how to place. From 0.1.8 a system whose installed version predates a list entry installs the rest, names the new file in the output of the update check as one it will not install, and stamps the new version while leaving that file unchanged or absent. Versions before 0.1.8 refuse such a release outright. When the running tool does not know a new file name, two installations can be needed: the first delivers the tool that knows it, and, after a restart, the second places the file. The second installation does not require a higher release number.

From 0.2.1, offer() returns repair when the installed and offered versions match but payload_state() finds missing files, symlinks or non-regular files, or unreadable files or checksum differences in the Gamescope or performance-overlay groups. The desktop window lists those files and offers to apply the same signed release again. Other recognized payload files are checked for presence and regular-file type, not byte equality. A newer release is offered as an ordinary update. Both paths use the existing signed A/B transaction; they do not rewrite the active system.

Integration updates share the driver's transaction lock, staging mechanism and rollback record. They retain the selected NVIDIA driver and verify it rather than rebuilding it unnecessarily. New helpers and policies are applied to the inactive root. The old image build metadata is preserved; a separate integration-version.json records the current tools version.

Repository transition in 0.2.2

The 0.2.2 transition uses the existing signed archive contract. After the installed updater writes the new payload to the prepared root, it sources that root's pc-support.sh. Its pc_install_installer_update hook calls the new updater's migrate-source command before addon hashes are recorded.

Migration changes only release_api, from the old repository to 60plus/SteamOS_Nvidia_Installer, and only when the complete configuration equals the previous official source, including all five fields and no extra fields. Formatting and key order do not affect the comparison. The verification key and custom configurations are preserved. The destination root is resolved before checking paths, so a build workspace reached through a symlink is supported while symlinks inside the prepared root are refused. The configuration is validated, written atomically, and read back before pc_write_addon_manifest records it.

An installed update already verifies the inactive device before this hook runs. The image builder uses the same hook for a mounted recovery image. The running system's configuration is not rewritten by the update transaction; reboot selects the prepared slot and its new source. Automated tests cover these paths.

A subsequent hardware test covered installation of the published 0.2.2 release. The user reported using the desktop update window, and the update log recorded a matching installation command. After reboot, all 21 installed payload files matched the signed release manifest, the new source was active and the verification key was unchanged. The previous slot retained 0.2.1 and its old source configuration; standard rollback had passed an earlier test.

The running system's old configuration and the updater's source-selection code indicate that this installation downloaded from the old repository. The download URL was not logged, so that origin is an inference rather than a recorded URL. The new repository was confirmed as the source used for update checks after reboot. These results cover one system and do not establish compatibility with other hardware or SteamOS releases.

This mechanism cannot deliver every possible change to image building or disk layout. Such changes may still need a new installer. See Installer updates for the user workflow.

Recovery and interrupted work

Preparation marks the target invalid before writing. The current system is retained, so interrupted preparation can be retried from the working slot. The desktop rollback action verifies that the saved transaction still describes both systems before selecting the previous slot. It refuses stale rollback after another operation has replaced the relevant files or OS version.

This is validated manual recovery, not a promise of automatic rollback after any failed boot. Power loss at different stages, firmware behavior and hardware failures need separate coverage. Keep recovery media available.

Display and Bluetooth helpers

  • hdr-defaults.py initializes new Steam display profiles with HDR off before Steam starts. It preserves existing choices; it is not a permanent HDR ban.
  • safe-graphics.py provides an optional recovery mode requesting conservative output settings. It does not replace the graphics driver or guarantee a fix for every compositor artifact.
  • bluetooth-resume.py attempts reconnection for relevant Bluetooth audio after resume. Reconnection support is different from waking the PC with a controller.
  • Controller support uses the stock stack by default. Optional xpadneo is built and checked separately when enabled. Its Bluetooth profile and module changes can affect how Steam sees a controller, so it is not required for all pads.

Performance overlay

An image built with all components in the build guide includes the corrected MangoApp. Custom builds add it with --mangoapp-dir. It is built from a pinned MangoHud revision with an upstream fix that refreshes NVIDIA sensor settings on each sampling cycle. A second patch hides unsupported NVIDIA voltage and junction temperature fields. It does not fabricate sensor values or change the NVIDIA driver. Steam overlay levels 3 and 4 also show the active renderer model, its dedicated VRAM capacity when available, and the CPU model reported by Linux. Smaller overlay levels keep their original layout. Hardware names are detected at startup; they are not taken from the computer used to build the image.

tools/build-mangoapp.sh builds the executable in a disposable SteamOS build root. The artifact contains the binary, a checksum and source metadata, and the MangoHud license. --mangoapp-dir includes these files in an image or a signed integration package. A build without that option keeps the stock overlay.

Installation checks the artifact checksum and resolves its runtime libraries in the target root before creating a systemd user service override. The override starts the corrected binary from /usr/lib/steamos-nvidia; the distribution's binary is preserved. The OS repair hook copies the artifact to the next slot and checks it again. An incompatible artifact fails preparation instead of silently installing an unusable overlay.

Capture-color correction

An image built with all components in the build guide includes a separately maintained Gamescope build, based on version 3.16.23.6. It carries four corrections: two capture fixes adapted from upstream work, a correction this project made for the corrupted Game Mode menus described below, and a filter that stops Steam being offered display modes Gamescope will reject. The two capture fixes are present in upstream Gamescope 3.16.30. The menu correction and the advertised mode filter are not in that version, so a system using it instead of this build does not have them. The version checked is upstream Gamescope 3.16.30. Outgoing Remote Play has passed hardware testing; screenshot formats still need separate acceptance. Custom builders supply the artifact with --gamescope-dir.

The Gamescope pin is temporary and reviewed manually. With each SteamOS update, we check what Valve has fixed in upstream Gamescope. When we adopt upstream code that contains a correction, we remove the corresponding patch from this project's patch set. We move the pin to a newer Gamescope only after testing shows it is an improvement for supported NVIDIA systems. These are manual maintenance decisions, not automatic changes made by the updater. Once the Gamescope shipped by SteamOS works without our corrections, we will retire the separate build and use Valve's compositor.

The capture correction is backported to Gamescope version 3.16.23.6. It checks whether the Vulkan device supports the 10-bit RGB format required for capture. When that format is unavailable, it uses the supported BGR layout and decodes screenshot channels in the matching order. The shared capture pool also requests sampled-image usage: the RGB-to-NV12 shader reads these textures. Missing that usage can produce black video even when the local game renders normally. This backport targets swapped red and blue in PipeWire capture, including Remote Play, and AVIF screenshots. It does not change the monitor mode, HDR defaults, kernel, or NVIDIA driver, and is not a fix for HDMI HDR failures. The same build also carries this project's own fix for corrupted Game Mode menus on NVIDIA, described below.

tools/build-gamescope.sh builds from a pinned source revision in a disposable stable build root. The output contains a staged installation tree, source and patch metadata, checksums and license notices. It does not replace Gamescope on the running system. The image builder accepts it through --gamescope-dir.

The binary lives under /usr/lib/steamos-nvidia/gamescope/bin, leaving the package-managed binary intact. A user service drop-in selects it through PATH only when the artifact itself accepts the flag list the session recipe passes, which is checked by running it inside the target root with --help last, the display variables removed and a bounded timeout. The expected session script is still required. Neither the installed Gamescope package nor the SteamOS release takes part in that decision. Naming a SteamOS point release here was a mistake that cost a working feature: stable moved from 3.8.16 to 3.8.28 on 22 September 2026, the rule stopped matching, and outgoing Remote Play went back to a black picture on an ordinary system update. Comparing the installed Gamescope package was the next mistake: it would have stood this artifact aside on SteamOS 3.9.2, where the same build was measured running Game Mode correctly beside Valve's gamescope 3.16.30-2. Neither the release nor the package version is a condition now, so a Gamescope bump no longer returns the system to Valve's build on its own. The installer verifies its source revision, hash and target library resolution. A recovery image keeps the stock binary. The A/B repair hook carries the artifact into the updated slot and reevaluates compatibility before activation. An artifact that no longer accepts the session's flags returns to stock Gamescope automatically; a newer Gamescope package in the updated slot does not by itself cause that.

Installer Update preserves an already installed artifact, reevaluates this policy, and since 0.1.9 can also deliver the Gamescope artifact itself. The binary, its provenance file and its license form one group that is installed in full or not at all, written to the paths the existing installer check already reads, so the same verification of binary against provenance runs whether the artifact arrived with an image or with a release. Delivery is not the same as activation: an artifact built from a source commit the installer does not recognize is refused, and the selection rule above is then applied exactly as it is after a fresh installation, so a system where the delivered build does not accept the session's flags keeps Valve's build. Diagnostics include the selected status and session environment. The shipped-file checksum manifest covers the capture binary, provenance and licenses. Generated status and the service selection drop-in are excluded because they change when an OS update activates the backport or returns to stock. Physical capture tests are still required before treating the correction as verified on a particular GPU.

Game Mode menu corruption on NVIDIA

The Gamescope build described under Capture-color correction fixes this fault. On a build without that change, Gamescope can hand the game and Steam's interface to separate hardware planes and let the display driver blend them there instead of composing the frame itself. On NVIDIA that blend is wrong, and the result is displaced fragments of the interface, black rectangles and colored bands over much of the screen. It showed in the Game Mode menus, and at a lower output resolution it could also appear in the Steam interface with no game running. Composing the same frame is correct, which is why turning the performance overlay on, turning HDR on, or moving the image scale away from one to one used to clear it. Each of those forced composition for its own reason, and none of them is needed any more.

patches/gamescope/0003-nvidia-alpha-composition.patch is this project's own change, not a backport. The finding and the change were reported upstream, on the Gamescope issue that describes this symptom, gamescope#1964. When more than one layer is present and a layer other than the base layer is not fully opaque or asks for alpha blending, it refuses the plane route before a request is built, so no atomic request is rolled back by a policy choice, and composes the frame instead. The decision is taken once, from the display device that was actually opened and never from the render GPU, and only when the kernel reports that device as nvidia-drm, so a machine whose screen hangs off an AMD or Intel output is untouched. It is on by default, and the chosen policy is written to the Gamescope log at startup.

Setting GAMESCOPE_NVIDIA_COMPOSITE_ALPHA=0 in the Gamescope session environment switches off this one reason to compose a frame, for comparison during a test; 1 asks for it explicitly. Every other reason Gamescope already had to compose still applies either way, and the variable cannot turn the policy on for a display that is not NVIDIA. There is no reason to set it in normal use.

The fix travels in the Gamescope artifact, so it reaches a system either with a new image or with a signed release that carries that artifact, provided the installed tools are already 0.1.9 or later. An installation on older tools has to reach 0.1.9 first, and receives the artifact with the release after that. See Updating from an older installer for the order. It was published in 0.2.0.

Changing the output to 4K on NVIDIA

On an NVIDIA card, a running Game Mode session could corrupt the whole picture when the output was raised to 3840x2160, or when HDR was switched on at that resolution: the right layout repeated lower down at an offset and mixed with static. The image Gamescope composed was correct; reading it out to the display was not. Valve's packaged Gamescope 3.16.23.6-1 showed the same resolution-change fault on the same machine.

patches/gamescope/0005-nvidia-gbm-scanout-experiment.patch lets Gamescope allocate the images it hands to an NVIDIA display through GBM and compose every frame into them. With it, raising the resolution to 3840x2160 and switching HDR at that resolution, which had broken the picture, stayed correct, and so did VRR changes, on the machine measured, an RTX 5060 driving a 4K monitor over DisplayPort at 60 Hz. The same monitor over HDMI was also checked, with HDR and resolution changes correct; VRR is not offered on that connection. A television on HDMI has not been measured with it.

The path is switched on by GAMESCOPE_NVIDIA_GBM_SCANOUT=1, which the installer writes into the same session override that selects this project's Gamescope, so it exists only where that build runs and is removed with the selection. Setting it to 0 in the Gamescope session environment switches the path off for comparison during a test; there is no reason to set it in normal use. The GBM dependency is optional in Gamescope's own build, so a build without the GBM headers would compile and pass every test while lacking the fix; this project's build refuses to produce such a binary.

It travels in the Gamescope artifact like the fixes above, and was published in 0.2.4.

Code map and contributing

Component Main source
Image build and generated OS repair hook steamos-nvidia-installer.sh
Shared install, integrity and library checks lib/pc-support.sh
Disk selection and recovery script adaptation scripts/install-target.py, scripts/patch-repair.py
Driver selection, staging and rollback scripts/driver-manager.py, scripts/driver-change.py, scripts/driver-stage.sh
Signed integration updates scripts/installer-update.py, scripts/installer-update-ui.py
Release package creation tools/build-installer-release.py
Read-only support report scripts/steamos-nvidia-diagnostics
Patches applied to upstream components patches/gamescope/, patches/mangohud/, patches/nvidia-vaapi-driver/
Optional component builders tools/build-mangoapp.sh, tools/build-gamescope.sh, tools/build-remote-play.sh, tools/build-nvenc.sh
Complete image build wrapper tools/build-complete.sh
Helper and transaction tests tests/

For a contribution, describe the failing behavior and how to reproduce it, link the relevant issue, identify the affected entry point and explain the validation. Keep changes to display policy, driver selection and disk operations separately reviewable. Report VM and physical hardware results separately. Do not remove upstream authorship or licensing when reorganizing code.

Remote Play receiving and color formats

Receiving a stream from another PC uses Steam's decoder and renderer. This is separate from Gamescope's PipeWire capture path used when sending a stream. Fixing capture does not automatically fix playback in the other direction.

The NVIDIA VAAPI decoder exports decoded frames for Vulkan to import. The exported memory layout and plane descriptors must agree with the format selected by Steam. NV12 stores interleaved U/V chroma; NV21 reverses that order. The correction changes the exported NV12 chroma-plane DRM descriptor from RG88 to GR88 so its byte order matches the data. A wrong descriptor can corrupt video colors while the receiver's menu remains correct. The receiver artifact included by the complete build in the build guide corrects the NV12 chroma descriptor and exports a single memory object, which Steam's Vulkan importer requires. Build it with tools/build-remote-play.sh and include it with --remote-play-dir.

A second correction in the same artifact ends a hang at the end of a session. A decoded surface can stay marked as still resolving after the thread that would clear the mark has gone, and the wait for it had no time limit, so the next export or sync call on that surface never returned. The receiver's main thread then sat waiting at no CPU, ignored an ordinary stop request, and Gamescope held its last black frame on screen for as long as the window existed, which reads as the machine hanging after leaving a game. The patch releases every surface an ending resolve thread abandons, so a normal teardown wakes all waiters at once, and bounds the remaining waits against one shared deadline, logging which surface it gave up on. The trade is one possibly stale frame in place of a process that cannot be closed. It was reported upstream.

The artifact lives under /usr/lib/steamos-nvidia/remote-play. Steam's user service loads a small environment helper with matching 32-bit and 64-bit libraries. The 64-bit helper checks the executable name and applies the decoder settings only to streaming_client; it does not alter Steam's installed files. Existing preload entries are preserved. Other applications keep their decoder and display settings. The receiver uses SDR because its X11 display detection can select HDR10 even when Game Mode HDR output is off. This does not disable HDR for local games.

The repair hook copies and verifies the artifact in the updated A/B slot and recreates the service configuration. This artifact is not in the list a release may carry, so Installer Update does not distribute this optional binary artifact.

Steam runs under two different user services, and the policy is applied to both: steam-launcher.service in Game Mode, and an instance of [email protected] when Steam starts from the desktop. The desktop instance name is chosen at launch, so a user override has to be placed on the template. To disable the receiver policy for diagnosis, set STEAMOS_NVIDIA_REMOTE_PLAY=0 in a user override for both launchers, then restart Steam in the mode you are testing:

for unit in steam-launcher.service [email protected]; do
  mkdir -p ~/.config/systemd/user/$unit.d
  printf '[Service]\nEnvironment=STEAMOS_NVIDIA_REMOTE_PLAY=0\n' > ~/.config/systemd/user/$unit.d/90-disable-remote-play.conf
done
systemctl --user daemon-reload

Remove both files and reload again to restore the policy. Before 0.1.7 only the Game Mode launcher carried the policy, so an override on that unit alone was enough; it no longer is. Hardware testing is required after installing a new image.

NVENC encoding from a 32-bit Steam host

RTX 50 hardware can encode video, but NVIDIA's 32-bit CUDA interface cannot use these GPUs. The NVENC bridge included by the complete build in the build guide provides a VAAPI encoder to the 32-bit Steam host. Frames pass through shared memory to a 64-bit helper, which runs CUDA and NVENC, then returns H.264 or HEVC bitstreams. Steam therefore reports VAAPI HEVC or VAAPI H264 even though encoding is performed by NVIDIA hardware.

The artifact is stored in /usr/lib/steamos-nvidia/nvenc. Its 32-bit driver is installed at /usr/lib32/dri/nvidia_drv_video.so. The user service steamos-nvidia-nvenc.service starts with either Steam launcher, steam-launcher.service in Game Mode or an instance of [email protected] from the desktop, and uses a socket inside the user's runtime directory. The desktop drop-in also removes LIBVA_DRIVER_NAME from Steam's environment, because the desktop session inherits Valve's AMD default from /etc/profile.d/libva.sh and Game Mode never sets it at all. Without that, Steam on the desktop looked for an AMD driver and encoded in software. No network listener or root daemon is added. Steam can fall back to software encoding if the optional service is unavailable.

The 64-bit receiver keeps its separate decoder and NV12 color correction. The encoding bridge does not change receiver HDR policy or add HDR streaming support. The repair hook copies the private artifact into the new A/B slot, verifies its hashes and loader dependencies, then recreates the driver, service and Steam drop-in. The addon manifest covers those files. This artifact is not in the list a release may carry, so Installer Update does not distribute this optional binary artifact to systems built without it.

Device testing on RTX 5060 confirmed HEVC SDR encoding, reconnect and automatic helper startup after reboot. A fresh installation followed by the 3.8.14 to 3.8.16 OS update preserved the packaged bridge and service. Encoding also worked after deep suspend/resume. Other GPUs and later OS updates require their own validation.

Windows build helper

tools/build-on-windows.ps1 connects to an existing Linux build VM over SSH and runs the same image builder. It quotes paths and options for the remote shell, uses the normal interactive SSH/sudo authentication and propagates failures. It does not create a VM, distribute a recovery image or replace package checks.

Before the build, tools/check-build-host.sh checks tools, local filesystem type and space. Its temporary loop-backed Btrfs image and OverlayFS write test run in a private mount namespace. Cleanup removes only its own temporary probe after unmounting and detaching; failed cleanup retains the directory for inspection. See Build on Windows for setup and validation limits.

Complete image build

tools/build-complete.sh opens the original recovery image with a read-only loop device. It mounts rootfs-A read-only with log replay disabled, then uses a separate writable OverlayFS layer for build dependencies and compilation. The original image is not the build root's writable filesystem. The builder records the recovery release it found and warns when it is outside the validated list in config/build-baselines.json. It refuses a build root whose package repositories belong to another SteamOS line, because artifacts compiled there would target libraries the produced image does not ship.

The four existing component builders run in order. Once all succeed, the wrapper stops background processes rooted in that build environment, unmounts it and discards its compilation layer, then invokes the normal installer with every artifact and updater source. A failure prevents later stages from running. The output checksum is only generated after the installer returns successfully. See Complete build for defaults, space requirements and the current validation scope.

Embedded Steam notifications

Installer 0.1.2 includes notification-renderer.py. For the reviewed Steam client assets (Stable build 1788652215 and Beta build 1789781627), the helper selects the client's existing embedded toast renderer instead of the separate notification BrowserView. It does not filter pixels or disable notifications.

Installer 0.2.5 also supports Stable client build 1791918634 from the October 13, 2026 Steam update. That client moved the notification selector from chunk~2dcc5aaf7.js into sp.js. Both older client profiles remain supported; updating the installer does not require updating or downgrading Steam. The helper chooses the asset used by the client and applies only its reviewed replacement. When sp.js contains the Gamepad UI, an unrecognized version is skipped even if a recognized old chunk remains on disk. This prevents patching an obsolete file after a client update. Each supported asset retains its own verified backup.

The new profile passed apply, repeated apply and exact restore on a copy of the full client asset, and a controller notification was confirmed without the black border on SteamOS 3.8.28. Regression tests cover both older profiles, an already patched older client, transition between old and new layouts, and refusal of an unknown new client. These tests do not establish support for future Steam builds.

A user service starts with either Steam launcher, in Game Mode and from the desktop alike, and waits up to 120 seconds for the main Steam browser process. The workaround is checked again whenever either launcher service starts. An internal Steam client restart within the same launcher session may not rerun the service. It accepts only a complete SHA-256 hash from its list of reviewed JavaScript assets, verifies a single known replacement, saves the original under ~/.local/state/steamos-nvidia/notifications/, then replaces the asset atomically. The replacement preserves the original byte length to avoid triggering Steam's routine file-size repair. Full integrity verification can still restore the asset; no Steam verification mechanism is disabled. No Steam JavaScript asset is distributed with the installer. An unrecognized asset is left unchanged, and failure does not block Steam startup. Restoring a backup also requires the current file to match this exact patch; a newer Steam asset is never replaced with an older backup.

Steam verifies its client assets during startup and can restore the original UI before opening its browser. Applying the patch before Steam starts is therefore insufficient. The service waits for that browser, applies the verified patch and reloads only that browser once when the file was changed. Steam respawns it. The service then exits; it does not continuously watch files or restart the UI during a session. A brief UI reload can occur at startup.

An unknown client asset is skipped without a reload. A new client version needs a reviewed hash and visual testing. A client update later in the same session may restore the original; the next Steam start checks again. Before 0.2.4 the service stayed marked as done after its first run and relied on systemd to reset it when Steam stopped. A plain restart of Steam did reset it, but a full switch into Game Mode did not, so a client update followed by that switch could leave the original asset in place while the service still reported success. From 0.2.4 the service does not stay active once it finishes, so every start of either Steam service runs the check again. Since 0.1.8 the startup hook is attached to both Steam services, steam-launcher.service in Game Mode and instances of [email protected] from the desktop, so starting Steam from the desktop runs the same check. The client asset is shared, so Game Mode and desktop Big Picture show the same result. From 0.1.2 to 0.1.7 the hook reached the Game Mode launcher only, so a client verification during a desktop session left the original asset in place until Steam was next started in Game Mode. The current workaround has been tested after restart, from Beta to Preview and back to Stable. Those tests used the supported client builds; they do not establish compatibility with future client releases. Fresh installation and its first system update have also been verified.

The project helper and startup hook are restored in the inactive system slot by the existing repatch process. The client asset and backup live in the user's home directory, outside the A/B root filesystems. Use status, disable or enable with the helper to inspect or change this workaround, then restart Steam when no game is running.

Signed update packages embed the helper in pc-support.sh, which is already part of the archive format accepted by updaters 0.1.0 and 0.1.1. During preparation, that signed script installs the helper into the inactive slot and verifies its checksum before enabling the startup hook. The package retains the existing file allowlist and signing key. Image builds install the same helper directly.

The Stable and Beta client assets have separate exact replacements and backups. Controller, download-complete, message and Remote Play notifications were verified without black borders, including notifications over a running game and during streaming. Game icons, avatars and message previews were preserved. This is a client compatibility list, not a guarantee for future SteamOS or Steam client releases.