Bazzite
Set up a Slipstream host on Bazzite, it follows the box between Steam Gaming Mode (gamescope) and the KDE Plasma desktop automatically.
Bazzite already ships everything a Slipstream host needs, the NVIDIA driver, NVENC, PipeWire, gamescope, and the KDE Plasma desktop. So a Bazzite host is the most "appliance-like" setup, and it streams both of Bazzite's faces:
- Steam Gaming Mode (gamescope), the couch/handheld game UI.
- The KDE Plasma desktop, the full desktop you get from "Switch to Desktop".
The host auto-detects which one is live and follows the box across the switch, including
mid-stream. You flip between Gaming Mode and Desktop with Bazzite's normal Steam UI /
"Switch to Desktop"; the host just re-targets whatever's running and keeps streaming. Nothing in
host.env forces a mode.
Ideal when you want one box for game streaming and for remote desktop from the office. For a pure desktop machine, install on Ubuntu or Fedora and configure the KDE or GNOME desktop directly, simpler.
New here? Read Security & Safe Use first, a streaming host is remote control of the machine, so keep it on a trusted LAN or VPN and require pairing.
Install
The host installs as a systemd system extension (sysext), no rpm-ostree layering. The
Bazzite docs treat layering as a last resort (layered packages slow every OS update and can block
upgrades until removed); a sysext never enters an rpm-ostree transaction: it overlays /usr
read-only from /var/lib/extensions/, survives OS updates, installs and updates without a
reboot, and is removable in one command. This is the same mechanism the Fedora Atomic
maintainers ship via the fedora-sysexts project.
# From a clone of this repo (or download the script from GitHub):
# https://raw.githubusercontent.com/vindeckyy/slipstream/main/packaging/bazzite/slipstream-sysext.sh
curl -fsSLO https://raw.githubusercontent.com/vindeckyy/slipstream/main/packaging/bazzite/slipstream-sysext.sh
sudo bash slipstream-sysext.sh install --from-file /path/to/slipstream.rawBuild the image locally with packaging/bazzite/build-sysext.sh. Operators with a private feed can
set SLIPSTREAM_SYSEXT_REGISTRY and omit --from-file. See
packaging/bazzite/README.md.
When a feed is used, the checksum manifest is OpenPGP-signed (public key baked into the script), and
slipstream-sysext checks that signature before it trusts a single checksum, so it needs gpg on
the box, and it refuses a feed it can't verify.
The plugin runner rides along in the image but isn't started: run
systemctl --user enable --now slipstream-scripting when you want plugins.
For later local updates:
sudo slipstream-sysext update --from-file /path/to/newer-slipstream.raw
sudo slipstream-sysext status # channel, installed vs latest version
sudo slipstream-sysext remove # unmerge and delete the image (~/.config/slipstream is kept)After an update, restart the host so it runs the new binary (the updater prints this reminder too). The image carries the console as well, so restart that first if you enabled it:
systemctl --user restart slipstream-web # only if you run the console
systemctl --user restart slipstream-hostTo switch channel later, re-run the install: sudo slipstream-sysext install --channel canary
(or --channel stable). update takes no channel flag, it follows whatever the last install wrote
to /etc/slipstream-sysext.conf. To be able to go back to a build that worked, keep a copy of the
image before you update, and re-install that file afterwards:
sudo cp /var/lib/extensions/slipstream.raw ~/slipstream-known-good.raw # before updating
sudo slipstream-sysext install --from-file ~/slipstream-known-good.raw # to go back to itThe web console can also run the update for you, see Updating the Host, which
needs the one-time sudo usermod -aG slipstream-update $USER.
remove deletes the image and the /etc files it seeded (the tray autostart entry, and the
gamescope session drop-in unless you've edited it), but three things it created outside /usr stay
behind. To clear those too, services first, because once the image unmerges their binaries are
gone and the units just keep failing:
systemctl --user disable --now slipstream-host slipstream-web
sudo slipstream-sysext remove
sudo rm -f /etc/modules-load.d/slipstream.conf /etc/udev/rules.d/60-slipstream.rules
sudo groupdel slipstream-update # the (empty) group for web-console updatesUninstalling has the same walkthrough for the other install methods, and for the clients.
Three things to know:
- After a Bazzite major rebase (Fedora 43 -> 44) the old image refuses to load rather than
run against mismatched system libraries, run
sudo slipstream-sysext updateonce and it fetches the image built for the new base. - Already layering Slipstream? Install the sysext (it shadows the layered copy immediately),
then drop the layer so it stops slowing your updates:
sudo rpm-ostree uninstall slipstream slipstream-web && systemctl reboot. - If it refuses the feed.
refusing to install from an unsigned feedmeans that Fedora major's feed predates signing; it gets sealed on the next publish. To install from it anyway, accepting that the images are unauthenticated, runsudo env SLIPSTREAM_SYSEXT_ALLOW_UNSIGNED=1 bash slipstream-sysext.sh install. The other message,the feed's SHA256SUMS is NOT signed by the expected key, is not the same thing, don't install; re-download the script and try again.
For a fully baked appliance image there's also a bootc Containerfile that installs RPMs you
point it at during image-build time, see packaging/bootc/ in the repo. Plain rpm-ostree
layering with a local RPM (built via packaging/rpm
or taken from GitHub Releases) keeps working too:
sudo rpm-ostree install ./slipstream-*.rpm ./slipstream-web-*.rpm and reboot. The sysext is still
the supported default. Building from source also works (Bazzite is Fedora Atomic underneath, same
steps as Fedora).
Allow controller input
Gamepad and DualSense input needs your user in the input group. On Bazzite, don't use
usermod, the base is immutable and the group is managed by a recipe. Use:
ujust add-user-to-input-groupThen log out and back in. (A controller that's "detected but does nothing" is almost always this permission, not a client problem.)
Configure
The RPM ships a Bazzite-tuned config you can copy as your starting point:
mkdir -p ~/.config/slipstream
cp /usr/share/slipstream/host.env.bazzite ~/.config/slipstream/host.envThe template is deliberately minimal, it does not force a compositor, because the host auto-detects Gaming Mode (gamescope) vs Desktop (KWin) on every connect and follows the switch mid-stream. No session anchors are needed either (a user service inherits the right runtime dir). The only settings that matter (GPU zero-copy is on by default):
SLIPSTREAM_VIDEO_SOURCE=virtual
# GPU zero-copy (dmabuf -> CUDA -> NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU.
SLIPSTREAM_GAMESCOPE_ATTACH=1 # Gaming Mode = attach to the box's own session, SDR, and no cursor (see below)Gaming Mode: attach vs managed
For Gaming Mode there are two models (pick one; the shipped default is attach):
- Attach (
SLIPSTREAM_GAMESCOPE_ATTACH=1, the template's default), the box owns its gamescope session on its own display, and the host attaches to whatever's live without ever tearing it down (on a headless box, a box-owned autologin session is restarted at the client's resolution on a mismatch; with a display connected it streams at the box's own mode). Switching Desktop ↔ Game is rock-solid. - Managed (
SLIPSTREAM_GAMESCOPE_MANAGED=1, and remove the attach line), the host takes the box's gamescope over and relaunches it headless at the client's exact resolution and refresh, Game Mode on the virtual screen, restoring the box on idle.
Full treatment: Steam / gamescope -> How the host gets a gamescope.
Mid-stream Gaming ↔ Desktop following (SLIPSTREAM_SESSION_WATCH) is on by default on
Bazzite/SteamOS. See Configuration for the full list of knobs.
Streaming the KDE Plasma desktop
The virtual output (video) for the Desktop session needs no config, the host package ships an
io.slipstream.Host.desktop file whose X-KDE-Wayland-Interfaces grants the host KWin's
restricted screencast protocol on a normal interactive Plasma session (background:
KDE Plasma). After a fresh host install, log out and back into the Desktop session
once so KWin re-reads that grant.
The one thing a normal KDE login lacks is the RemoteDesktop grant for headless input injection. Seed it once (as the streaming user, no root) so the host auto-approves instead of popping an un-answerable dialog:
bash /usr/share/slipstream/bazzite/kde-desktop-setup.shGaming Mode needs none of this, it auto-attaches.
Run as an always-on host
Bazzite hosts are typically headless. Enable the host service and linger so it starts at boot, see Running as a Service. One host service covers both Gaming Mode and the Desktop; it follows whichever the box is in.
systemctl --user enable --now slipstream-host
systemctl --user enable --now slipstream-web # web console: pairing + status
sudo loginctl enable-linger "$USER" # start at boot with nobody logged inWithout that last line the --user units don't start until someone logs in, which on a headless box
never happens.
Then open The Web Console for the login password and to arm pairing.
Good to know
These apply to the Gaming Mode (gamescope) path; the KDE Desktop path is unaffected:
- gamescope 3.16.22 or newer is required; 3.16.23 or newer for the Steam overlay. Below 3.16.22 headless capture can deadlock; between the two, capture works but the Steam overlay (Shift+Tab / the Quick Access Menu) is never painted into the captured node. Bazzite's current gamescope is past both; this only bites if you've pinned an old one.
- The template pins attach, and that costs you both the cursor and HDR. The sysext ships the
slipstream-gamescopebuild, but it only reaches a session the host starts itself, and thehost.envtemplate above setsSLIPSTREAM_GAMESCOPE_ATTACH=1, where the live session is Bazzite's own stock gamescope. Comment that line out and let the managed default take over: you get the compositor-drawn pointer and real HDR. To stay on attach instead, setSLIPSTREAM_GAMESCOPE_HDR=0andSLIPSTREAM_GAMESCOPE_BIN=/usr/bin/gamescope. Why each half breaks: gamescope -> Known limits for the cursor, HDR -> Linux + gamescope for the failed connect.
Those are the two that bite on Bazzite. The full set, touch, mouse modes, the clipboard, is on gamescope -> Known limits.
Then connect a client: Android, Steam Deck, or Moonlight for a smart TV. Trouble? See Troubleshooting.
