From 292c9c02fdc827fecfc989a5f3cfa00665ab2a2e Mon Sep 17 00:00:00 2001 From: Chihab Djaidja Date: Fri, 8 May 2026 20:40:44 +0200 Subject: [PATCH] feat: add CachyOS image (Gamescope + Steam) - Add distros/cachyos image.yaml and helper files under files/ - Wire kernel packaging and --distro cachyos in build_image.sh - Stage CachyOS artifacts in image-builder entrypoints; include cachyos in multi image - Add boot/kexec-cachyos.sh and document in README Co-authored-by: Cursor --- README.md | 22 +- boot/kexec-cachyos.sh | 6 + build_image.sh | 10 +- distros/cachyos/files/first-boot-setup | 73 ++++ distros/cachyos/files/first-boot.service | 23 ++ distros/cachyos/files/gamescope-session-ps5 | 28 ++ distros/cachyos/files/grow-rootfs | 14 + distros/cachyos/files/grow-rootfs.service | 13 + .../cachyos/files/plasma-workspace-env-ps5.sh | 9 + distros/cachyos/files/ps5-display.lua | 25 ++ distros/cachyos/files/ps5-tty-session.sh | 73 ++++ .../files/return-to-gaming-mode.desktop | 10 + distros/cachyos/files/steamos-session-select | 78 ++++ distros/cachyos/image.yaml | 335 ++++++++++++++++++ docker/image-builder/entrypoint-multi.sh | 20 +- docker/image-builder/entrypoint.sh | 19 +- 16 files changed, 743 insertions(+), 15 deletions(-) create mode 100755 boot/kexec-cachyos.sh create mode 100644 distros/cachyos/files/first-boot-setup create mode 100644 distros/cachyos/files/first-boot.service create mode 100644 distros/cachyos/files/gamescope-session-ps5 create mode 100644 distros/cachyos/files/grow-rootfs create mode 100644 distros/cachyos/files/grow-rootfs.service create mode 100644 distros/cachyos/files/plasma-workspace-env-ps5.sh create mode 100644 distros/cachyos/files/ps5-display.lua create mode 100644 distros/cachyos/files/ps5-tty-session.sh create mode 100644 distros/cachyos/files/return-to-gaming-mode.desktop create mode 100644 distros/cachyos/files/steamos-session-select create mode 100644 distros/cachyos/image.yaml diff --git a/README.md b/README.md index c97868e..6ade5d3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PS5 Linux Image Builder -Builds bootable Linux USB images for PlayStation 5 using Docker containers. Supports Ubuntu 26.04, Ubuntu 24.04, Arch, and Alpine, individually or as a multi-distro image with kexec switching. +Builds bootable Linux USB images for PlayStation 5 using Docker containers. Supports Ubuntu 26.04, Ubuntu 24.04, Arch, CachyOS (Gamescope + Steam), and Alpine, individually or as a multi-distro image with kexec switching. ## Prerequisites @@ -27,7 +27,12 @@ OR OR -# Build a multi-distro image (ubuntu2604 + ubuntu2404 + arch + alpine) +# Build CachyOS (Arch-based, Gamescope + Steam Big Picture) +./build_image.sh --distro cachyos + +OR + +# Build a multi-distro image (ubuntu2604 + ubuntu2404 + arch + alpine + cachyos) ./build_image.sh --distro all ``` @@ -43,7 +48,7 @@ sudo dd if=output/ps5-ubuntu2604.img of=/dev/sdX bs=4M status=progress | Flag | Description | Default | |------|-------------|---------| -| `--distro` | `ubuntu2604`, `ubuntu2404`, `arch`, `alpine`, or `all` | `ubuntu2604` | +| `--distro` | `ubuntu2604`, `ubuntu2404`, `arch`, `cachyos`, `alpine`, or `all` | `ubuntu2604` | | `--kernel` | Path to kernel source directory | auto-clone `v6.19.10` | | `--img-size` | Disk image size in MB | `12000` (`32000` for `all`) | | `--clean` | Remove all cached build artifacts and start fresh | off | @@ -64,7 +69,7 @@ Use `--clean` to wipe everything and rebuild from scratch. The build will also s PS5 Linux Image Builder ======================= Distro: all - (ubuntu2604 ubuntu2404 arch alpine) + (ubuntu2604 ubuntu2404 arch alpine cachyos) Image size: 32000MB Kernel src: /path/to/work/linux @@ -89,11 +94,12 @@ All verbose output goes to `build.log`. The terminal shows a spinner with live p | Ubuntu 24.04 (Noble) | GNOME | `.deb` | systemd | | Ubuntu 26.04 (Resolute) | GNOME | `.deb` | systemd | | Arch | Sway | `.pkg.tar.zst` | systemd | +| CachyOS | Gamescope + Steam Big Picture (Arch + `[cachyos]` repo, no v3 migration in image build) | `.pkg.tar.zst` | systemd | | Alpine (3.21) | GNOME | extracted from `.deb` | OpenRC | ## Multi-distro Image -`--distro all` builds a 32GB image with 5 partitions: +`--distro all` builds a 32GB image with 6 partitions (one EFI boot partition plus five root filesystems): | Partition | Type | Label | Content | |-----------|------|-------|---------| @@ -102,6 +108,7 @@ All verbose output goes to `build.log`. The terminal shows a spinner with live p | p3 | ext4 | ubuntu2404 | Ubuntu 24.04 rootfs | | p4 | ext4 | arch | Arch rootfs | | p5 | ext4 | alpine | Alpine rootfs | +| p6 | ext4 | cachyos | CachyOS rootfs | The boot partition contains kexec scripts to switch between distros at runtime. Ubuntu 26.04 is the default boot target. @@ -111,7 +118,7 @@ The boot partition contains kexec scripts to switch between distros at runtime. build_image.sh # Main build script docker/ kernel-builder/ # Kernel compilation container - kernel-builder-arch/ # Repackages .deb kernel as .pkg.tar.zst + kernel-builder-arch/ # Repackages .deb kernel as .pkg.tar.zst image-builder/ Dockerfile # Image building container (distrobuilder) entrypoint.sh # Single-distro build logic @@ -120,12 +127,13 @@ distros/ ubuntu2404/ # Ubuntu 24.04 (Noble) ubuntu2604/ # Ubuntu 26.04 (Resolute) arch/ # Arch Linux + cachyos/ # CachyOS repos + Gamescope/Steam alpine/ # Alpine 3.21 shared/ # Kernel postinst hooks (single + multi) boot/ cmdline.txt # Kernel cmdline template (__DISTRO__ placeholder) vram.txt # VRAM allocation - kexec-{ubuntu2604,ubuntu2404,arch,alpine}.sh + kexec-{ubuntu2604,ubuntu2404,arch,alpine,cachyos}.sh work/ # Build artifacts (auto-created) linux-bin/ # Compiled kernel packages output/ # Final .img files diff --git a/boot/kexec-cachyos.sh b/boot/kexec-cachyos.sh new file mode 100755 index 0000000..20a0654 --- /dev/null +++ b/boot/kexec-cachyos.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Switch to CachyOS (Gamescope + Steam) via kexec +set -e +BOOT=/boot/efi +kexec -l "$BOOT/bzImage" --initrd="$BOOT/initrd-cachyos.img" --command-line="$(cat $BOOT/cmdline-cachyos.txt)" +kexec -e diff --git a/build_image.sh b/build_image.sh index a5bf7cf..117a9d8 100755 --- a/build_image.sh +++ b/build_image.sh @@ -9,13 +9,13 @@ KERNEL_SRC="" CLEAN=false IMG_SIZE=12000 -MULTI_DISTROS="ubuntu2604 ubuntu2404 arch alpine" +MULTI_DISTROS="ubuntu2604 ubuntu2404 arch alpine cachyos" usage() { echo "Usage: $0 [--distro ] [--kernel ] [--img-size ] [--clean]" echo "" echo "Options:" - echo " --distro Distribution to build: ubuntu2604, ubuntu2404, arch, alpine, all (default: ubuntu2604)" + echo " --distro Distribution to build: ubuntu2604, ubuntu2404, arch, cachyos, alpine, all (default: ubuntu2604)" echo " --kernel Path to kernel source directory (default: auto-clone to work/linux/)" echo " --img-size Disk image size in MB (default: 12000, 32000 for --distro all)" echo " --clean Remove all cached build artifacts and start from scratch" @@ -90,7 +90,7 @@ SKIP_KERNEL=false SKIP_CHROOT=false # Kernel packages already built? -if [ "$DISTRO" = "arch" ]; then +if [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "cachyos" ]; then ls "$KERNEL_OUT"/*.pkg.tar.zst 1>/dev/null 2>&1 && SKIP_KERNEL=true elif [ "$DISTRO" = "all" ]; then ls "$KERNEL_OUT"/*.deb 1>/dev/null 2>&1 && \ @@ -109,7 +109,7 @@ else [ -d "$CHROOT_DIR/bin" ] && SKIP_CHROOT=true fi -if [ "$DISTRO" = "arch" ]; then +if [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "cachyos" ]; then PKG_EXT="pkg.tar.zst" else PKG_EXT="deb" @@ -281,7 +281,7 @@ else -v "$KERNEL_OUT":/out \ ps5-kernel-packager-arch - elif [ "$DISTRO" = "arch" ]; then + elif [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "cachyos" ]; then run_stage "Build arch packager image" \ docker build -t ps5-kernel-packager-arch \ -f "$SCRIPT_DIR/docker/kernel-builder-arch/Dockerfile" "$SCRIPT_DIR" diff --git a/distros/cachyos/files/first-boot-setup b/distros/cachyos/files/first-boot-setup new file mode 100644 index 0000000..c0c6ce0 --- /dev/null +++ b/distros/cachyos/files/first-boot-setup @@ -0,0 +1,73 @@ +#!/bin/bash +# First-boot interactive setup for CachyOS PS5 image. +# Sets the password for the 'steam' user and optionally the hostname, +# then creates a sentinel so this script never runs again. +set -e + +# Keep interactive prompts readable on tty1 by suppressing kernel console noise +# during setup. Restore the previous setting before exiting. +ORIG_PRINTK="$(cat /proc/sys/kernel/printk 2>/dev/null || true)" +if [ -n "$ORIG_PRINTK" ]; then + echo "1 4 1 7" > /proc/sys/kernel/printk || true +fi +cleanup_printk() { + if [ -n "$ORIG_PRINTK" ]; then + echo "$ORIG_PRINTK" > /proc/sys/kernel/printk 2>/dev/null || true + fi +} +trap cleanup_printk EXIT + +clear +echo "==================================================" +echo " Welcome to CachyOS on PS5!" +echo "==================================================" +echo +echo "User: steam (fixed — used for both Gaming Mode and Desktop)" +echo + +# --- Password --- +while true; do + read -rsp "Set a password for 'steam': " PASSWORD; echo + read -rsp "Confirm password: " PASSWORD2; echo + if [[ -z "$PASSWORD" ]]; then + echo "Password cannot be empty." + elif [[ "$PASSWORD" != "$PASSWORD2" ]]; then + echo "Passwords do not match. Try again." + else + break + fi +done + +# --- Hostname --- +read -rp "Hostname [ps5]: " HOSTNAME +HOSTNAME="${HOSTNAME:-ps5}" + +echo +echo "Applying settings..." + +echo "steam:$PASSWORD" | chpasswd + +hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || echo "$HOSTNAME" > /etc/hostname +# Update /etc/hosts if a 127.0.1.1 line exists, otherwise append one. +if grep -q '^127\.0\.1\.1' /etc/hosts; then + sed -i "s/^127\.0\.1\.1.*/127.0.1.1 $HOSTNAME/" /etc/hosts +else + echo "127.0.1.1 $HOSTNAME" >> /etc/hosts +fi + +# Sentinel — prevents this service from running on subsequent boots. +touch /etc/ps5-first-boot-done + +echo +echo "==================================================" +echo " Setup complete!" +echo "==================================================" +echo +echo "The system will log in on tty1 and start Steam Gaming Mode (Gamescope)." +echo "(No display manager — same pattern as the Arch Sway image in this repo.)" +echo +echo "Tips:" +echo " In Steam: Power > Switch to Desktop → KDE Plasma (X11)" +echo " In KDE: Double-click 'Return to Gaming Mode' on the desktop" +echo +sleep 3 diff --git a/distros/cachyos/files/first-boot.service b/distros/cachyos/files/first-boot.service new file mode 100644 index 0000000..eea2ec0 --- /dev/null +++ b/distros/cachyos/files/first-boot.service @@ -0,0 +1,23 @@ +[Unit] +Description=First Boot Interactive Setup +# Run before tty1 autologin so the steam user never races the installer on tty1. +Before=getty@tty1.service +After=systemd-user-sessions.service +# Skip entirely on every boot after the first. +ConditionPathExists=!/etc/ps5-first-boot-done +DefaultDependencies=no + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/first-boot-setup +StandardInput=tty-force +StandardOutput=tty +StandardError=tty +TTYPath=/dev/tty1 +TTYReset=yes +TTYVHangup=yes +TTYVTDisallocate=yes +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/distros/cachyos/files/gamescope-session-ps5 b/distros/cachyos/files/gamescope-session-ps5 new file mode 100644 index 0000000..8d87203 --- /dev/null +++ b/distros/cachyos/files/gamescope-session-ps5 @@ -0,0 +1,28 @@ +#!/bin/bash +# PS5 gamescope session launcher — invoked by tty1 (ps5-tty-session.sh). +# +# All flags are PS5-specific workarounds: +# LIBSEAT_BACKEND=logind : avoids seatd/logind conflict; matches the user +# session so gamescope can take DRM/input devices +# via logind TakeDevice() +# WLR_LIBINPUT_NO_DEVICES=1: suppresses libinput "no input devices" abort; +# PS5 has no directly-attached HID devices on boot +# --backend drm : take the DRM device directly (no nested compositor) +# -e : embedded / Steam Big Picture mode +# --prefer-output DP-1 : target the PS5's internal DP connector +# --sdr-gamut-wideness 0 : keep SDR colour space at sRGB; the PS5's internal +# DP->HDMI bridge cannot translate BT2020 signalling +# +# The ps5-display.lua script in ~/.config/gamescope/scripts/ enforces sRGB +# colorimetry at the DRM atomic commit level (belt-and-suspenders with the flag). + +export LIBSEAT_BACKEND=logind +export WLR_LIBINPUT_NO_DEVICES=1 + +exec /usr/bin/gamescope \ + --backend drm \ + -e \ + --prefer-output DP-1 \ + --sdr-gamut-wideness 0 \ + -- \ + /usr/bin/steam -gamepadui -steamos3 diff --git a/distros/cachyos/files/grow-rootfs b/distros/cachyos/files/grow-rootfs new file mode 100644 index 0000000..dc36ff3 --- /dev/null +++ b/distros/cachyos/files/grow-rootfs @@ -0,0 +1,14 @@ +#!/bin/bash +# Grows the root partition and filesystem to fill the disk. +# Runs once on first boot, then disables itself. +set -e + +ROOT_DEV=$(findmnt -no SOURCE /) +DISK="/dev/$(lsblk -ndo PKNAME "$ROOT_DEV")" +PART_NUM=$(cat /sys/class/block/$(basename "$ROOT_DEV")/partition) + +growpart "$DISK" "$PART_NUM" + +resize2fs "$ROOT_DEV" + +systemctl disable grow-rootfs.service diff --git a/distros/cachyos/files/grow-rootfs.service b/distros/cachyos/files/grow-rootfs.service new file mode 100644 index 0000000..722eca7 --- /dev/null +++ b/distros/cachyos/files/grow-rootfs.service @@ -0,0 +1,13 @@ +[Unit] +Description=Grow root filesystem to fill disk +DefaultDependencies=no +Before=local-fs.target +After=local-fs-pre.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/grow-rootfs +RemainAfterExit=yes + +[Install] +WantedBy=local-fs.target diff --git a/distros/cachyos/files/plasma-workspace-env-ps5.sh b/distros/cachyos/files/plasma-workspace-env-ps5.sh new file mode 100644 index 0000000..c6f6fb3 --- /dev/null +++ b/distros/cachyos/files/plasma-workspace-env-ps5.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Sourced very early by startplasma-wayland (before KWin). PS5 routes the internal +# DP through an HDMI bridge that mishandles HDR / wide-gamut / 10bpc signalling: +# assumed-HDR EDID paths and AR30/AB30 primaries can yield a valid mode but a black +# HDMI image. Force HDR off and prefer 8bpc/24bpp framebuffers. +# - KWIN_FORCE_ASSUME_HDR_SUPPORT=0 (https://invent.kde.org/plasma/kwin/-/merge_requests/7337) +# - KWIN_DRM_PREFER_COLOR_DEPTH=24 +export KWIN_FORCE_ASSUME_HDR_SUPPORT=0 +export KWIN_DRM_PREFER_COLOR_DEPTH=24 diff --git a/distros/cachyos/files/ps5-display.lua b/distros/cachyos/files/ps5-display.lua new file mode 100644 index 0000000..3ba3fd3 --- /dev/null +++ b/distros/cachyos/files/ps5-display.lua @@ -0,0 +1,25 @@ +-- Force sRGB colorimetry so gamescope does not set BT2020_RGB on the DP +-- connector. The PS5's internal DP->HDMI bridge cannot correctly translate +-- BT2020 signalling to HDMI, causing a black screen. +gamescope.config.known_displays.ps5_srgb_override = { + pretty_name = "PS5 sRGB Override", + hdr = { + supported = false, + force_enabled = false, + eotf = gamescope.eotf.gamma22, + max_content_light_level = 400, + max_frame_average_luminance = 400, + min_content_light_level = 0.5 + }, + colorimetry = { + r = { x = 0.640, y = 0.330 }, + g = { x = 0.300, y = 0.600 }, + b = { x = 0.150, y = 0.060 }, + w = { x = 0.3127, y = 0.3290 } + }, + matches = function(display) + debug("[ps5_srgb_override] Forcing sRGB for PS5 external display") + return 100 + end +} +debug("Registered PS5 sRGB display override") diff --git a/distros/cachyos/files/ps5-tty-session.sh b/distros/cachyos/files/ps5-tty-session.sh new file mode 100644 index 0000000..342c8bd --- /dev/null +++ b/distros/cachyos/files/ps5-tty-session.sh @@ -0,0 +1,73 @@ +# /etc/profile.d/ps5-tty-session.sh — tty1 autologin: Gamescope or Plasma (no SDDM). +# Sourced from /etc/profile (login shells) and from ~/.bashrc (agetty autologin +# usually starts a non-login shell). Idempotent guard avoids double-exec. + +case ${PS5_TTY_SESSION_INIT-} in +1) return ;; +esac +PS5_TTY_SESSION_INIT=1 +export PS5_TTY_SESSION_INIT + +tty_path=$(tty 2>/dev/null) || tty_path="" +[ "$tty_path" = "/dev/tty1" ] || return 0 +[ -z "${WAYLAND_DISPLAY-}" ] || return 0 +[ -z "${DISPLAY-}" ] || return 0 + +SESSION_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/ps5-next-session" +mkdir -p "$(dirname "$SESSION_FILE")" +[ -f "$SESSION_FILE" ] || echo gamescope >"$SESSION_FILE" +read -r SESSION <"$SESSION_FILE" 2>/dev/null || SESSION=gamescope +SESSION=$(printf '%s' "$SESSION" | tr -d '\r\n\t ') + +export LIBSEAT_BACKEND=logind + +case "$SESSION" in + desktop|plasma|plasma-wayland|plasmax11) + # Do not `exec` Plasma: if startplasma-{wayland,x11} exits immediately the login + # shell ends, getty respawns in a tight loop, and start-limit-hit fires. Default + # is Plasma Wayland from VT (no SDDM greeter Xorg). The plasmax11 token is an + # explicit X11 escape hatch via startx + startplasma-x11. + LOG=/tmp/ps5-tty-session.log + # KDE helper: wraps Plasma in a session bus only when one is not already provided + # by systemd --user. Plain dbus-run-session would start an isolated bus and break + # org.freedesktop.systemd1 activation, PipeWire sockets, and xdg-desktop-portal. + PLASMA_DBUS_WRAP="" + [ -x /usr/lib/plasma-dbus-run-session-if-needed ] && PLASMA_DBUS_WRAP=/usr/lib/plasma-dbus-run-session-if-needed + _run_plasma_dbus() { + if [ -n "${PLASMA_DBUS_WRAP}" ]; then + "$PLASMA_DBUS_WRAP" "$@" + elif [ -n "${DBUS_SESSION_BUS_ADDRESS-}" ]; then + "$@" + else + dbus-run-session -- "$@" + fi + } + rv=0 + case "$SESSION" in + plasmax11) + if [ -x /usr/bin/startx ] && [ -x /usr/bin/startplasma-x11 ]; then + _run_plasma_dbus startx /usr/bin/startplasma-x11 -- >>"$LOG" 2>&1 || rv=$? + else + echo "$(date) startx or startplasma-x11 missing — falling back to gamescope" >>"$LOG" + fi + ;; + *) + if [ -x /usr/bin/startplasma-wayland ]; then + _run_plasma_dbus /usr/bin/startplasma-wayland >>"$LOG" 2>&1 || rv=$? + else + echo "$(date) startplasma-wayland missing — falling back to gamescope" >>"$LOG" + fi + ;; + esac + [ "$rv" = 0 ] || echo "$(date) plasma session exited rv=$rv" >>"$LOG" + echo gamescope >"$SESSION_FILE" + unset PS5_TTY_SESSION_INIT + export WLR_LIBINPUT_NO_DEVICES=1 + exec /usr/bin/gamescope-session-ps5 + ;; + gamescope|*) + echo gamescope >"$SESSION_FILE" + export WLR_LIBINPUT_NO_DEVICES=1 + exec /usr/bin/gamescope-session-ps5 + ;; +esac diff --git a/distros/cachyos/files/return-to-gaming-mode.desktop b/distros/cachyos/files/return-to-gaming-mode.desktop new file mode 100644 index 0000000..0fe8226 --- /dev/null +++ b/distros/cachyos/files/return-to-gaming-mode.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Return to Gaming Mode +Comment=Switch back to Steam Big Picture (Gamescope) session +Exec=steamos-session-select gamescope +Icon=steam +Terminal=false +Categories=Game; +Keywords=steam;gamescope;gaming; diff --git a/distros/cachyos/files/steamos-session-select b/distros/cachyos/files/steamos-session-select new file mode 100644 index 0000000..cc35511 --- /dev/null +++ b/distros/cachyos/files/steamos-session-select @@ -0,0 +1,78 @@ +#!/bin/bash +# steamos-session-select — switch between Gamescope and KDE Plasma (no SDDM). +# +# Called by Steam's "Power > Switch to Desktop" (passes "desktop") and by the +# "Return to Gaming Mode" desktop shortcut (passes "gamescope"). +# +# Mechanism +# Root : write ~/.config/ps5-next-session to "plasma" or "gamescope" +# Non-root: sudo to write the token, then end / restart the session. +# +# Direction notes +# Plasma -> Gamescope: terminate the Plasma session; agetty autologin re-runs the +# profile.d script, reads the token and execs gamescope-session-ps5. +# Gamescope -> Plasma: a soft tty handoff leaves the AMD GPU's DP->HDMI bridge in +# a state that produces a black HDMI image even with KWin running and a valid +# 2560x1440 mode set. PCI function-level reset hangs the GPU; amdgpu module +# reload fails with EBUSY because fbcon / snd-hda hold refcounts. Only a real +# reboot — which power-cycles PCI and re-runs atombios — clears the bridge, +# so this direction calls `systemctl reboot`. The next-session token has +# already been written, so tty1 boots straight into Plasma after the reboot. +# On PS5 the boot chain requires re-running the jailbreak after a reboot; +# this is an accepted limitation. +# +# Optional: write `plasmax11` to ~/.config/ps5-next-session for an X11 escape +# hatch via startx + startplasma-x11 (ps5-tty-session.sh). + +set -euo pipefail + +MARKER="/etc/ps5-use-tty-sessions" +SESSION_FILE="/home/steam/.config/ps5-next-session" + +session="${1:-gamescope}" + +# ---------- Root path: write next tty session token ---------- +if [[ $EUID -eq 0 ]]; then + case "$session" in + gamescope) + next="gamescope" + ;; + desktop|plasma|plasma-wayland|plasmax11) + next="plasma" + ;; + *) + echo >&2 "steamos-session-select: unrecognised session '$session'" + exit 1 + ;; + esac + + if [[ ! -f "$MARKER" ]]; then + echo >&2 "steamos-session-select: $MARKER missing — image not using tty sessions" + exit 1 + fi + + mkdir -p "$(dirname "$SESSION_FILE")" + printf '%s\n' "$next" >"$SESSION_FILE" + sync + chown steam:steam "$SESSION_FILE" + chmod 644 "$SESSION_FILE" + echo "steamos-session-select: next tty1 session set to $next" + exit 0 +fi + +# ---------- Non-root path: escalate, then end / restart the session ---------- +sudo "$(realpath "$0")" "$session" + +case "$session" in + gamescope) + if [[ -n "${XDG_SESSION_ID:-}" ]]; then + loginctl terminate-session "$XDG_SESSION_ID" + else + pkill -u steam kwin_wayland || true + pkill -u steam kwin_x11 || true + fi + ;; + desktop|plasma|plasma-wayland|plasmax11) + exec sudo systemctl reboot + ;; +esac diff --git a/distros/cachyos/image.yaml b/distros/cachyos/image.yaml new file mode 100644 index 0000000..bcf0aab --- /dev/null +++ b/distros/cachyos/image.yaml @@ -0,0 +1,335 @@ +image: + name: ps5-cachyos + distribution: archlinux + description: Arch + CachyOS extras — tty1 autologin, Gamescope + Steam, KDE Plasma (no SDDM) + architecture: x86_64 + +source: + downloader: archlinux-http + url: https://geo.mirror.pkgbuild.com/iso + skip_verification: true + +packages: + manager: pacman + # Do not "pacman -Su" the whole bootstrap: CachyOS *-v3 replaces glibc/pacman, breaks Docker/QEMU hooks and multilib. + update: false + cleanup: true + + repositories: + - name: mirrorlist + url: |- + Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch + + sets: + - packages: + # Gamescope + Steam (Big Picture / gamepad UI) + - gamescope + - multilib/steam + - mangohud + - gamemode + - multilib/lib32-mesa + - multilib/lib32-vulkan-radeon + - multilib/lib32-mangohud + - xorg-xwayland + - xorg-xinit + + # KDE Plasma (no SDDM — tty + dbus-run-session + startplasma-wayland). plasma-meta + # pulls plasma-workspace, which ships /usr/bin/startplasma-{wayland,x11}; xorg-xinit + # below provides startx for the optional `plasmax11` next-session token (X11 fallback). + - plasma-meta + - sddm + - qt6-wayland + - egl-wayland + - konsole + - dolphin + + # Audio / media + - pipewire + - wireplumber + - pipewire-pulse + + # Display / GPU (AMD) — qualify extra/ so pacman does not prompt vs [cachyos] mesa + - extra/mesa + - extra/vulkan-radeon + - extra/libva-mesa-driver + - extra/xf86-video-amdgpu + - libinput + - xkeyboard-config + + # Networking + - networkmanager + - linux-firmware + + # System + - base + - openssh + - sudo + - nano + - bash + - mkinitcpio + - kmod + - e2fsprogs + - cloud-utils + - parted + - polkit + - dbus + - kexec-tools + - curl + + # Fonts (Steam UI + KDE) — CJK + emoji avoid "tofu" boxes in Steam Big Picture language list + - ttf-dejavu + - ttf-liberation + - noto-fonts + - noto-fonts-cjk + - noto-fonts-emoji + - fontconfig + action: install + +files: +- path: /etc/hostname + generator: hostname + +- path: /etc/hosts + generator: hosts + +- path: /etc/kernel/postinst.d/zz-update-boot + generator: copy + source: /tmp/build-staging/zz-update-boot + mode: "0755" + +- path: /etc/fstab + generator: copy + source: /tmp/build-staging/fstab + +- path: /usr/local/sbin/grow-rootfs + generator: copy + source: /tmp/build-staging/files/grow-rootfs + mode: "0755" + +- path: /etc/systemd/system/grow-rootfs.service + generator: copy + source: /tmp/build-staging/files/grow-rootfs.service + +- path: /opt/pkgs/ + generator: copy + source: /tmp/build-staging/pkgs + +- path: /usr/local/sbin/first-boot-setup + generator: copy + source: /tmp/build-staging/files/first-boot-setup + mode: "0755" + +- path: /etc/systemd/system/first-boot.service + generator: copy + source: /tmp/build-staging/files/first-boot.service + +- path: /etc/profile.d/ps5-tty-session.sh + generator: copy + source: /tmp/build-staging/files/ps5-tty-session.sh + mode: "0644" + +- path: /usr/bin/gamescope-session-ps5 + generator: copy + source: /tmp/build-staging/files/gamescope-session-ps5 + mode: "0755" + +- path: /usr/bin/steamos-session-select + generator: copy + source: /tmp/build-staging/files/steamos-session-select + mode: "0755" + +- path: /usr/share/applications/return-to-gaming-mode.desktop + generator: copy + source: /tmp/build-staging/files/return-to-gaming-mode.desktop + +- path: /usr/local/share/ps5-display.lua + generator: copy + source: /tmp/build-staging/files/ps5-display.lua + +- path: /usr/local/share/plasma-workspace-env-ps5.sh + generator: copy + source: /tmp/build-staging/files/plasma-workspace-env-ps5.sh + mode: "0755" + +actions: +- trigger: post-unpack + action: |- + #!/bin/bash + set -eux + # Pacman 7 Landlock/seccomp sandbox breaks under distrobuilder/Docker (unsupported kernel). + if grep -q '^#DisableSandbox' /etc/pacman.conf; then + sed -i 's/^#DisableSandbox/DisableSandbox/' /etc/pacman.conf + elif ! grep -q '^DisableSandbox' /etc/pacman.conf; then + sed -i '/^\[options\]/a DisableSandbox' /etc/pacman.conf + fi + + CACHYOS_PKG="https://mirror.cachyos.org/repo/x86_64/cachyos" + + pacman-key --init + pacman-key --populate archlinux + + pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com + pacman-key --lsign-key F3B607488DB35A47 + + # Keyring + main mirrorlist only (no *-v3 repos — those force a full distro migration in the chroot). + pacman -U --noconfirm \ + "${CACHYOS_PKG}/cachyos-keyring-20240331-1-any.pkg.tar.zst" \ + "${CACHYOS_PKG}/cachyos-mirrorlist-27-1-any.pkg.tar.zst" + + # Optional CachyOS packages: repo listed *after* Arch so core/extra/multilib win on conflicts (mesa, steam deps). + if ! grep -q '^\[cachyos\]' /etc/pacman.conf; then + printf '\n[cachyos]\nInclude = /etc/pacman.d/cachyos-mirrorlist\n' >> /etc/pacman.conf + fi + + # Enable #[multilib] / #Include block (\\\\ in YAML would break the sed address regex) + sed -i '/^#\[multilib\]/,/^#Include/s/^#//' /etc/pacman.conf + + curl -fsSL "https://archlinux.org/mirrorlist/?country=all&protocol=https&use_mirror_status=on" \ + | sed 's/^#Server/Server/' > /etc/pacman.d/mirrorlist + + pacman -Syy --noconfirm + + +- trigger: post-packages + action: |- + #!/bin/bash + set -eux + # fc-cache / systemctl can SIGSEGV under QEMU when image plat is linux/amd64 on arm64 hosts. + fc-cache -f 2>/dev/null || true + + symlink_wants() { + local src=$1 + local target=$2 + [ -e "$src" ] || return 0 + local base + base=$(basename "$src") + mkdir -p "/etc/systemd/system/${target}.wants" + ln -sf "$src" "/etc/systemd/system/${target}.wants/${base}" + } + + sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config + sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config + symlink_wants /usr/lib/systemd/system/sshd.service multi-user.target + symlink_wants /usr/lib/systemd/system/NetworkManager.service multi-user.target + # PipeWire/WirePlumber: user session after tty autologin; default.target matches Arch Sway image. + for s in pipewire pipewire-pulse wireplumber; do + symlink_wants "/usr/lib/systemd/system/${s}.service" default.target || true + done + +- trigger: post-files + action: |- + #!/bin/bash + set -eux + mkdir -p /etc/systemd/system/local-fs.target.wants + ln -sf /etc/systemd/system/grow-rootfs.service \ + /etc/systemd/system/local-fs.target.wants/grow-rootfs.service + + # Stub out steamos-update so Steam's -steamos3 OOBE skips the SteamOS OTA + # step cleanly. Exit 7 = "no update available" per Valve's convention. + printf '#!/bin/bash\n# Stub: no SteamOS OTA on this image\nexit 7\n' \ + > /usr/bin/steamos-update + chmod +x /usr/bin/steamos-update + + useradd -m -G wheel,seat,video,audio,input -s /bin/bash steam + echo "steam:steam" | chpasswd + echo "%wheel ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel + + # --- No SDDM: tty1 autologin + /etc/profile.d/ps5-tty-session.sh (+ ~/.bashrc source) --- + # SDDM is only a display manager (login UI, VT handoff, relaunch). plasma-meta may + # still pull sddm as a dependency; keep it masked. Remove leftover SDDM config from older images. + rm -f /etc/sddm.conf.d/steamos.conf /etc/sddm.conf.d/zz-steamos-autologin.conf 2>/dev/null || true + rm -f /etc/systemd/system/sddm.service.d/ps5-seat.conf 2>/dev/null || true + rmdir /etc/systemd/system/sddm.service.d 2>/dev/null || true + rm -f /usr/local/share/steam-bash_profile.ps5 2>/dev/null || true + + touch /etc/ps5-use-tty-sessions + ln -sf /usr/lib/systemd/system/multi-user.target /etc/systemd/system/default.target + ln -sf /dev/null /etc/systemd/system/sddm.service + + mkdir -p /etc/systemd/system/getty@tty1.service.d + printf '%s\n' '[Service]' 'ExecStart=' 'ExecStart=-/sbin/agetty --autologin steam --noclear %I $TERM' \ + > /etc/systemd/system/getty@tty1.service.d/autologin.conf + printf '%s\n' '[Unit]' 'After=first-boot.service' \ + > /etc/systemd/system/getty@tty1.service.d/after-first-boot.conf + + mkdir -p /etc/systemd/system/multi-user.target.wants + ln -sf /etc/systemd/system/first-boot.service \ + /etc/systemd/system/multi-user.target.wants/first-boot.service + + # agetty --autologin often starts a non-login shell — ~/.bashrc must source profile.d. + # Write via printf (not heredoc) to avoid column-0 lines that would terminate this + # YAML block scalar. + printf '%s\n' \ + '# ps5-linux-image: tty1 autostart when /etc/profile was skipped (agetty autologin).' \ + 'if [ -f /etc/profile.d/ps5-tty-session.sh ]; then' \ + ' . /etc/profile.d/ps5-tty-session.sh' \ + 'fi' \ + > /home/steam/.bashrc + chown steam:steam /home/steam/.bashrc + chmod 644 /home/steam/.bashrc + + printf '%s\n' \ + '# Login shells: /etc/profile sources profile.d first; keep .bashrc for interactive tools.' \ + '[ -f ~/.bashrc ] && . ~/.bashrc' \ + > /home/steam/.bash_profile + chown steam:steam /home/steam/.bash_profile + chmod 644 /home/steam/.bash_profile + + mkdir -p /home/steam/.config + echo gamescope > /home/steam/.config/ps5-next-session + # User PipeWire (systemd --user): post-packages symlinks system default.target, which does + # not pull user units. Enable sockets/services under the steam user's default.target so + # Plasma (real session bus) can connect to PipeWire. + mkdir -p /home/steam/.config/systemd/user/default.target.wants + for u in /usr/lib/systemd/user/pipewire.socket /usr/lib/systemd/user/pipewire-pulse.socket \ + /usr/lib/systemd/user/wireplumber.service; do + [ -e "$u" ] || continue + ln -sf "$u" "/home/steam/.config/systemd/user/default.target.wants/$(basename "$u")" + done + chown -R steam:steam /home/steam/.config/systemd + + # Gamescope display profile: force sRGB colorimetry so gamescope does not set + # BT2020_RGB on the DP connector. The PS5's internal DP->HDMI bridge cannot + # correctly translate BT2020 signalling to HDMI, causing a black screen. + # Staged via files: to /usr/local/share/ps5-display.lua to avoid heredoc + # indentation conflicts with the YAML literal block scalar. + mkdir -p /home/steam/.config/gamescope/scripts + cp /usr/local/share/ps5-display.lua /home/steam/.config/gamescope/scripts/ps5-display.lua + + # Plasma (X11 from tty): avoid HDR negotiation that black-screens the PS5 DP→HDMI path. + mkdir -p /home/steam/.config/plasma-workspace/env + cp /usr/local/share/plasma-workspace-env-ps5.sh \ + /home/steam/.config/plasma-workspace/env/ps5-drm-bridge.sh + chmod +x /home/steam/.config/plasma-workspace/env/ps5-drm-bridge.sh + + # KDE Desktop shortcut: "Return to Gaming Mode" + # chmod +x lets KDE trust and execute it without showing an "untrusted" dialog. + mkdir -p /home/steam/Desktop + cp /usr/share/applications/return-to-gaming-mode.desktop /home/steam/Desktop/ + chmod +x /home/steam/Desktop/return-to-gaming-mode.desktop + + chown -R steam:steam /home/steam + + [ -f /etc/vconsole.conf ] || echo "KEYMAP=us" > /etc/vconsole.conf + + # Busybox-based initrd: polls for the root device (works after kexec, unlike + # the systemd initrd which waits for udev events that never re-fire). + # `kms` loads the amdgpu module + firmware early so the display comes up + # before the rootfs is mounted. `keyboard` allows emergency-mode input. + sed -i \ + -e 's/^HOOKS=.*/HOOKS=(base udev modconf kms keyboard block filesystems fsck)/' \ + -e 's/^MODULES=.*/MODULES=(amdgpu)/' \ + /etc/mkinitcpio.conf + + if ls /opt/pkgs/*.pkg.tar.zst 1>/dev/null 2>&1; then + pacman -U --noconfirm /opt/pkgs/*.pkg.tar.zst + rm -rf /opt/pkgs + KVER=$(ls -1t /lib/modules | head -1) + mkinitcpio -k "$KVER" -g "/boot/initrd.img-$KVER" || true + fi + + mkdir -p /boot/efi + /etc/kernel/postinst.d/zz-update-boot + +mappings: + architecture_map: archlinux diff --git a/docker/image-builder/entrypoint-multi.sh b/docker/image-builder/entrypoint-multi.sh index f4318e2..eced5de 100644 --- a/docker/image-builder/entrypoint-multi.sh +++ b/docker/image-builder/entrypoint-multi.sh @@ -5,7 +5,7 @@ set -ex IMG_SIZE="${IMG_SIZE:-32000}" SKIP_CHROOT="${SKIP_CHROOT:-false}" -DISTROS="${DISTROS:-ubuntu2404 ubuntu2604 arch alpine}" +DISTROS="${DISTROS:-ubuntu2404 ubuntu2604 arch alpine cachyos}" STAGING="/tmp/build-staging" EFI_LABEL="boot" IMG="/output/ps5-multi.img" @@ -34,22 +34,38 @@ for DISTRO in $DISTROS; do printf 'LABEL=%-14s / ext4 rw,relatime 0 1\nLABEL=%-14s /boot/efi vfat rw,relatime 0 2\n' \ "$ROOT_LABEL" "$EFI_LABEL" > "$STAGING/fstab" - cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/${DISTRO}/nm-dns.conf "$STAGING/" 2>/dev/null || true case "$DISTRO" in ubuntu*) + cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/${DISTRO}/grow-rootfs.service "$STAGING/" cp /kernel-debs/*.deb "$STAGING/debs/" ;; alpine) + cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/alpine/grow-rootfs.openrc "$STAGING/" ;; arch) + cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/arch/grow-rootfs.service "$STAGING/" cp /repo/distros/arch/first-boot-setup "$STAGING/" cp /kernel-debs/*.pkg.tar.zst "$STAGING/pkgs/" ;; + cachyos) + mkdir -p "$STAGING/files" + cp /repo/distros/cachyos/files/grow-rootfs "$STAGING/files/" + cp /repo/distros/cachyos/files/grow-rootfs.service "$STAGING/files/" + cp /repo/distros/cachyos/files/first-boot-setup "$STAGING/files/" + cp /repo/distros/cachyos/files/first-boot.service "$STAGING/files/" + cp /repo/distros/cachyos/files/gamescope-session-ps5 "$STAGING/files/" + cp /repo/distros/cachyos/files/steamos-session-select "$STAGING/files/" + cp /repo/distros/cachyos/files/return-to-gaming-mode.desktop "$STAGING/files/" + cp /repo/distros/cachyos/files/ps5-display.lua "$STAGING/files/" + cp /repo/distros/cachyos/files/plasma-workspace-env-ps5.sh "$STAGING/files/" + cp /repo/distros/cachyos/files/ps5-tty-session.sh "$STAGING/files/" + cp /kernel-debs/*.pkg.tar.zst "$STAGING/pkgs/" + ;; esac # --- Build rootfs --- diff --git a/docker/image-builder/entrypoint.sh b/docker/image-builder/entrypoint.sh index 5b3c0df..a4054d6 100755 --- a/docker/image-builder/entrypoint.sh +++ b/docker/image-builder/entrypoint.sh @@ -26,23 +26,40 @@ else LABEL=$ROOT_LABEL / ext4 defaults 0 1 LABEL=$EFI_LABEL /boot/efi vfat defaults 0 1 EOF - cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/${DISTRO}/nm-dns.conf "$STAGING/" 2>/dev/null || true case "$DISTRO" in ubuntu*) + cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/${DISTRO}/grow-rootfs.service "$STAGING/" cp /kernel-debs/*.deb "$STAGING/debs/" ;; alpine) + cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/alpine/grow-rootfs.openrc "$STAGING/" ;; arch) + cp /repo/distros/${DISTRO}/grow-rootfs "$STAGING/" cp /repo/distros/arch/grow-rootfs.service "$STAGING/" cp /repo/distros/arch/first-boot-setup "$STAGING/" mkdir -p "$STAGING/pkgs" cp /kernel-debs/*.pkg.tar.zst "$STAGING/pkgs/" ;; + cachyos) + mkdir -p "$STAGING/files" + cp /repo/distros/cachyos/files/grow-rootfs "$STAGING/files/" + cp /repo/distros/cachyos/files/grow-rootfs.service "$STAGING/files/" + cp /repo/distros/cachyos/files/first-boot-setup "$STAGING/files/" + cp /repo/distros/cachyos/files/first-boot.service "$STAGING/files/" + cp /repo/distros/cachyos/files/gamescope-session-ps5 "$STAGING/files/" + cp /repo/distros/cachyos/files/steamos-session-select "$STAGING/files/" + cp /repo/distros/cachyos/files/return-to-gaming-mode.desktop "$STAGING/files/" + cp /repo/distros/cachyos/files/ps5-display.lua "$STAGING/files/" + cp /repo/distros/cachyos/files/plasma-workspace-env-ps5.sh "$STAGING/files/" + cp /repo/distros/cachyos/files/ps5-tty-session.sh "$STAGING/files/" + mkdir -p "$STAGING/pkgs" + cp /kernel-debs/*.pkg.tar.zst "$STAGING/pkgs/" + ;; esac # --- Build rootfs ---