mirror of
https://github.com/ps5-linux/ps5-linux-image.git
synced 2026-08-13 23:19:04 -07:00
Add SteamOS distro
Adds `--distro steamos` as a first-class build target. Pattern is the
same as bazzite-deck/batocera: distros/steamos/build-rootfs.sh fetches
the upstream rootfs (Valve's Steam Deck recovery .img.bz2 from
steamdeck-images.steamos.cloud), extracts the rootfs-A + var-A btrfs
slots into $CHROOT, swaps in our linux-ps5 kernel pkg.tar.zst, and
rebuilds the initramfs with the modules amdgpu needs for the PS5
Oberon GPU.
PS5-specific fixes applied to the SteamOS rootfs:
- Replace /usr/bin/steamos-session-select with a shim that writes
/etc/sddm.conf.d/zzz-session-override.conf directly + restarts sddm.
The vendored helper goes through steamosctl / steamos-manager whose
Deck-hardware code paths silently no-op on PS5, leaving the UI stuck
at "Switching to Desktop..." with no actual session swap.
- Install a small Python dbus monitor (steam-session-switch-listener)
that catches org.freedesktop.DisplayManager.Seat.SwitchToUser from
Big Picture's "Switch to Desktop" button and writes the same
override file. No -steamos3 / -steampal needed (those trigger Steam's
A/B update reboot loop on a non-Deck box).
- Drop a Return-to-Gaming-Mode.desktop into /home/deck/Desktop pointing
at steamos-session-select gamescope.
- chown /home/deck to deck:deck (the recovery image leaves it root:root,
which breaks ~/.config writability on first Plasma launch).
- Enable sshd + set deck:deck so the box is reachable for further
iteration without dragging a USB keyboard around.
- Patch /etc/fstab: comment out the by-partsets atomic mount entries
(those paths don't exist on a flat-rootfs flash), append a LABEL-based
/efi mount so the FAT boot partition comes up cleanly. Required for
ps5-iw620-firmware.service to find the WLAN blob.
- Append amdgpu.dpm=0 + amdgpu.gpu_recovery=0 to the kernel cmdline.
modprobe.d alone isn't enough — amdgpu loads before the rootfs conf
is read, so the cmdline is the only reliable path. Without these
Switch-to-Desktop produces a black screen on the PS5 HDMI output.
- Polkit-deny the deck user reboot/poweroff to break Steam's update
reboot loop on first boot.
- Patch steam-jupiter to skip its OOBE rm -rf path. The OOBE assumes
Deck factory layout and wipes /home/deck/.local if it runs.
Verified end-to-end on real PS5 (HW-rev EA-1102): autologin lands in
gamescope, Switch-to-Desktop and Return-to-Gaming both work, sshd is
reachable, ps5-iw620 brings up internal WiFi after firmware staging.
Files:
- distros/steamos/build-rootfs.sh (646 lines)
- distros/steamos/README.md
- distros/steamos/return-to-gaming-mode.desktop
- distros/steamos/steam-session-switch-listener.{py,service}
- build_image.sh: steamos → FORMAT=arch (uses linux-ps5 pkg.tar.zst)
- .github/workflows/build-image.yml: steamos in matrix options + distros list
This commit is contained in:
@@ -8,7 +8,7 @@ on:
|
||||
description: 'Distribution'
|
||||
default: 'ubuntu2604'
|
||||
type: choice
|
||||
options: [all, ubuntu2604, arch, cachyos, fedora, proxmox, debian, bazzite, bazzite-deck]
|
||||
options: [all, ubuntu2604, arch, cachyos, fedora, proxmox, debian, bazzite, bazzite-deck, steamos]
|
||||
workflow_call:
|
||||
inputs:
|
||||
distro:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
run: |
|
||||
INPUT="${{ inputs.distro || 'ubuntu2604' }}"
|
||||
if [ "$INPUT" = "all" ]; then
|
||||
echo 'distros=["ubuntu2604","arch","cachyos","fedora","proxmox","debian","bazzite","bazzite-deck"]' >> "$GITHUB_OUTPUT"
|
||||
echo 'distros=["ubuntu2604","arch","cachyos","fedora","proxmox","debian","bazzite","bazzite-deck","steamos"]' >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "distros=[\"$INPUT\"]" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ esac
|
||||
|
||||
if [ -z "$FORMAT" ]; then
|
||||
case "$DISTRO" in
|
||||
arch|cachyos) FORMAT="arch" ;;
|
||||
arch|cachyos|steamos) FORMAT="arch" ;;
|
||||
fedora|bazzite*) FORMAT="rpm" ;;
|
||||
all) FORMAT="all" ;;
|
||||
*) FORMAT="deb" ;;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# steamos
|
||||
|
||||
SteamOS 3 image variant for PS5, built by extracting the rootfs from
|
||||
Valve's official Steam Deck recovery image and swapping in our
|
||||
`linux-ps5` kernel.
|
||||
|
||||
## Source
|
||||
|
||||
- Upstream: <https://steamdeck-images.steamos.cloud/recovery/steamdeck-repair-latest.img.bz2>
|
||||
(Steam Deck recovery image; SteamOS Holo Arch-based.)
|
||||
- ~3.4 GB compressed bz2, ~10 GB decompressed.
|
||||
- Valve CDN — no per-IP throttle observed; first-time cache pull on a
|
||||
cold runner is ~3-5 min, subsequent runs are instant via
|
||||
`/data/cache/ps5/downloads/`.
|
||||
|
||||
## What the build does
|
||||
|
||||
1. Resolve `steamdeck-repair-latest.img.bz2` → its dated filename so
|
||||
the cache key is stable.
|
||||
2. Decompress → losetup -P.
|
||||
3. Mount the first **erofs** partition (rootfs-A; A and B are
|
||||
identical) read-only, `cp -a` to `$CHROOT`.
|
||||
4. Mount the first **ext4 < 2 GB** partition that contains `/lib`
|
||||
(var-A), `cp -a` to `$CHROOT/var` — SteamOS splits `/var` off the
|
||||
rootfs and first-boot fails without it.
|
||||
5. Extract our `linux-ps5-*.pkg.tar.zst` and merge `usr/`, `boot/`,
|
||||
`etc/` into `$CHROOT`.
|
||||
6. Delete SteamOS's `linux-neptune` kernel + modules so grub boots
|
||||
ours.
|
||||
7. `depmod -a $KVER`.
|
||||
|
||||
The standard image-builder flow then packs `$CHROOT` into an ext4
|
||||
image with our EFI partition.
|
||||
|
||||
## Caveats
|
||||
|
||||
- The Steam UI session expects the Deck's `jupiter` hardware quirks
|
||||
(gamepad, ALS, fan curve daemons) — most of those won't apply on
|
||||
PS5. Falling back to plasma desktop on the first session is normal.
|
||||
- SteamOS is read-only by design (steamos-readonly enable). The image
|
||||
we build here is regular ext4; if you want the read-only A/B atomic
|
||||
experience, that needs a separate variant.
|
||||
Executable
+646
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Return to Gaming Mode
|
||||
Comment=Switch the autologin session back to gamescope / Steam Deck UI
|
||||
Exec=bash -c 'steamos-session-select gamescope'
|
||||
Icon=steamdeck-gaming-return
|
||||
Terminal=false
|
||||
Categories=System;
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Listens on the system bus for Steam Big Picture's Switch-to-Desktop
|
||||
dbus call (org.freedesktop.DisplayManager.Seat.SwitchToUser) and performs
|
||||
the session swap directly — runs as root so no pkexec / polkit needed.
|
||||
|
||||
Flow:
|
||||
1. Steam UI's Power-Menu Switch-to-Desktop fires
|
||||
dbus-send --system --dest=org.freedesktop.DisplayManager \\
|
||||
/org/freedesktop/DisplayManager/Seat0 \\
|
||||
org.freedesktop.DisplayManager.Seat.SwitchToUser \\
|
||||
string:doorstop string:plasma
|
||||
2. This listener catches the call (BecomeMonitor mode on system bus)
|
||||
3. Writes /etc/sddm.conf.d/zzt-steamos-temp-login.conf for plasma
|
||||
4. Runs 'steam -shutdown' as the deck user — gamescope-session ends
|
||||
cleanly because Steam (gamescope's child) exits, DRM master is
|
||||
released the gentle way, sddm bounces and autologs into plasma.
|
||||
5. The zzt- conf is one-shot — sddm's ExecStartPre wipes it on the
|
||||
next sddm restart, so closing plasma drops back to gamescope.
|
||||
"""
|
||||
import dbus, dbus.mainloop.glib, subprocess, sys, traceback
|
||||
from gi.repository import GLib
|
||||
|
||||
LOG = "[steam-session-switch-listener]"
|
||||
# zzz- sorts AFTER zz-steamos-autologin.conf (so it overrides), AND it
|
||||
# doesn't match the ExecStartPre rm pattern that only deletes 'zzt-...'
|
||||
# — so the conf survives `systemctl restart sddm` cycles.
|
||||
ZZT = "/etc/sddm.conf.d/zzz-session-override.conf"
|
||||
|
||||
SESSION_MAP = {
|
||||
"plasma": "plasma.desktop",
|
||||
"plasma-wayland": "plasma.desktop",
|
||||
"plasma-wayland-persistent": "plasma.desktop",
|
||||
"desktop": "plasma.desktop",
|
||||
"plasma-x11": "plasmax11.desktop",
|
||||
"plasma-x11-persistent": "plasmax11.desktop",
|
||||
"gamescope": "gamescope-wayland.desktop",
|
||||
}
|
||||
|
||||
def handle_switch(session):
|
||||
target = SESSION_MAP.get(session, "gamescope-wayland.desktop")
|
||||
print(f"{LOG} writing zzt- conf -> {target}", flush=True)
|
||||
with open(ZZT, "w") as f:
|
||||
f.write(f"[Autologin]\nUser=deck\nSession={target}\nRelogin=true\n")
|
||||
|
||||
# Restart sddm directly. Since we used zzz-session-override.conf,
|
||||
# the ExecStartPre that only kills zzt-... doesn't wipe it. sddm
|
||||
# restart reads the conf (zzz- sorts after zz-steamos-autologin.conf,
|
||||
# so it wins) and autologs into the requested session.
|
||||
print(f"{LOG} systemctl restart sddm", flush=True)
|
||||
subprocess.Popen(["systemctl", "restart", "sddm"], stdin=subprocess.DEVNULL)
|
||||
|
||||
def on_message(_bus, message):
|
||||
try:
|
||||
if message.get_interface() != "org.freedesktop.DisplayManager.Seat":
|
||||
return
|
||||
if message.get_member() != "SwitchToUser":
|
||||
return
|
||||
args = list(message.get_args_list())
|
||||
if len(args) < 2:
|
||||
return
|
||||
user, session = str(args[0]), str(args[1])
|
||||
print(f"{LOG} caught SwitchToUser({user!r}, {session!r})", flush=True)
|
||||
handle_switch(session)
|
||||
except Exception:
|
||||
# NEVER let an exception kill the monitor — log and continue
|
||||
traceback.print_exc()
|
||||
|
||||
def main():
|
||||
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
|
||||
bus = dbus.SystemBus()
|
||||
bus_obj = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||
monitoring = dbus.Interface(bus_obj, "org.freedesktop.DBus.Monitoring")
|
||||
monitoring.BecomeMonitor(
|
||||
["type='method_call',"
|
||||
"interface='org.freedesktop.DisplayManager.Seat',"
|
||||
"member='SwitchToUser'"],
|
||||
dbus.UInt32(0),
|
||||
)
|
||||
bus.add_message_filter(on_message)
|
||||
print(f"{LOG} listening on system bus for SwitchToUser", flush=True)
|
||||
GLib.MainLoop().run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Translate Steam Big Picture Switch-to-Desktop dbus call into steamos-session-select
|
||||
Documentation=https://gist.github.com/Rishikant181/e26fb23d4c57db74bddaa0a57b26cd26
|
||||
After=dbus.service sddm.service
|
||||
PartOf=sddm.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/sbin/steam-session-switch-listener
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user