Files
steamify-cachyos-dev/.claude/skills/cachyos-vm-testing/SKILL.md
T
theupriser c6abfd2ea8 docs: Renamed to steamify-cachyos-dev, for Steamify CachyOS
README and skill point to github.com/theupriser/steamify-cachyos; local paths keep their names.
2026-09-24 12:28:13 +02:00

12 KiB

name, description
name description
cachyos-vm-testing Use when testing Steamify CachyOS (repo steamify-cachyos, setup-gamescope-boot.sh) changes in the CachyOS QEMU test VM - starting/stopping the VM, restoring snapshots, running the wizard over SSH with scripted menu input, checking each component's state, reboot checks and screenshots.

Testing Steamify CachyOS in the CachyOS VM

The wizard (setup-gamescope-boot.sh + lib/*.sh) opens a menu that detects which components are on and toggles them to match the user's choice. Menu order:

  1. SteamOS conversion (boot into gaming mode via autologin, Return to Gaming Mode shortcut, Steam desktop autostart)
  2. SteamOS theme (installs cachyos-vapor and applies the Vapor global theme with its desktop and window layout; needs a running Plasma session)
  3. Steam Deck/Machine icons (STEAM_GAMEPADUI_ARGS -steamos3)
  4. Single user mode (SDDM, no lock screen/user switching/log out; enabling it enables 1, disabling 1 disables it)
  5. Steam Machine support (only on DMI Valve/Fremont: leds-valve-dkms-git built for every kernel via /etc/dkms/leds-valve-dkms.conf, ensure-kernel-headers.service, udev rule, steamos-manager)

Undo journals live in ~/.local/state/cachyos-gamescope-boot/ in the guest.

Run everything from the root of this repo on the host. Defaults: user theupriser, SSH port 2222 (the scripts take VM_USER / VM_PORT / VM_HOST).

The VM's disk, vars*.fd and run.sh copy may live outside the repo (~/vms/cachyos-test on the main dev machine). Find it with readlink /proc/$(pgrep -f '^qemu-system')/cwd when the VM runs; the snapshot commands below run in that directory. scripts/vmreset.sh takes VM_DIR and defaults to the repo if it holds disk.qcow2, else ~/vms/cachyos-test.

Quick start (the usual loop)

scripts/vmreset.sh --fremont        # restore ssh-ready, boot, mount repo, autologin, wait for Plasma
scripts/cmp.sh save                 # baseline of the KDE configs
scripts/vmwatch.sh '1\n3\n\ny\nn\n' "Theme only"   # wizard in a visible Konsole in the VM
scripts/vmstate.sh                  # component state
scripts/vmshot.sh /path/shot.png    # screenshot, then view it

Prefer vmwatch.sh over vmrun.sh when the user is watching the QEMU window: vmrun.sh runs invisibly over SSH, so they see nothing happen.

VM lifecycle

Start in the background:

./run.sh --fremont > /tmp/vm.log 2>&1 &     # flags: [install] [--fremont] [--nvidia] [--vulkan]

Wait for SSH:

until ssh -p 2222 -o BatchMode=yes -o ConnectTimeout=3 theupriser@localhost true 2>/dev/null; do sleep 3; done

Shut down and wait until QEMU is gone. Use '^qemu-system': a plain pgrep -f qemu-system also matches the shell running it.

ssh -p 2222 -o BatchMode=yes theupriser@localhost sudo systemctl poweroff
while pgrep -f '^qemu-system' >/dev/null; do sleep 2; done

Snapshots

Only while the VM is off. Keep vars.fd together with the disk.

qemu-img snapshot -l disk.qcow2                                               # list
qemu-img snapshot -a ssh-ready disk.qcow2 && cp vars.ssh-ready.fd vars.fd     # restore
qemu-img snapshot -c my-state disk.qcow2 && cp vars.fd vars.my-state.fd       # create

Existing snapshots: clean (fresh install) and ssh-ready (sshd + host key + passwordless sudo). Reset to ssh-ready before each full test run.

First-time guest setup

Only when building from clean. In the guest console:

sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh

This installs and enables sshd, opens the firewall (ufw/firewalld if active), adds the host keys (share/host-keys.pub, written by run.sh) and a NOPASSWD sudoers rule for the test user.

Mounting the project repo

The mount is lost on every reboot (unless the fstab line below was added). Without it the wizard fails with /mnt/setup-gamescope-boot.sh: No such file or directory, which is easy to miss in filtered output. Remount after each reboot.

The repo (REPO, default ~/projects/cachyos-gamescope-boot) is shared read-write as 9p tag repo. The ssh-ready snapshot does not mount it:

ssh -p 2222 -o BatchMode=yes theupriser@localhost bash -s << 'EOF'
sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
# optional, survives reboots:
grep -q ' /mnt 9p ' /etc/fstab || echo 'repo /mnt 9p trans=virtio,version=9p2000.L,nofail 0 0' | sudo tee -a /etc/fstab
EOF

Remote commands: the guest shell is fish

Pass remote commands as bash heredocs, never as inline strings containing $:

ssh -p 2222 -o BatchMode=yes theupriser@localhost bash -s << 'EOF'
echo "$HOME"
EOF

Getting a Plasma session

On a fresh snapshot the plasmalogin greeter is shown. Enable test autologin:

ssh -p 2222 -o BatchMode=yes theupriser@localhost bash -s << 'EOF'
sudo mkdir -p /etc/plasmalogin.conf.d
printf '[Autologin]\nUser=theupriser\nSession=plasma.desktop\n' | sudo tee /etc/plasmalogin.conf.d/00-test-autologin.conf
sudo systemctl restart plasmalogin
EOF

Remove /etc/plasmalogin.conf.d/00-test-autologin.conf before testing the "normal login screen" case.

Running the wizard non-interactively

scripts/vmrun.sh '<input>' does this (log also at /tmp/wizard.log in the guest). Manually, in the guest:

export XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
printf '\ny\nn\n' | /mnt/setup-gamescope-boot.sh

Menu input: a number toggles a component, an empty line continues, a re-applies what is on, q quits. Then y answers "Go ahead?" and n the restart question.

Input Meaning
'\ny\nn\n' first run, accept all
'2\n\ny\nn\n' toggle the theme
'a\ny\nn\n' re-apply what is on
'q\n' just show the menu
'1\n3\n\ny\nn\n' from all-off: theme only (Steam Machine too with --fremont; add 5\n to drop it)
'1\n\ny\nn\n' from a state where 1 is off: turn the conversion on

Know the starting ticks before choosing input. When everything is off (fresh snapshot, or after turning the last component off), the menu treats it as a first run and pre-ticks all components, so 2 means "theme off, rest on". Otherwise the ticks show what is on now. Unticking 1 also unticks 4, and ticking 4 ticks 1 again, so '1\n3\n4\n...' ends with the conversion on. When unsure, run 'q\n' first and read the ticks.

a only re-applies components that are already on: it does not retry one that failed. Re-applying the conversion also resets the autologin session to gamescope, so set it back to plasma before the next reboot (below).

Gamescope does not render in this VM

(Also after any re-apply that includes the conversion.)

No suitable Vulkan (venus is unstable with the host NVIDIA driver). After enabling the conversion, before rebooting:

sudo /usr/lib/steamos/steam-set-session plasma.desktop

so autologin lands in Plasma. Stuck in a gamescope relogin loop: over SSH set the session to plasma and sudo systemctl restart display-manager (or stop the DM, set the session, start it).

Checking component state

scripts/vmstate.sh prints:

  • display manager, SDDM autologin file, plasmalogin [Autologin] section
  • session sync bridge units (sync-steamos-session.path) and sudoers rule (gamescope-session-switch)
  • Return to Gaming Mode shortcut Exec= line
  • steam-desktop-autostart user unit; glyphs env file (99-gamescope-steam-glyphs.conf)
  • LookAndFeelPackage, ColorScheme, GTK theme, font
  • KDE Action Restrictions (lock_screen), kscreenlockerrc Autolock, Lock Session shortcut
  • panel floating/thickness in plasmashellrc
  • kickoff primaryActions / systemFavorites / icon
  • leds-valve-dkms-git package, udev rule, leds_valve module, /sys/class/leds/valve-leds* count and owner (user must be able to write)
  • steamos-manager package and whether it is active
  • undo journals present, and duplicate keys (cut -f1-3 journal | sort | uniq -d)

scripts/cmp.sh save stores a baseline of the KDE/GTK configs in the guest (~/.cache/vm-baseline, not /tmp); scripts/cmp.sh diffs against it.

After changing the theme, cmp.sh should show only spectacle's kglobalshortcutsrc entries (from screenshots) and keys whose value equals ~/.config/kdedefaults (e.g. ColorScheme=BreezeDark, widgetStyle=Breeze): KDE drops or writes those on its own.

Reboot checks

  • Single user mode on: SDDM autologin without greeter: pgrep sddm-greeter empty, plasmashell running.
  • Everything off: plasmalogin greeter shown: loginctl list-sessions shows a greeter session, no user plasmashell.

Full test matrix

Reset to ssh-ready, start with --fremont, mount the repo, enable test autologin, then (all passed last run; scripts/vmstate.sh after every step):

  1. Fresh run turning everything on ('\ny\nn\n'), set session to plasma, reboot.
  2. Rerun: all shown on, "Everything is already the way you want it".
  3. a re-apply: no duplicate journal entries.
  4. Theme on: Steam Deck wallpaper, full-width 46px panel, distributor-logo-steamdeck launcher icon, dark-lnf=com.valve.vapor.desktop (Brightness & Color's Dark Mode toggle is on, hint "Switch to Breeze"); with single user on, kickoff keeps primaryActions=3. Theme off: CachyOS wallpaper, floating 30px panel, CachyOS launcher icon, BreezeDark colors (not light), cachyos-vapor removed, cmp.sh clean.
  5. Single user off: switches to plasmalogin + sync bridge + sudoers; shortcut Exec uses sudo -n.
  6. Single user on: back to SDDM.
  7. Icons + Steam Machine support off: driver, udev rule, modules-load, steamos-manager removed; yay kept. 7b. DKMS: vmstate.sh shows installed for every kernel. Headers at boot: sudo pacman -R --noconfirm linux-cachyos-lts-headers (DKMS drops the LTS build), set the session to plasma, reboot, then journalctl -b -u ensure-kernel-headers shows the install and DKMS lists LTS again.
  8. Conversion off: single user auto-unticked, plasmalogin [Autologin] back to CachyOS's Session=plasma, journals empty.
  9. Remove the test autologin file, reboot: normal login screen.

BIOS update item

Only shown with --fremont, and only tickable when the guest's BIOS version differs from Valve's newest. Fake an older one with BIOS_VERSION=F7F0107 ./run.sh --fremont (SMBIOS type 0; vmreset.sh passes the environment through). fwupd correctly refuses the firmware in the VM ("not for this machine's hardware"), so walk the whole flow with WIZARD_BIOS_DRY_RUN=1 (skips only that check, never flashes). Scripted: printf '6\n\ny\ny\nUPDATE\n\nq\n' | WIZARD_BIOS_DRY_RUN=1 /mnt/setup-gamescope-boot.sh.

The wizard loops back to the menu after every run (Enter, or m/r when a restart is needed) until q; scripted input that runs out ends it like q.

Visual checks

scripts/vmshot.sh <out.png> does the below. Starting spectacle straight from SSH core-dumps; it has to run as a user unit (systemd-run --user --wait).

ssh -p 2222 -o BatchMode=yes theupriser@localhost bash -s << 'EOF'
export XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
qdbus6 org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.activateLauncherMenu   # optional, toggles
sleep 1; systemd-run --user --wait -q spectacle -b -n -f -o /tmp/x.png
EOF
scp -P 2222 theupriser@localhost:/tmp/x.png /tmp/x.png    # then view it

The launcher call toggles; retake if the screenshot misses it. Apps started directly over SSH lack the session's Qt platform theme and look light; start them with systemd-run --user <app>.

Pitfalls

  • Broken mirror: mirror5.krfoss.org served a bad .sig ("Maximum file size exceeded"), failing the conversion's package install. Comment it out: sudo sed -i '/krfoss/s/^Server/#Server/' /etc/pacman.d/*mirrorlist*.

  • Windows open in the snapshot (System Settings, CachyOS Hello) show stale data (e.g. a theme list from before cachyos-vapor) and cover screenshots; vmreset.sh closes them.

  • The guest's /tmp is cleared on reboot; keep baselines elsewhere.

  • The fake Fremont DMI makes leds-valve load 17 LED nodes, but there is no real hardware.

  • shellcheck: sudo pacman -S shellcheck, then in /mnt: shellcheck -S warning -x setup-gamescope-boot.sh lib/*.sh (SC2154/SC2034 cross-file warnings are false positives).

  • This repo's .gitignore is an allowlist: it ignores everything and un-ignores only the tracked scripts/docs. Add any new tracked file to it explicitly, or git will silently ignore it.