From 97c5c569c822485b165c4301b8db3b3a76bbb26a Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Wed, 23 Sep 2026 15:14:17 +0200 Subject: [PATCH] fix: Add CachyOS test VM dev environment and VM testing skill QEMU/KVM scripts for a CachyOS KDE test VM (run.sh/run.ps1, get-iso, guest SSH setup), host helper scripts to drive and inspect the cachyos-gamescope-boot wizard in the guest, and a Claude Code skill documenting the test workflow. --- .claude/skills/cachyos-vm-testing/SKILL.md | 204 +++++++++++++++++++++ .gitignore | 27 +++ README.md | 83 +++++++++ add-ssh-key.ps1 | 9 + get-iso.ps1 | 28 +++ get-iso.sh | 29 +++ run.ps1 | 29 +++ run.sh | 49 +++++ scripts/cmp.sh | 25 +++ scripts/common.sh | 5 + scripts/vmrun.sh | 21 +++ scripts/vmstate.sh | 22 +++ share/guest-ssh-setup.sh | 31 ++++ 13 files changed, 562 insertions(+) create mode 100644 .claude/skills/cachyos-vm-testing/SKILL.md create mode 100644 .gitignore create mode 100644 README.md create mode 100644 add-ssh-key.ps1 create mode 100644 get-iso.ps1 create mode 100755 get-iso.sh create mode 100644 run.ps1 create mode 100755 run.sh create mode 100755 scripts/cmp.sh create mode 100644 scripts/common.sh create mode 100755 scripts/vmrun.sh create mode 100755 scripts/vmstate.sh create mode 100755 share/guest-ssh-setup.sh diff --git a/.claude/skills/cachyos-vm-testing/SKILL.md b/.claude/skills/cachyos-vm-testing/SKILL.md new file mode 100644 index 0000000..e73e811 --- /dev/null +++ b/.claude/skills/cachyos-vm-testing/SKILL.md @@ -0,0 +1,204 @@ +--- +name: cachyos-vm-testing +description: Use when testing cachyos-gamescope-boot (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 cachyos-gamescope-boot 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 (Vapor) +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, 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`). + +## VM lifecycle + +Start in the background: + +```bash +./run.sh --fremont > /tmp/vm.log 2>&1 & # flags: [install] [--fremont] [--nvidia] [--vulkan] +``` + +Wait for SSH: + +```bash +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. + +```bash +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. + +```bash +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: + +```bash +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 repo (`REPO`, default `~/projects/cachyos-gamescope-boot`) is shared +read-write as 9p tag `repo`. The `ssh-ready` snapshot does not mount it: + +```bash +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 `$`: + +```bash +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: + +```bash +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 ''` does this (log also at `/tmp/wizard.log` in the +guest). Manually, in the guest: + +```bash +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 | + +## Gamescope does not render in this VM + +No suitable Vulkan (venus is unstable with the host NVIDIA driver). After +enabling the conversion, before rebooting: + +```bash +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. + +## 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 off: look restored (CachyOS wallpaper, floating 30px panel, `org.cachyos.hello` launcher icon). +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. +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. + +## Visual checks + +```bash +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; 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 `. + +## Pitfalls + +- 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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbdeb74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Allowlist: ignore everything, then un-ignore only scripts and docs. +# New tracked files must be added here explicitly. +/* +!/.gitignore +!/README.md +!/run.sh +!/run.ps1 +!/get-iso.sh +!/get-iso.ps1 +!/add-ssh-key.ps1 +!/scripts/ +!/share/ +!/.claude/ + +share/* +!share/guest-ssh-setup.sh + +# Belt and braces, anywhere in the tree +*.iso +*.iso.version +*.qcow2 +*.img +*.fd +*.png +*.log +*.tmp +host-keys.pub diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3a0ece --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# cachyos-gamescope-boot dev environment + +A QEMU/KVM test VM running CachyOS (KDE Plasma 6) for developing and testing +[cachyos-gamescope-boot](https://github.com/theupriser/cachyos-gamescope-boot), +plus helper scripts and a Claude Code skill that documents the test workflow. + +Only scripts and docs live here. The ISO, disk image, UEFI variable stores, +logs and screenshots are generated locally. `.gitignore` is an allowlist: it +ignores everything and un-ignores only the tracked files, so add any new +tracked file to it explicitly. + +## Requirements + +- Linux host with KVM, `qemu-system-x86_64` (GTK/OpenGL display, virtfs/9p) and `qemu-img` + (Ubuntu: `sudo apt install qemu-system-x86 qemu-utils ovmf`). +- OVMF firmware at `/usr/share/OVMF/OVMF_CODE_4M.fd` and `OVMF_VARS_4M.fd`. +- `curl` and `sha256sum` for `get-iso.sh`. +- Optional: an NVIDIA dGPU for `--nvidia` (PRIME offload of the virgl renderer). + +### Windows + +`run.ps1` and `get-iso.ps1` are Windows versions. They need +[QEMU for Windows](https://qemu.weilnetz.de/w64/) (or set `QEMU_DIR`) and the +"Windows Hypervisor Platform" feature (`-accel whpx`). Windows QEMU has no 9p +shares: copy the repo into the guest with `scp -P 2222 -r ...`, and authorize +your key with `.\add-ssh-key.ps1 -User `. + +## Quick start + +```bash +./get-iso.sh # download and verify the latest CachyOS desktop ISO +./run.sh install # first run creates disk.qcow2 (60G) and vars.fd, boots the ISO +``` + +Install CachyOS with the **KDE Plasma** desktop and **plasma-login-manager** +as login manager. Power off and snapshot (only while the VM is off): + +```bash +qemu-img snapshot -c clean disk.qcow2 && cp vars.fd vars.clean.fd +``` + +Boot the installed system (`./run.sh`) and, in the guest, enable SSH with key +auth and passwordless sudo through the read-only `vmtools` share: + +```bash +sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh +``` + +Power off and snapshot again: + +```bash +qemu-img snapshot -c ssh-ready disk.qcow2 && cp vars.fd vars.ssh-ready.fd +``` + +Connect with `ssh -p 2222 @localhost`. The project repo is shared +read-write as 9p tag `repo`; in the guest: +`sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt`. + +### run.sh options + +``` +[REPO=/path/to/cachyos-gamescope-boot] ./run.sh [install] [--nvidia] [--vulkan] [--fremont] +``` + +- `install`: boot the installer ISO +- `--nvidia`: render the guest's virtio-gpu (virgl) on the host NVIDIA dGPU +- `--vulkan`: expose Vulkan to the guest (venus; unstable) +- `--fremont`: fake the Valve Steam Machine (Fremont) DMI data via `-smbios` +- `REPO` defaults to `$HOME/projects/cachyos-gamescope-boot` + +## Helper scripts + +- `scripts/vmrun.sh ''`: run the wizard in the guest's Plasma session with scripted input +- `scripts/vmstate.sh`: print the state of every wizard component +- `scripts/cmp.sh [save]`: save / diff the guest's KDE configs against a baseline + +They use `VM_USER` (default `theupriser`), `VM_PORT` (`2222`) and `VM_HOST` (`localhost`). + +## Claude Code skill + +The full test workflow is documented in +[`.claude/skills/cachyos-vm-testing/SKILL.md`](.claude/skills/cachyos-vm-testing/SKILL.md); +Claude Code picks it up when run from this repo. diff --git a/add-ssh-key.ps1 b/add-ssh-key.ps1 new file mode 100644 index 0000000..aaeb0a1 --- /dev/null +++ b/add-ssh-key.ps1 @@ -0,0 +1,9 @@ +# Windows: authorize your SSH key in the VM (asks for the VM password once). +# Run after guest-ssh-setup.sh inside the VM, or at least after sshd is enabled there. +# .\add-ssh-key.ps1 -User +param([Parameter(Mandatory)][string]$User) +$ErrorActionPreference = 'Stop' +$key = Join-Path $env:USERPROFILE '.ssh\id_ed25519.pub' +if (-not (Test-Path $key)) { ssh-keygen -t ed25519 -f ($key -replace '\.pub$', '') -N '""' } +Get-Content $key | ssh -p 2222 "$User@localhost" 'install -d -m 700 ~/.ssh && k=$(cat) && (grep -qxF "$k" ~/.ssh/authorized_keys 2>/dev/null || echo "$k" >> ~/.ssh/authorized_keys) && chmod 600 ~/.ssh/authorized_keys' +Write-Host "Done. Connect with: ssh -p 2222 $User@localhost" diff --git a/get-iso.ps1 b/get-iso.ps1 new file mode 100644 index 0000000..cdb015e --- /dev/null +++ b/get-iso.ps1 @@ -0,0 +1,28 @@ +# Download the latest CachyOS desktop ISO to cachyos.iso and verify its checksum. +# Windows version of get-iso.sh; re-running resumes a partial download. +$ErrorActionPreference = 'Stop' +Set-Location $PSScriptRoot + +$mirror = if ($env:MIRROR) { $env:MIRROR } else { 'https://nl.mirror.cx/cachyos' } + +$page = (Invoke-WebRequest -UseBasicParsing https://cachyos.org/download/).Content +$m = [regex]::Match($page, 'ISO/desktop/[0-9]+/cachyos-desktop-linux-[0-9]+\.iso') +if (-not $m.Success) { throw 'Could not find the current ISO on cachyos.org/download' } +$url = "$mirror/$($m.Value)" +$name = Split-Path $m.Value -Leaf +Write-Host "Latest release: $name" + +if ((Test-Path cachyos.iso) -and ((Get-Content cachyos.iso.version -ErrorAction SilentlyContinue) -ne $name)) { + Write-Host 'Existing cachyos.iso is an older release, replacing it.' + Remove-Item cachyos.iso +} + +# curl.exe ships with Windows 10+ and supports resuming (-C -). +curl.exe -fL -C - -o cachyos.iso $url +if ($LASTEXITCODE -ne 0) { throw 'Download failed' } +Set-Content cachyos.iso.version $name + +Write-Host 'Verifying checksum...' +$expected = ((curl.exe -fsL "$url.sha256") -split '\s+')[0] +$actual = (Get-FileHash cachyos.iso -Algorithm SHA256).Hash +if ($actual -ieq $expected) { Write-Host 'cachyos.iso: OK' } else { throw "Checksum mismatch: $actual" } diff --git a/get-iso.sh b/get-iso.sh new file mode 100755 index 0000000..a5282a2 --- /dev/null +++ b/get-iso.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Download the latest CachyOS desktop ISO to cachyos.iso and verify its checksum. +# Re-running resumes a partial download; an up-to-date ISO is left alone. +set -euo pipefail +cd "$(dirname "$0")" + +mirror="${MIRROR:-https://nl.mirror.cx/cachyos}" + +# The download page embeds the current ISO URL; take the release from it. +release="$(curl -fsL https://cachyos.org/download/ | + grep -oE 'ISO/desktop/[0-9]+/cachyos-desktop-linux-[0-9]+\.iso' | head -n 1)" +if [[ -z "$release" ]]; then + echo "Could not find the current ISO on cachyos.org/download" >&2 + exit 1 +fi +url="$mirror/$release" +name="$(basename "$release")" +echo "Latest release: $name" + +if [[ -f cachyos.iso && "$(cat cachyos.iso.version 2>/dev/null)" != "$name" ]]; then + echo "Existing cachyos.iso is an older release, replacing it." + rm -f cachyos.iso +fi + +curl -fL -C - -o cachyos.iso "$url" +echo "$name" > cachyos.iso.version + +echo "Verifying checksum..." +curl -fsL "$url.sha256" | awk '{print $1" cachyos.iso"}' | sha256sum -c diff --git a/run.ps1 b/run.ps1 new file mode 100644 index 0000000..7a18a25 --- /dev/null +++ b/run.ps1 @@ -0,0 +1,29 @@ +# CachyOS test VM for setup-gamescope-boot.sh -- Windows version. +# .\run.ps1 [-Install] +# Needs QEMU for Windows (https://qemu.weilnetz.de/w64/) and the Windows +# feature "Windows Hypervisor Platform" (for -accel whpx). +# Windows QEMU has no 9p shared folders: copy the repo in over SSH instead: +# scp -P 2222 -r ..\..\projects\cachyos-gamescope-boot @localhost: +param([switch]$Install) +$ErrorActionPreference = 'Stop' +Set-Location $PSScriptRoot + +$qemuDir = if ($env:QEMU_DIR) { $env:QEMU_DIR } else { 'C:\Program Files\qemu' } +$qemu = Join-Path $qemuDir 'qemu-system-x86_64.exe' +$code = Join-Path $qemuDir 'share\edk2-x86_64-code.fd' + +if (-not (Test-Path vars.fd)) { Copy-Item (Join-Path $qemuDir 'share\edk2-i386-vars.fd') vars.fd } +if (-not (Test-Path disk.qcow2)) { & (Join-Path $qemuDir 'qemu-img.exe') create -f qcow2 disk.qcow2 60G | Out-Null } + +$qargs = @( + '-accel', 'whpx,kernel-irqchip=off', '-machine', 'q35', '-cpu', 'max', '-smp', '6', '-m', '8G', + '-drive', "if=pflash,format=raw,readonly=on,file=$code", + '-drive', 'if=pflash,format=raw,file=vars.fd', + '-drive', 'file=disk.qcow2,if=virtio', + '-device', 'virtio-vga,xres=1920,yres=1080', '-display', 'sdl', + '-device', 'qemu-xhci', '-device', 'usb-tablet', + '-nic', 'user,model=virtio-net-pci,hostfwd=tcp::2222-:22' +) +if ($Install) { $qargs += @('-cdrom', 'cachyos.iso', '-boot', 'd') } + +& $qemu @qargs diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..3a8abdf --- /dev/null +++ b/run.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# CachyOS test VM for setup-gamescope-boot.sh. +# [REPO=/path/to/cachyos-gamescope-boot] ./run.sh [install] [--nvidia] [--vulkan] [--fremont] +# install boot the installer ISO +# --nvidia render the guest's virtio-gpu (virgl) on the host NVIDIA dGPU +# --vulkan expose Vulkan to the guest (venus; needed by gamescope, can be unstable) +# --fremont report the Valve Steam Machine's DMI data (for testing the wizard) +# REPO defaults to $HOME/projects/cachyos-gamescope-boot. +# The repo is shared into the guest; mount it there with: +# sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt +# First-time SSH setup, inside the guest: +# sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh +# SSH from the host: ssh -p 2222 @localhost +cd "$(dirname "$0")" +repo="${REPO:-$HOME/projects/cachyos-gamescope-boot}" + +cdrom=() +smbios=() +gpu=virtio-vga-gl,xres=1920,yres=1080 +for arg in "$@"; do + case "$arg" in + install) cdrom=(-cdrom cachyos.iso -boot d) ;; + --nvidia) export __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia \ + __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/10_nvidia.json ;; + --vulkan) gpu+=,hostmem=4G,blob=true,venus=true ;; + --fremont) smbios=(-smbios type=1,manufacturer=Valve,product=Fremont -smbios type=2,manufacturer=Valve,product=Fremont) ;; + *) echo "unknown argument: $arg" >&2; exit 1 ;; + esac +done + +# First run: create the disk and the writable UEFI variable store. +[[ -f disk.qcow2 ]] || qemu-img create -f qcow2 disk.qcow2 60G +[[ -f vars.fd ]] || cp /usr/share/OVMF/OVMF_VARS_4M.fd vars.fd + +# Hand the host's public keys to the guest setup script. +cat ~/.ssh/*.pub > share/host-keys.pub + +exec qemu-system-x86_64 \ + -enable-kvm -machine q35,memory-backend=mem -cpu host -smp 6 -m 8G \ + -object memory-backend-memfd,id=mem,size=8G,share=on \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ + -drive if=pflash,format=raw,file=vars.fd \ + -drive file=disk.qcow2,if=virtio \ + -device "$gpu" -display gtk,gl=on,zoom-to-fit=off \ + -device qemu-xhci -device usb-tablet \ + -nic user,model=virtio-net-pci,hostfwd=tcp::2222-:22 \ + -virtfs local,path="$repo",mount_tag=repo,security_model=mapped-xattr \ + -virtfs local,path="$PWD/share",mount_tag=vmtools,security_model=mapped-xattr,readonly=on \ + "${smbios[@]}" "${cdrom[@]}" diff --git a/scripts/cmp.sh b/scripts/cmp.sh new file mode 100755 index 0000000..ab4976f --- /dev/null +++ b/scripts/cmp.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Diff the guest's KDE/GTK configs against a saved baseline. +# scripts/cmp.sh save copy the current ~/.config files to the baseline +# scripts/cmp.sh show differences against the baseline +# Env: VM_USER (theupriser), VM_PORT (2222), VM_HOST (localhost), +# BASELINE (guest dir, default ~/.cache/vm-baseline; not /tmp, which is cleared on reboot). +set -euo pipefail +. "$(dirname "$0")/common.sh" +mode="${1:-diff}" +# The guest login shell is fish, so pass values in through the bash script itself. +{ printf 'mode=%q base=%q\n' "$mode" "${BASELINE:-}"; cat << 'REMOTE' +base="${base:-$HOME/.cache/vm-baseline}" +files="kdeglobals plasmarc plasmashellrc kwinrc kwinrulesrc konsolerc kscreenlockerrc kglobalshortcutsrc gtk-3.0/settings.ini gtk-4.0/settings.ini plasma-org.kde.plasma.desktop-appletsrc" +if [ "$mode" = save ]; then + for f in $files; do mkdir -p "$(dirname "$base/config/$f")"; cp -f ~/.config/"$f" "$base/config/$f" 2>/dev/null || true; done + echo "baseline saved to $base"; exit 0 +fi +noise='^(ColorSchemeHash|popupHeight|popupWidth|ItemGeometries|ItemGeometriesHorizontal|DialogHeight|DialogWidth|[0-9]+ screens: )' +for f in $files; do + d=$(diff <(sort -u "$base/config/$f" 2>/dev/null | grep -vE "$noise") <(sort -u ~/.config/"$f" 2>/dev/null | grep -vE "$noise") | grep '^[<>]' || true) + [ -n "$d" ] && { echo "== $f"; echo "$d" | head -25; } +done +echo "(end of diff)" +REMOTE +} | vm_ssh bash -s 2>&1 diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100644 index 0000000..4f2dbf7 --- /dev/null +++ b/scripts/common.sh @@ -0,0 +1,5 @@ +# Sourced by the other scripts: SSH target of the test VM. +VM_USER="${VM_USER:-theupriser}" +VM_PORT="${VM_PORT:-2222}" +VM_HOST="${VM_HOST:-localhost}" +vm_ssh() { ssh -p "$VM_PORT" -o BatchMode=yes "$VM_USER@$VM_HOST" "$@"; } diff --git a/scripts/vmrun.sh b/scripts/vmrun.sh new file mode 100755 index 0000000..5e8f37f --- /dev/null +++ b/scripts/vmrun.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Run the wizard inside the guest's Plasma session with scripted menu input. +# scripts/vmrun.sh '' e.g. scripts/vmrun.sh '\ny\nn\n' +# Input is interpreted by printf: numbers toggle, empty line continues, +# a = re-apply, q = quit; then y for "Go ahead?" and n for the restart question. +# Env: VM_USER (theupriser), VM_PORT (2222), VM_HOST (localhost), +# GUEST_REPO (/mnt, where the repo is mounted in the guest), GUEST_UID (1000). +set -euo pipefail +. "$(dirname "$0")/common.sh" +input="${1:?usage: $0 ''}" +GUEST_REPO="${GUEST_REPO:-/mnt}" +GUEST_UID="${GUEST_UID:-1000}" +# The guest login shell is fish, so pass values in through the bash script itself. +{ printf 'input=%q repo=%q uid=%q\n' "$input" "$GUEST_REPO" "$GUEST_UID"; cat << 'REMOTE' +export XDG_RUNTIME_DIR=/run/user/$uid WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus +log=/tmp/wizard.log +# shellcheck disable=SC2059 +printf "$input" | "$repo/setup-gamescope-boot.sh" > "$log" 2>&1; echo "exit=$?" >> "$log" +cat "$log" +REMOTE +} | vm_ssh bash -s 2>&1 | sed 's/\x1b\[[0-9;]*m//g' diff --git a/scripts/vmstate.sh b/scripts/vmstate.sh new file mode 100755 index 0000000..3e7e271 --- /dev/null +++ b/scripts/vmstate.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Print the state of every wizard component in the guest. +# Env: VM_USER (theupriser), VM_PORT (2222), VM_HOST (localhost). +set -euo pipefail +. "$(dirname "$0")/common.sh" +vm_ssh bash -s << 'REMOTE' 2>&1 +yn() { [ -e "$1" ] && echo y || echo n; } +echo "DM=$(systemctl show -p Id --value display-manager) sddm-autologin=$(yn /etc/sddm.conf.d/10-gamescope-autologin.conf) sync=$(yn /etc/systemd/system/sync-steamos-session.path) sudoers=$(sudo test -f /etc/sudoers.d/gamescope-session-switch && echo y || echo n)" +echo "plasmalogin [Autologin]: $(sed -n '/^\[Autologin\]/,/^\[/p' /etc/plasmalogin.conf | grep -v '^\[' | tr '\n' ' ')" +echo "shortcut=$(grep -h Exec= ~/Desktop/*Gaming*.desktop 2>/dev/null || echo none)" +echo "steam-unit=$([ -f ~/.config/systemd/user/steam-desktop-autostart.service ] && systemctl --user is-enabled steam-desktop-autostart.service || echo none) glyphs=$(yn ~/.config/environment.d/99-gamescope-steam-glyphs.conf)" +echo "lnf=$(kreadconfig6 --file kdeglobals --group KDE --key LookAndFeelPackage) scheme=$(kreadconfig6 --file kdeglobals --group General --key ColorScheme) gtk=$(kreadconfig6 --file ~/.config/gtk-3.0/settings.ini --group Settings --key gtk-theme-name) font=$(kreadconfig6 --file kdeglobals --group General --key font)" +echo "lock_screen=$(kreadconfig6 --file kdeglobals --group 'KDE Action Restrictions' --key action/lock_screen) autolock=$(kreadconfig6 --file kscreenlockerrc --group Daemon --key Autolock) metaL=$(kreadconfig6 --file kglobalshortcutsrc --group ksmserver --key 'Lock Session' | cut -c1-12)" +echo "panel: $(grep -A1 '^\[PlasmaViews\]\[Panel [0-9]*\]$' ~/.config/plasmashellrc | grep floating | tr '\n' ' ') $(grep -h 'thickness' ~/.config/plasmashellrc | tr '\n' ' ')" +echo "kickoff: $(grep -E '^(primaryActions|systemFavorites|icon)=' ~/.config/plasma-org.kde.plasma.desktop-appletsrc | tr '\n' ' ')" +echo "leds-pkg=$(pacman -Q leds-valve-dkms-git 2>/dev/null || echo none) steamos-manager=$(pacman -Q steamos-manager 2>/dev/null || echo none)/$(systemctl is-active steamos-manager 2>/dev/null) udev=$(yn /etc/udev/rules.d/70-valve-leds-user.rules) module=$(lsmod | grep -c leds_valve) leds=$(ls /sys/class/leds | grep -c valve)" +echo "leds-owner=$(stat -c %U /sys/class/leds/valve-leds*/brightness 2>/dev/null | sort -u | tr '\n' ' ')" +j=~/.local/state/cachyos-gamescope-boot +echo "journals: $(ls "$j" 2>/dev/null | tr '\n' ' ')" +for f in "$j"/*; do [ -f "$f" ] || continue; d=$(cut -f1-3 "$f" | sort | uniq -d); [ -n "$d" ] && echo "DUPLICATES in $(basename "$f"): $d"; done +true +REMOTE diff --git a/share/guest-ssh-setup.sh b/share/guest-ssh-setup.sh new file mode 100755 index 0000000..f8bb165 --- /dev/null +++ b/share/guest-ssh-setup.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Run INSIDE the VM (as your normal user) to make it reachable from the host +# over SSH with key auth and passwordless sudo: +# sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh +set -euo pipefail +here="$(cd "$(dirname "$0")" && pwd)" + +sudo pacman -S --needed --noconfirm openssh +sudo systemctl enable --now sshd + +# Open port 22 if a firewall is active. +if command -v ufw >/dev/null && sudo ufw status | grep -q 'Status: active'; then + sudo ufw allow ssh +fi +if command -v firewall-cmd >/dev/null && sudo firewall-cmd --state >/dev/null 2>&1; then + sudo firewall-cmd --permanent --add-service=ssh && sudo firewall-cmd --reload +fi + +# Authorize the host's key(s), without duplicates. +install -d -m 700 ~/.ssh +touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys +while IFS= read -r key; do + [[ -n "$key" ]] && ! grep -qxF "$key" ~/.ssh/authorized_keys && echo "$key" >> ~/.ssh/authorized_keys +done < <(cat "$here/host-keys.pub" 2>/dev/null) + +# Test VM only: passwordless sudo so the host can drive it non-interactively. +echo "$USER ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/99-test-vm >/dev/null +sudo chmod 440 /etc/sudoers.d/99-test-vm + +echo "Done. From the host: ssh -p 2222 $USER@localhost" +[[ -s "$here/host-keys.pub" ]] || echo "No host keys were shared; add yours from the host (see add-ssh-key.ps1 on Windows)."