mirror of
https://github.com/theupriser/steamify-cachyos-dev.git
synced 2026-10-05 12:45:56 +02:00
Compare commits
105
Commits
d041d452ca
...
737882c213
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
737882c213 | ||
|
|
0e63b931ea | ||
|
|
3bcbdf3d98 | ||
|
|
9dbf05ab9c | ||
|
|
86a44d7e68 | ||
|
|
d540dd2310 | ||
|
|
f7625f5882 | ||
|
|
b4d6c23129 | ||
|
|
7a313a96ee | ||
|
|
9e8e05e99b | ||
|
|
b95ccb686d | ||
|
|
dabc43ded6 | ||
|
|
a253ff15cd | ||
|
|
c8a6a2cef5 | ||
|
|
1aff67612d | ||
|
|
b2fc992745 | ||
|
|
90fe42da5f | ||
|
|
5146dd1109 | ||
|
|
b840eb3697 | ||
|
|
5ea436efc5 | ||
|
|
f33d3bf491 | ||
|
|
32db035e9b | ||
|
|
cfba222439 | ||
|
|
76b162d7e1 | ||
|
|
da263529e9 | ||
|
|
f604e10b3e | ||
|
|
19ea677cf7 | ||
|
|
ca0cf43919 | ||
|
|
7b7715ab07 | ||
|
|
8ef247e596 | ||
|
|
1db4693e3c | ||
|
|
6190abadfc | ||
|
|
4cb253b41c | ||
|
|
7b0147f065 | ||
|
|
f35df9c4d3 | ||
|
|
85657a3183 | ||
|
|
92bf3f0b19 | ||
|
|
b07ff5f99b | ||
|
|
2daf83b013 | ||
|
|
d8c7670c53 | ||
|
|
58b233c20b | ||
|
|
b6e801664a | ||
|
|
fa01c19ba1 | ||
|
|
f356cc3f74 | ||
|
|
0824e78249 | ||
|
|
48edfc53c5 | ||
|
|
36bd85cce6 | ||
|
|
4670b296dc | ||
|
|
b6082c0322 | ||
|
|
ebf9e3c267 | ||
|
|
ade5566174 | ||
|
|
6729d61ea8 | ||
|
|
5f97f1334c | ||
|
|
c6578103a3 | ||
|
|
97f68a8065 | ||
|
|
3a0d9f98d8 | ||
|
|
fd1b916023 | ||
|
|
b6a7b30092 | ||
|
|
314bac87e7 | ||
|
|
dea5f9d715 | ||
|
|
a576b734c7 | ||
|
|
105ac638ee | ||
|
|
d231aec125 | ||
|
|
5b3e246629 | ||
|
|
f24ff3927e | ||
|
|
b895e28181 | ||
|
|
0a12c0e4c6 | ||
|
|
fcffceb941 | ||
|
|
ea1207580f | ||
|
|
38a50ab57d | ||
|
|
ed12ecb57d | ||
|
|
8ea75424b0 | ||
|
|
e435577b5c | ||
|
|
104d1dac44 | ||
|
|
8b0c168711 | ||
|
|
d0c551d23a | ||
|
|
728f329ebc | ||
|
|
332fdca4c2 | ||
|
|
92c1e58f55 | ||
|
|
36d1e6d326 | ||
|
|
c5ac00faf6 | ||
|
|
107f8f4076 | ||
|
|
1c1bc55a62 | ||
|
|
6df107d578 | ||
|
|
6840044038 | ||
|
|
32499b1c01 | ||
|
|
b1e9a273a3 | ||
|
|
9706066269 | ||
|
|
7a0a216327 | ||
|
|
4adc018654 | ||
|
|
ac8f77cf4a | ||
|
|
0f57d86bae | ||
|
|
46e039809e | ||
|
|
379f1c7f7f | ||
|
|
471332e20f | ||
|
|
8ce2917c0f | ||
|
|
9a4653b6d3 | ||
|
|
3b7b685667 | ||
|
|
7a0443e131 | ||
|
|
8d5a659c89 | ||
|
|
6d992c83c7 | ||
|
|
974cea7cf4 | ||
|
|
19b6ba4d97 | ||
|
|
788d345a9d | ||
|
|
10357b2d52 |
No files matched your search
@@ -9,14 +9,25 @@ The wizard (`steamify.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. Steamify shortcut (desktop icon + launcher entry that `curl | bash` the newest release; its gear icon is a release asset)
|
||||
6. 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, powerdevilrc)
|
||||
7. Update BIOS (only on Fremont, an opt-in action, never pre-ticked; tickable only when Valve has a newer BIOS)
|
||||
2. └ Boot into: gamescope / desktop (sub-option of 1, shown while 1 is ticked; Left/Right or the number switches it)
|
||||
3. SteamOS theme (installs `cachyos-vapor` and applies the Vapor global theme with its desktop and window layout; needs a running Plasma session)
|
||||
4. Steam Deck/Machine icons (`STEAM_GAMEPADUI_ARGS -steamos3`)
|
||||
5. Single user mode (SDDM, no lock screen/user switching/log out, Valve's empty KDE wallet; enabling it enables 1, disabling 1 disables it)
|
||||
6. Steamify shortcut (desktop icon + launcher entry that `curl | bash` the newest release; its gear icon is a release asset)
|
||||
7. HDMI-CEC (any PC; pre-ticked on a first run only on Fremont: Valve's cecd/cec-audio-control/inputattach-cec-units from the holo repo, the steam-launcher drop-in that overrides `STEAM_ENABLE_CEC=0`; see "Fake HDMI-CEC")
|
||||
8. 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, powerdevilrc)
|
||||
9. └ Power-off fix (sub-option of 8, ticked along with it, opt-out: DKMS module `steamify-fremont-poweroff` for every kernel; in the VM it loads but finds no real GPIO wake bit to clear)
|
||||
10. └ Update BIOS (only on Fremont, an opt-in action, never pre-ticked; tickable only when Valve has a newer BIOS)
|
||||
|
||||
Without `--fremont` the menu has only 1-5.
|
||||
Since 2.2.0 "Pin the kernel" and "HDMI refresh boost" are only shown while
|
||||
something of them is left, unticked, so a normal run removes them. The menu
|
||||
marks components a normal run will update with "(update)" (feature versions
|
||||
in `~/.local/state/cachyos-gamescope-boot/features.state`; set an entry to an
|
||||
older version, e.g. `machine=2.1.0`, to test the update flow in the menu and
|
||||
the app).
|
||||
|
||||
Without `--fremont` the menu has only 1-7. Numbers shift when a parent is
|
||||
unticked (its sub-option is hidden), so read the ticks with `'q\n'` first.
|
||||
|
||||
Undo journals live in `~/.local/state/cachyos-gamescope-boot/` in the guest.
|
||||
|
||||
@@ -29,13 +40,81 @@ The VM's disk, `vars*.fd` and `run.sh` copy may live outside the repo
|
||||
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`.
|
||||
|
||||
## Always let the user watch
|
||||
|
||||
The user follows the tests in the VM's window (on the Steam Machine, over
|
||||
Moonlight). **Every run is visible**: run the wizard and every other test
|
||||
command in the tmux-backed Konsole on the VM's desktop ("Visible terminal"
|
||||
below), never hidden over SSH. When a run was started hidden anyway, open a
|
||||
Konsole that follows its log (`tail -n +1 -f <log>`) right away. **Show the
|
||||
app too**: start it on the VM's desktop (`systemd-run --user
|
||||
/mnt/ui/steamify-ui`) for the screens you test, and screenshot it.
|
||||
Long unattended jobs (installs, `vmtest.sh`): report them as the
|
||||
`progress-report` skill says.
|
||||
|
||||
## The VM on the Steam Machine
|
||||
|
||||
The Mac has no KVM, so the VM runs on the Steam Machine
|
||||
(`~/projects/steamify-cachyos-dev`, `REPO=~/projects/steamify-cachyos`; see
|
||||
the steam-machine-testing skill). From the Mac:
|
||||
|
||||
- Connect with agent forwarding (`ssh -A steammachine`), then
|
||||
`ssh -p 2222 -o UserKnownHostsFile=~/projects/steamify-cachyos-dev/known_hosts theupriser@localhost`.
|
||||
The Steam Machine has no key of its own: `share/host-keys.pub` holds the
|
||||
Mac's key (`ssh-add -L`), and the VM's host key stays in `~/projects`, not
|
||||
`~/.ssh`.
|
||||
- Inner `ssh` in a `bash -s` heredoc reads the rest of the heredoc as its
|
||||
stdin: use `ssh -n` for single commands, or the rest of the script silently
|
||||
never runs.
|
||||
- Start the VM as a user unit so it survives the SSH session:
|
||||
`systemd-run --user --collect -u steamify-vm --working-directory=$PWD --setenv=REPO=$HOME/projects/steamify-cachyos ./run.sh --fremont`.
|
||||
- A fresh install has no `/media`: the guest setup is
|
||||
`sudo mkdir -p /media && sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh`.
|
||||
|
||||
## No VM yet
|
||||
|
||||
Create one with `scripts/vminstall.sh` (the vm-install skill): unattended,
|
||||
ends with the snapshots `clean` and `ssh-ready`. Pass its `VM_DIR` to every
|
||||
script. Login by hand: your host username, password `steamify`.
|
||||
|
||||
## Test plan
|
||||
|
||||
`TESTPLAN.md` (repo root) lists what to test per release (regression,
|
||||
features, Steam Machine only) and logs every run. Follow it, add a section
|
||||
for every new feature, and add a line to its results log after each run.
|
||||
|
||||
## Start of every session: update the snapshot first
|
||||
|
||||
A snapshot's package databases age quickly: the mirrors drop the versions it
|
||||
knows (404s, then "signature is invalid" on partial downloads) and the
|
||||
conversion's package install fails, which looks like a wizard bug. So once
|
||||
per session, before any test, bring `ssh-ready` up to date and overwrite it
|
||||
(visibly, in a Konsole on the VM's desktop):
|
||||
|
||||
```bash
|
||||
scripts/vmreset.sh --fremont
|
||||
# in the guest (Konsole): keyrings first, then everything else
|
||||
sudo pacman -Sy --noconfirm archlinux-keyring cachyos-keyring
|
||||
sudo pacman -Su --noconfirm --needed tmux shellcheck
|
||||
sudo pacman -Scc --noconfirm
|
||||
sudo systemctl poweroff
|
||||
# on the host, in the VM dir, once QEMU is gone:
|
||||
qemu-img snapshot -d ssh-ready disk.qcow2
|
||||
qemu-img snapshot -c ssh-ready disk.qcow2 && cp vars.fd vars.ssh-ready.fd
|
||||
```
|
||||
|
||||
Keep `/etc/plasmalogin.conf.d/00-test-autologin.conf` in the snapshot: the
|
||||
VM then logs in at boot. After a plasmalogin update, restarting it from a
|
||||
booted greeter fails (`HELPER_TTY_ERROR`, start-limit-hit), so `vmreset.sh`
|
||||
only restarts it when Plasma isn't up yet.
|
||||
|
||||
## Quick start (the usual loop)
|
||||
|
||||
```bash
|
||||
scripts/vmreset.sh --fremont # restore ssh-ready, boot, mount repo, autologin, wait for Plasma
|
||||
# (also skips the broken krfoss mirror, installs shellcheck)
|
||||
scripts/cmp.sh save # baseline of the KDE configs
|
||||
scripts/vmwatch.sh '1\n3\n\ny\n' "Theme only" # wizard in a visible Konsole in the VM
|
||||
scripts/vmwatch.sh 'q\n' "Menu" # wizard in a visible Konsole in the VM (read the ticks first)
|
||||
scripts/vmwatch.sh --release '\ny\nm\nq\n' "Full run" # the newest GitHub release instead of /mnt
|
||||
scripts/vmstate.sh # component state
|
||||
scripts/vmshot.sh --clean /path/shot.png # screenshot (--clean: close Steam/Hello/Konsole first)
|
||||
@@ -58,7 +137,7 @@ window: `vmrun.sh` runs invisibly over SSH, so they see nothing happen.
|
||||
Start in the background:
|
||||
|
||||
```bash
|
||||
./run.sh --fremont > /tmp/vm.log 2>&1 & # flags: [install] [--fremont] [--nvidia] [--vulkan]
|
||||
./run.sh --fremont > /tmp/vm.log 2>&1 & # flags: [install] [--fremont] [--nvidia] [--vulkan [--amd]]
|
||||
```
|
||||
|
||||
Wait for SSH:
|
||||
@@ -86,7 +165,8 @@ qemu-img snapshot -c my-state disk.qcow2 && cp vars.fd vars.my-state.fd #
|
||||
```
|
||||
|
||||
Existing snapshots: `clean` (fresh install) and `ssh-ready` (sshd + host key +
|
||||
passwordless sudo). Reset to `ssh-ready` before each full test run.
|
||||
passwordless sudo, test autologin; brought up to date at the start of each
|
||||
session, see above). Reset to `ssh-ready` before each full test run.
|
||||
|
||||
## First-time guest setup
|
||||
|
||||
@@ -127,6 +207,46 @@ echo "$HOME"
|
||||
EOF
|
||||
```
|
||||
|
||||
## Visible terminal (let the user watch)
|
||||
|
||||
When the user watches the VM's window, run commands in a Konsole window on
|
||||
the guest's desktop instead of hidden over SSH: they see every command and
|
||||
its output. A tmux session makes that window scriptable (needs a Plasma
|
||||
session; `sudo pacman -S --needed tmux` once, or put it in the snapshot).
|
||||
|
||||
```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
|
||||
tmux kill-session -t claude 2>/dev/null
|
||||
# Plain bash, not fish: fish rejects bash syntax ($?) and swallows Enter
|
||||
# keys sent while it's still drawing its prompt.
|
||||
tmux new-session -d -s claude -x 200 -y 50 'bash --norc --noprofile -i'; sleep 1
|
||||
tmux send-keys -t claude "PS1='[claude] \\w\\$ '; clear" Enter
|
||||
systemd-run --user -q --setenv=XDG_CURRENT_DESKTOP=KDE konsole --separate -e tmux attach -t claude
|
||||
EOF
|
||||
```
|
||||
|
||||
Run a command and wait for it: end it with a marker, then poll the pane.
|
||||
|
||||
```bash
|
||||
ssh -p 2222 -o BatchMode=yes theupriser@localhost bash -s << 'EOF'
|
||||
tmux send-keys -t claude "clear; cd /mnt; printf 'q\\n' | ./steamify.sh; echo '== EXIT'" Enter
|
||||
for i in $(seq 200); do tmux capture-pane -p -t claude -S -400 | grep -q '^== EXIT' && break; sleep 3; done
|
||||
tmux capture-pane -p -t claude -S -400 | grep -v '^\s*$' | tail -30
|
||||
EOF
|
||||
```
|
||||
|
||||
- `clear` first, or an old marker still in the pane ends the wait at once;
|
||||
with several runs, count the markers (`grep -c`).
|
||||
- Longer scripts: write them to a file in the heredoc (not `/tmp` if a
|
||||
reboot comes in between), then `tmux send-keys -t claude 'clear; bash <file>' Enter`.
|
||||
- For long runs (packages, yay, DKMS), poll from a `run_in_background` Bash
|
||||
call and report when it finishes.
|
||||
- A reboot or snapshot restore ends the session and the window: set it up
|
||||
again before sending anything, or `send-keys` fails
|
||||
(`error connecting to /tmp/tmux-1000/default`) while a wait loop spins.
|
||||
- `vmshot.sh --clean` closes Konsole windows, including this one.
|
||||
|
||||
## Getting a Plasma session
|
||||
|
||||
On a fresh snapshot the plasmalogin greeter is shown. Enable test autologin:
|
||||
@@ -163,19 +283,22 @@ line at the restart question.
|
||||
|
||||
| Input | Meaning |
|
||||
|---|---|
|
||||
| `'\ny\nm\nq\n'` | first run, accept all (not the BIOS item), back to the menu, quit |
|
||||
| `'2\n\ny\n'` | toggle the theme |
|
||||
| `'\ny\nm\nq\n'` | first run, accept what's pre-ticked (not Boot into desktop, not BIOS; HDMI-CEC only on Fremont), back to the menu, quit |
|
||||
| `'3\n\ny\n'` | toggle the theme (while 1 is ticked) |
|
||||
| `'7\n\ny\nm\nq\nn\n'` | toggle HDMI-CEC (while 1 is ticked) |
|
||||
| `'a\ny\nm\nq\n'` | re-apply what is on (includes the conversion, so a restart is pending) |
|
||||
| `'q\n'` | just show the menu |
|
||||
| `'1\n3\n5\n\ny\n'` | from all-off: theme only (with `--fremont` also add `6\n` to drop Steam Machine support) |
|
||||
| `'1\n\ny\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.
|
||||
as a first run and pre-ticks the components (not Boot into desktop, BIOS, or
|
||||
HDMI-CEC off Fremont). Otherwise the ticks show what is on now. Unticking 1
|
||||
also unticks single user mode (5) and **hides** the Boot into row (2), so
|
||||
every later number moves up one; the same for Steam Machine support (8) and
|
||||
its kernel pin row (9). Ticking 5 ticks 1 again. The numbers apply to the
|
||||
menu as it is when you type them, one line at a time. 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
|
||||
@@ -183,6 +306,20 @@ gamescope, so set it back to plasma before the next reboot (below).
|
||||
|
||||
## Gamescope does not render in this VM
|
||||
|
||||
|
||||
Everything was tried (2026-09-25); don't spend time on it again:
|
||||
- virgl (default): no Vulkan, black.
|
||||
- `--vulkan --amd` (venus on the host 780M, RADV): gamescope and Steam run,
|
||||
but virtio-gpu rejects every framebuffer gamescope makes ("Cannot import FB
|
||||
to DRM ... not supported for scan-out", AR24 and XR24, also with
|
||||
`--force-composition --disable-layers`): black. KWin crashes on venus
|
||||
("Illegal command buffer"), so Plasma freezes too.
|
||||
- nested gamescope with lavapipe (`vulkan-swrast`): refuses to start, lavapipe
|
||||
lacks VK_KHR_present_id/present_wait.
|
||||
- Other hypervisors (VirtualBox, VMware) have no Vulkan at all.
|
||||
Check gaming mode (Steam's settings, HDMI-CEC section, refresh rates) on the
|
||||
real Steam Machine. The VM covers everything else.
|
||||
|
||||
(Also after any re-apply that includes the conversion.)
|
||||
|
||||
No suitable Vulkan (venus is unstable with the host NVIDIA driver). After
|
||||
@@ -242,13 +379,20 @@ autologin, then (all passed last run; `scripts/vmstate.sh` after every step):
|
||||
|
||||
1. Fresh run turning everything on (`'\ny\nm\nq\n'`), set session to plasma, reboot:
|
||||
SDDM logs straight in, Vapor desktop, three desktop icons (Return to Gaming
|
||||
Mode, Steam, Steamify CachyOS with the gear icon), LEDs loaded (17 nodes).
|
||||
Mode, Steam, Steamify CachyOS with the gear icon), LEDs loaded (17 nodes),
|
||||
`uname -r` 7.1.6-1-cachyos and `pacman -Qu` shows linux-cachyos `[ignored]`,
|
||||
`~/.local/share/kwalletd/kdewallet.kwl.bak-steamify` next to Valve's empty wallet,
|
||||
`steamos-manager-configure-cecd` enabled and `~/.config/cecd/config.d/00-steamos-manager.toml` written.
|
||||
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.
|
||||
7. Icons + Steam Machine support off: driver, udev rule, modules-load, steamos-manager removed; yay kept;
|
||||
kernel pin removed (`IgnorePkg` empty, `pacman -Syu` back to the current kernel), files kept in
|
||||
`/var/cache/steamify/kernel`. On again (even with the CachyOS servers blocked in
|
||||
`/etc/hosts`): installs 7.1.6 from that directory; DKMS builds the LED driver once per kernel.
|
||||
7c. HDMI-CEC: load vivid, then `scripts/vmcec.sh` (all PASS), off/on via the menu.
|
||||
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
|
||||
@@ -264,12 +408,55 @@ differs from Valve's newest. Fake an older one with
|
||||
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 '7\n\ny\ny\nUPDATE\nm\nq\nn\n' | WIZARD_BIOS_DRY_RUN=1 /mnt/steamify.sh`
|
||||
(BIOS is item 7 with `--fremont`; the final `n` answers the restart question).
|
||||
`printf '10\n\ny\ny\nUPDATE\nm\nq\nn\n' | WIZARD_BIOS_DRY_RUN=1 /mnt/steamify.sh`
|
||||
(BIOS is item 10 with `--fremont`; the final `n` answers the restart question).
|
||||
|
||||
A successful dry run counts as staged, so `[m]`/`[r]` and the restart question
|
||||
on `q` show up too; in dry-run mode "restart" only prints.
|
||||
|
||||
## Fake HDMI-CEC (vivid): `scripts/vmcec.sh`
|
||||
|
||||
The VM has no CEC hardware; the kernel's `vivid` test driver emulates it: a
|
||||
virtual HDMI input (`/dev/cec0`, plays the TV via `cec-ctl`/`cec-follower`)
|
||||
and output (`/dev/cec1`, the PC, where cecd runs). Needs the HDMI-CEC item on;
|
||||
the Steam-settings checks need Steam Machine support (steamos-manager).
|
||||
|
||||
```bash
|
||||
scripts/vmcec.sh # all checks + a real suspend/resume (~5 min)
|
||||
scripts/vmcec.sh --no-sleep # without suspending the VM
|
||||
scripts/vmcec.sh --sleep-only # only the suspend/resume check
|
||||
scripts/vmwake.sh # wake a sleeping VM (QMP socket from run.sh)
|
||||
```
|
||||
|
||||
It prints PASS/FAIL/SKIP for: identity (address 1.0.0.0, name "Steam
|
||||
Machine", Valve vendor ID), every TV remote key and the key it becomes, the
|
||||
Steam settings over D-Bus (remote control, WakeTv, SuspendTv, SuspendDevice
|
||||
and cecd's config), PC to TV (make active, wake, volume, audio status,
|
||||
standby), TV standby putting the PC to sleep (blocked by an inhibitor), and
|
||||
sleep/wake turning the TV off and on.
|
||||
|
||||
Gotchas it handles, learned the hard way:
|
||||
- The cecd service (`-e`) grabs every CEC device, the fake TV too: the test
|
||||
uses a temporary drop-in (`~/.config/systemd/user/cecd.service.d/vmcec.conf`)
|
||||
with `-d /dev/cec1`, removed at the end.
|
||||
- The TV's power button (KEY_POWER) and TV standby with SuspendDevice on
|
||||
really suspend the VM (Steam Machine support makes the power button sleep):
|
||||
block with `sudo systemd-inhibit --what=sleep:handle-power-key` (as user it's
|
||||
refused). If the VM sleeps anyway: `scripts/vmwake.sh` (the QEMU window's
|
||||
keyboard doesn't wake it; a VM started before run.sh had `-qmp` needs a hard restart).
|
||||
- `rtcwake` skips logind, so cecd never sees the sleep: suspend with
|
||||
`systemctl suspend` and wake over QMP.
|
||||
- `steamosctl get/set-hdmi-cec-suspend-tv|suspend-device` don't work
|
||||
(steamos-manager 26.4.1); Steam uses the D-Bus properties of
|
||||
`com.steampowered.SteamOSManager1.HdmiCec2`, and so does the test.
|
||||
- The kernel answers "give OSD name" itself, even without cecd: it's no proof
|
||||
cecd runs. cecd logs "Putting TV in standby" only at `RUST_LOG=debug`.
|
||||
- CecDevice1 methods take arguments (`VolumeUp y 0`, Daemon1 `Standby b true`).
|
||||
- After the test (or a hard restart) CachyOS may put autologin back on
|
||||
gamescope: set the session to plasma and restart the display manager.
|
||||
|
||||
The menu item is 7 (8 = Steam Machine support, 9 = kernel pin, 10 = BIOS with `--fremont`).
|
||||
|
||||
## Steamify shortcut
|
||||
|
||||
Test a double-click with `systemd-run --user kioclient exec
|
||||
@@ -301,6 +488,24 @@ them with `systemd-run --user <app>`.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- To see the plan without applying it, answer "n" at "Go ahead?"
|
||||
(`'\nn\nq\n'`); a "y" applies it. Never wrap a run that may call pacman in
|
||||
`timeout`: killing pacman after its transaction but before its hooks leaves
|
||||
a stale boot entry and `db.lck` (restore the snapshot, or remove the lock
|
||||
and reinstall the same packages so the hooks run).
|
||||
|
||||
- After a reboot or hard restart the autologin may be back on gamescope
|
||||
(black screen): `sudo /usr/lib/steamos/steam-set-session plasma.desktop &&
|
||||
sudo systemctl restart display-manager`.
|
||||
- `vmreset.sh` sometimes stops at "Plasma did not start" (the greeter stays up
|
||||
although the test autologin file is there): restart the display manager.
|
||||
- `pkill -f '<pattern>'` from the host shell also matches the command running
|
||||
it and kills it (exit 144): match something narrower, or kill by PID.
|
||||
- Counting sudo password prompts (the VM has NOPASSWD): move
|
||||
`/etc/sudoers.d/99-test-vm` aside, give the user a password, run the wizard
|
||||
in a Python `pty` that answers `[sudo] password` and logs each prompt, then
|
||||
put the file back. `makepkg -i` runs `sudo -k` and always asks again.
|
||||
|
||||
- Broken mirror: `mirror5.krfoss.org` served a bad `.sig` ("Maximum file size
|
||||
exceeded"), failing the conversion's package install. `vmreset.sh` comments
|
||||
it out; by hand: `sudo sed -i '/krfoss/s/^Server/#Server/' /etc/pacman.d/*mirrorlist*`.
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: progress-report
|
||||
description: Use whenever a long VM job runs for the user (vminstall.sh, vmbootloadertest.sh, vmsuite.sh, vmtest.sh, an ISO build), locally or on the WSL PC, and the user wants to follow it - how to report progress (a table with a bar per VM and a total, only when something changes), show the log in the user's terminal and the VM's screen on their desktop, and look into a job that seems stuck.
|
||||
---
|
||||
|
||||
# Progress report for VM jobs
|
||||
|
||||
What the user asked for (2026-09-29): see what is going on, without noise.
|
||||
|
||||
## The rules
|
||||
|
||||
- **Report only on a change**: an install stage, a test step, a job finishing, a FAIL. Never a
|
||||
timed "no change" message. One monitor, not polling from the conversation.
|
||||
- **Every report is the same table**, one row per VM/job plus a total row:
|
||||
|
||||
| VM | Step | Checks | Progress |
|
||||
|---|---|---|---|
|
||||
| grub | ✅ done | 40 / 40, 0 failed | `████████████████████` 100% |
|
||||
| systemd-boot | test: kernel update | 27 / 40, 0 failed | `█████████████░░░░░░░` 68% |
|
||||
| limine | install: desktop environment (40%) | 0 / 41 | `░░░░░░░░░░░░░░░░░░░░` 0% |
|
||||
| **Total** | | **67 / 121**, 0 failed | `███████████░░░░░░░░░` 55% |
|
||||
|
||||
Bar: 20 cells, `█` per 5% of checks passed out of the expected count. Step in plain words
|
||||
("install: base system (9%)", "test: reboot into the other kernel"), ✅ when done, ❌ with the
|
||||
failed check's name when something fails. Under the table at most two lines: what is odd or next.
|
||||
- **A FAIL or a job that stops moving is reported at once**, with the cause looked up first (below),
|
||||
not only the numbers.
|
||||
|
||||
## The monitor
|
||||
|
||||
`scripts/vmprogress.sh [job...]` (jobs: limine systemd-boot grub cli menu hw installer toggles)
|
||||
prints one line only when something changes and `ALLDONE` at the end; it reads the latest run in
|
||||
`~/vms/bl-<loader>.test.log` / `~/vms/run-<suite>.log` and the install percentage from
|
||||
`$VM_DIR/install-www/install.log`. Expected check counts live at its top: update them when a
|
||||
suite gains checks. `--once` prints the current state.
|
||||
|
||||
On the WSL PC, from the laptop:
|
||||
|
||||
```
|
||||
Monitor(command: "ssh wsl '~/projects/steamify-cachyos-dev/scripts/vmprogress.sh grub systemd-boot limine'",
|
||||
description: "boot loader tests: only on change", timeout_ms: 1800000)
|
||||
```
|
||||
|
||||
Re-arm it when it expires while jobs still run. Turn each event into the table above.
|
||||
|
||||
Stopping a background command or monitor on the laptop (TaskStop) does not stop what it started on
|
||||
the PC: its remote loop keeps running (`pkill -f` it there). A leftover `until vm_ssh ...` loop
|
||||
against a VM that gets reinstalled saves the live ISO's host key in `$VM_DIR/known_hosts`, and the
|
||||
install then hangs at its first-boot ssh check ("REMOTE HOST IDENTIFICATION HAS CHANGED"). Never
|
||||
ssh to a VM with its `VM_DIR` settings while it installs; fix it with `ssh-keygen -R "[localhost]:<port>" -f $VM_DIR/known_hosts`.
|
||||
|
||||
Monitor rules learned on 2026-09-29: **one monitor per job, stop the old one** (`TaskStop`) when a new run
|
||||
starts, or every run is reported twice; a job that ends by itself (the ISO build on Gitea, `vmtest.sh`) is
|
||||
best watched with a loop that exits on its terminal states and prints only *changes* (a new step, a finished
|
||||
job, a FAIL, a new HTTP status), never on a percentage or a PASS count alone. For a Gitea Actions run, poll its
|
||||
public log (`.../actions/runs/<run>/jobs/<job>/logs`, see `steamify-iso-release`) and grep for the step names.
|
||||
A chain across systems (Steamify release -> ISO tag -> mirror sync -> Gitea build -> download link) is one
|
||||
monitor that says each link when it happens and ends on the real check (the download URL answers 200).
|
||||
When the tool that runs commands stops answering (auto-mode classifier "no verdict"), retry once, then say so
|
||||
and carry on with reading/local work: nothing running on the PC is affected.
|
||||
|
||||
## What the user sees on the PC
|
||||
|
||||
- **Their terminal**: stream the running job's log into it (the `showlog` unit, `wsl-build-host`
|
||||
skill); switch it when the job changes (install log, then the test log, then `~/vms/test.log`).
|
||||
- **The VM's screen**: automated runs are headless. To show one: `scripts/vmview.py ~/vms/<vm>` in
|
||||
a unit with the WSLg env (a window refreshed every 2 s, view only). When the user wants to watch
|
||||
or step in, restart the job with a real window (`--window`, WSLg env) instead.
|
||||
- One screenshot for yourself (`scripts/qmpshot.py <qmp.sock> out.png`, copied back and Read) only
|
||||
to diagnose a stuck boot, not as a test.
|
||||
|
||||
## A job that seems stuck
|
||||
|
||||
1. `--once` and the log's last `#####` step: how long has it been there, compared with the others?
|
||||
2. Is the VM up: `pgrep -af '[q]emu'` (its `hostfwd` port), ssh to it with the test's own options
|
||||
(`. scripts/common.sh` with its `VM_DIR`/`VM_PORT`): a known_hosts mismatch or "Permission
|
||||
denied" is a test setup problem, not the guest.
|
||||
3. No ssh banner: take one screenshot. Firmware PXE/HTTP boot means no usable boot entry: read the
|
||||
UEFI variables from a copy of `vars.fd` (`virt-fw-vars -i <copy> --print`, package
|
||||
virt-firmware) and compare with a loader that works.
|
||||
@@ -0,0 +1,300 @@
|
||||
---
|
||||
name: steam-machine-iso
|
||||
description: Use when working on the Steam Machine CachyOS ISO (repo steamify-cachyos-live-iso) or Steamify's install-time mode (steamify.sh --defaults, --first-login) - building the ISO in podman (in the test VM, or on the Steam Machine), the Calamares Steamify step, simulating the installer in the test VM, and testing the first desktop login.
|
||||
---
|
||||
|
||||
# Steam Machine ISO and Steamify's install-time mode
|
||||
|
||||
Releasing the ISO (GitHub tag, built and attached on the Gitea mirror, naming, runner and server settings) is
|
||||
the `steamify-iso-release` skill; this one is for building and testing by hand.
|
||||
|
||||
A CachyOS live ISO for the Steam Machine that installs Steamify's default
|
||||
setup during the install. Two repos:
|
||||
|
||||
- `steamify-cachyos` (Steamify): `steamify.sh --defaults` applies what the
|
||||
menu would preselect, no prompts, passwordless sudo required. Without a
|
||||
session (installer) `user_systemctl` only changes unit files, the theme
|
||||
replaces an untouched `/etc/skel` Plasma layout so Plasma builds Vapor's at
|
||||
first login, and `lib/first-login.sh` leaves a one-time autostart
|
||||
(`--first-login`: single user's launcher on the new layout, then the app,
|
||||
next to CachyOS Hello). See AGENTS.md there.
|
||||
- `steamify-cachyos-live-iso` (fork of CachyOS-Live-ISO), branch work
|
||||
never on master:
|
||||
- `archiso/airootfs/usr/local/bin/calamares-online.sh` (the installer
|
||||
launcher) copies CachyOS's `settings_online.conf` over
|
||||
`/etc/calamares/settings.conf` on every start, so a shipped settings.conf
|
||||
is lost: it `sed`s `shellprocess@steamify` (+ its instance) in before
|
||||
`cleanup_calamares` right after that copy.
|
||||
- `modules/shellprocess_steamify.conf` runs
|
||||
`/usr/local/bin/steamify-install ${ROOT} ${USER}` outside the chroot; that
|
||||
gives the user a temporary NOPASSWD rule, runs the bundle with
|
||||
`arch-chroot ... runuser -u <user> -- env -i ... --defaults`, logs to
|
||||
`/var/log/steamify-install.log` in the target, never fails the install.
|
||||
- `steamify-prepare.sh [steamify checkout]` puts the bundle on the ISO
|
||||
(`archiso/airootfs/usr/local/share/steamify/steamify.sh`, git-ignored):
|
||||
from a checkout, else the newest release; refuses one without `--defaults`.
|
||||
|
||||
## Everything visible
|
||||
|
||||
The user watches the Steam Machine over Moonlight: run builds and tests in a
|
||||
Konsole on its desktop (or open one that follows the log), never hidden.
|
||||
Progress in the conversation: the `progress-report` skill (a table, only on a change).
|
||||
|
||||
Open that Konsole only when none follows the log yet: `tail -F` picks up
|
||||
each new build's log by itself, so one window serves every build (check with
|
||||
`pgrep -af "tail -n 50 -F"` on the Steam Machine; never `pkill -f` that
|
||||
pattern from a remote command, it kills its own shell).
|
||||
|
||||
```bash
|
||||
ssh steammachine bash -c "'pgrep -f \"tail -n 50 -F\" >/dev/null || { export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus; systemd-run --user -q konsole --hold -e bash -c \"tail -n 50 -F ~/projects/iso-build.log\"; }'"
|
||||
```
|
||||
|
||||
## Steam Machine gotchas
|
||||
|
||||
- Login shells there and in the VM are **fish**: wrap loops in
|
||||
`bash -c '...'` or pipe a script to `bash -s`.
|
||||
- Never `pkill -f <pattern>` from a remote command containing that pattern:
|
||||
it kills its own shell (exit 255).
|
||||
- The VM's host key lives in `~/projects/steamify-cachyos-dev/known_hosts`.
|
||||
Scripts calling plain `ssh` (vmreset.sh) need a wrapper first in `PATH`:
|
||||
`/tmp/steamify-sshwrap/ssh` =
|
||||
`exec /usr/bin/ssh -o UserKnownHostsFile=$HOME/projects/steamify-cachyos-dev/known_hosts -o StrictHostKeyChecking=accept-new "$@"`
|
||||
(recreate after a reboot).
|
||||
- `vmreset.sh` shuts down *any* running QEMU (it checks `pgrep ^qemu-system`),
|
||||
including the ISO VM with someone's live session: ask before resetting the
|
||||
test VM while the ISO VM runs.
|
||||
- Run vmreset.sh as a systemd user unit with `-p KillMode=process`, a unique
|
||||
unit name, `--setenv=REPO=$HOME/projects/steamify-cachyos`, the wrapper
|
||||
`PATH` and `SSH_AUTH_SOCK` (connect with `ssh -A`, keep the session open with
|
||||
`--wait`). Without KillMode=process QEMU dies with the unit; without REPO
|
||||
QEMU fails on the old repo path and the script waits 5 minutes for SSH.
|
||||
|
||||
## Adding a Calamares page/module for Steamify: check it's real first
|
||||
|
||||
Before writing any QML/config for a Calamares page, or building an ISO to
|
||||
test one: confirm the module type is actually installed on this Calamares
|
||||
build. A `.conf` file existing under
|
||||
`archiso/airootfs/etc/calamares/modules/` does **not** mean the module
|
||||
exists — CachyOS's branding ships some vestigial config files with no
|
||||
matching `.so`. Check in a live session (boot the ISO,
|
||||
`scripts/vmisoboot.sh`, no install needed):
|
||||
|
||||
```bash
|
||||
ls /usr/lib/calamares/modules # real module types, e.g. packagechooser (not packagechooserq)
|
||||
pacman -Ql cachyos-calamares-next | grep viewmodule
|
||||
```
|
||||
|
||||
Found on this build (2026-09-28): `packagechooserq` (arbitrary custom QML,
|
||||
`qmlFilename`) does **not exist** — only `packagechooser` does. Its
|
||||
`mode: optionalmultiple` supports multi-select, but only with Shift/Ctrl-click (plain clicks
|
||||
replace the selection; unusable for a real user) despite the misleading static label text
|
||||
("Choose a product from the list. The selected product will be
|
||||
installed." shows in both single- and multi-select modes — don't trust
|
||||
it). Confirm real multi-select from the actual global-storage write in the
|
||||
foregrounded debug log (`pkexec-wrapper calamares -D6`), not the label
|
||||
text: `Config::updateGlobalStorage(const QStringList&)` logs
|
||||
`"<instance>" selected "id1,id2,id3"` (comma-separated) when it works.
|
||||
|
||||
Test a page live, without any ISO rebuild: in the booted live session,
|
||||
edit `/etc/calamares/modules/<module>_<instance>.conf` and the matching
|
||||
module reference in `/etc/calamares/settings.conf`, then relaunch
|
||||
(`pkexec-wrapper calamares -D6`). If Calamares is already running (e.g. an
|
||||
earlier failed launch left its window open), a second launch just prints
|
||||
"Calamares is already running." — close the first window
|
||||
(Afbreken/Cancel, confirm) before relaunching. Only spend the 13+ minute
|
||||
ISO build once the page's module and rendering are confirmed this way.
|
||||
|
||||
## Building the ISO in the test VM (preferred)
|
||||
|
||||
## Building the ISO in the test VM (preferred)
|
||||
|
||||
The build needs no real hardware: run it in the test VM (CachyOS; create it
|
||||
with the vm-install skill), not on the Steam Machine. Not tried yet in the
|
||||
VM: note what differs here. In the VM (`vm_ssh`, visibly in a Konsole there):
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed --noconfirm podman git
|
||||
git clone https://github.com/theupriser/steamify-cachyos-live-iso ~/projects/steamify-cachyos-live-iso
|
||||
sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt # the Steamify checkout (REPO)
|
||||
cd ~/projects/steamify-cachyos-live-iso && git checkout master && ./steamify-prepare.sh /mnt
|
||||
```
|
||||
|
||||
then the same `podman run` as below (paths in the VM; `~/projects/iso-build.log`).
|
||||
The ISO (~3.2 GB) fits the 60G disk; copy it out with
|
||||
`vm_scp "$VM_USER@$VM_HOST:~/projects/steamify-cachyos-live-iso/out/desktop/*.iso" .`
|
||||
(`scripts/common.sh`), then install it with `scripts/vminstall.sh --iso`.
|
||||
Give the VM more room with a bigger disk if `out/`, `build/` and the package
|
||||
cache grow over several builds (`sudo rm -rf build out` between builds).
|
||||
|
||||
## Building the ISO (podman on the Steam Machine)
|
||||
|
||||
Only podman is installed on the Steam Machine itself; the build tools live in
|
||||
the container. Rootful (loop devices, mounts), so `sudo podman ps` shows it,
|
||||
not a rootless podman GUI. Clone: `~/projects/steamify-cachyos-live-iso`.
|
||||
|
||||
```bash
|
||||
cd ~/projects/steamify-cachyos-live-iso
|
||||
git checkout master && ./steamify-prepare.sh ~/projects/steamify-cachyos
|
||||
sudo rm -rf build out
|
||||
systemd-run --user --collect -q -u isobuild-$(date +%s) --working-directory=$PWD bash -c \
|
||||
"sudo podman run --rm -t --pids-limit=-1 --ulimit nofile=65536:65536 --privileged --network=host \
|
||||
-v $HOME/projects/iso-cache:/var/cache/pacman/pkg \
|
||||
-v $PWD:/iso -w /iso docker.io/cachyos/cachyos:latest bash -c \
|
||||
'pacman-key --init && pacman-key --populate && pacman -Syu --noconfirm --needed archiso mkinitcpio-archiso git squashfs-tools grub sudo && ./build-live-modules.sh && ./build-calamares-modules.sh && { ./buildiso.sh -p desktop -w || ./buildiso.sh -p desktop -c -w; }' \
|
||||
> $HOME/projects/iso-build.log 2>&1"
|
||||
```
|
||||
|
||||
`-v ~/projects/iso-cache:/var/cache/pacman/pkg` keeps the downloaded packages
|
||||
(~3 GB) between builds (`mkdir -p ~/projects/iso-cache` once): the container's
|
||||
own tools and mkarchiso's `pacstrap -c` both use that cache, so only updated
|
||||
packages are downloaded. Clear it with `sudo rm -rf ~/projects/iso-cache/*`
|
||||
if it grows too large.
|
||||
|
||||
`build-calamares-modules.sh` builds Calamares' `packagechooserq` (the Steamify
|
||||
page's module; CachyOS's package leaves it out) from CachyOS's Calamares
|
||||
source against the installed `cachyos-calamares-next`, into airootfs, with
|
||||
the version it was built for in `built-for` (the prebuilt `.so` is no longer
|
||||
committed). `build-live-modules.sh` builds the power-off fix for the ISO's kernels
|
||||
(live session; needs the source steamify-prepare.sh puts on the ISO).
|
||||
Output: `out/desktop/steamify-cachyos-*.iso` (~3.2 GB, ~10 min). The build ends with
|
||||
`chown: missing operand` + `ERROR: An unknown error`: harmless, buildiso.sh
|
||||
chowns to `$SUDO_USER`, unset as root; the ISO is already written. Each flag fixes a failure seen before:
|
||||
|
||||
- `--network=host`: the default network has no DNS here (every mirror
|
||||
"Resolving timed out").
|
||||
- `--pids-limit=-1`: otherwise the last ~9 packages alphabetically fail with
|
||||
`GPGME error: Inappropriate ioctl for device` / "missing required signature".
|
||||
- `pacman-key --init/--populate`: the image's keyring isn't initialised.
|
||||
- The retry with `-c` keeps the work dir and cache for a flaky pass.
|
||||
|
||||
Never start a build while one runs (`sudo podman ps`): both use `build/`
|
||||
and `out/`, and `rm -rf build out` breaks the running one. Don't stop a running build to try something else without checking its log
|
||||
first (`grep -ac GPGME ~/projects/iso-build.log`). The repo's CI recipe
|
||||
(`archlinux:base-devel`, `.github/workflows/build.yml`) works too but pulls
|
||||
from one slow mirror.
|
||||
|
||||
## Testing Steamify's install-time mode in the VM
|
||||
|
||||
`scripts/vminstallsim.sh [--fresh]` (VM up via vmreset.sh `--fremont`, branch
|
||||
rsynced to `~/projects/steamify-cachyos`): creates `isotest` (never logged
|
||||
in), runs `/mnt/steamify.sh --defaults` for it without any session, visibly.
|
||||
Every component should print OK and `exit: 0`. Use `--fresh` after changes:
|
||||
an earlier run's single user mode edits the skel layout, and the theme then
|
||||
(rightly) refuses.
|
||||
|
||||
First desktop login as that user:
|
||||
|
||||
1. The first-login step (`~/.local/share/steamify/bin/first-login`, removes
|
||||
itself) starts the app through `run-app`, which always downloads the
|
||||
newest *release* (`releases/latest/download/steamify-app.sh`): before a
|
||||
release that shows the old version. For the branch's app, start it by
|
||||
hand afterwards as that user: copy `/mnt` to `/opt/steamify-test`
|
||||
(`chmod -R a+rX`), then `sudo -u isotest XDG_RUNTIME_DIR=/run/user/1001
|
||||
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus systemd-run --user
|
||||
/opt/steamify-test/ui/steamify-ui`.
|
||||
2. Gamescope doesn't run in the VM: `Session=plasma.desktop` in
|
||||
`/etc/sddm.conf.d/zz-steamos-autologin.conf`, remove
|
||||
`/etc/plasmalogin.conf.d/00-test-autologin.conf`, reboot.
|
||||
3. Expect: Steam Deck wallpaper (Vapor layout), `primaryActions=3` in
|
||||
isotest's `plasma-org.kde.plasma.desktop-appletsrc`, the autostart entry
|
||||
gone, `steamify-ui` running next to `cachyos-hello`.
|
||||
4. Screenshot: QMP `screendump` gives "no surface" with virgl; use
|
||||
`sudo -u isotest env XDG_RUNTIME_DIR=/run/user/1001 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus WAYLAND_DISPLAY=wayland-0 spectacle -b -n -f -o /tmp/shot.png`.
|
||||
|
||||
## Testing the ISO itself
|
||||
|
||||
Unattended: `VM_DIR=~/projects/iso-vm scripts/vminstall.sh --iso <built iso> --fremont`
|
||||
(vm-install skill) installs it with the ISO's headless `cachyos-installer`;
|
||||
Calamares and its Steamify step don't run then, so run `steamify-install`
|
||||
from the live script for that part. Through Calamares by hand, as below.
|
||||
|
||||
Install the built ISO in a **new** VM (`~/projects/iso-vm`: copy of run.sh,
|
||||
`share` symlink, `cachyos.iso` symlink to the build, own disk/vars; power the
|
||||
test VM off first, both use port 2222; `run.sh install --fremont` as a user
|
||||
unit), through Calamares, then check
|
||||
`/var/log/steamify-install.log` in the installed system. In the chroot
|
||||
`uname -r` is the live kernel: module loads and checks against the running
|
||||
kernel may fail even when DKMS built for the installed kernels. Real
|
||||
hardware (Steam Machine, USB stick) is the final test: gamescope, LEDs, CEC,
|
||||
power-off.
|
||||
|
||||
## Live-session quirks
|
||||
|
||||
- Copy/paste doesn't reach QEMU over Moonlight: type commands with
|
||||
`python3 scripts/qmptype.py <vm dir>/qmp.sock "<command>"` (US layout, adds
|
||||
Enter; the VM window's terminal must have focus). QMP `screendump` gives "no
|
||||
surface" with virgl: screenshot the Steam Machine's desktop instead
|
||||
(`spectacle -b -n -f -o /tmp/host.png` in its session).
|
||||
- CachyOS Hello refuses to start the installer on a self-built ISO ("testing
|
||||
ISO"): run `calamares-online.sh` in Konsole.
|
||||
- `cachyos-calamares-next 3.4.2-13` needs `libboost_*.so.1.91.0` while the
|
||||
repos ship Boost 1.92: steamify-prepare.sh puts 1.91's libraries on the ISO
|
||||
(from archive.archlinux.org) until CachyOS rebuilds it.
|
||||
- Driving the ISO VM's Calamares with `scripts/qmpkey.py`/`qmpclick.py`/
|
||||
`qmptype.py` (`scripts/vmisoboot.sh` for a VM without virgl, so
|
||||
`qmpshot.py` screendumps work): `qmptype.py`'s character map has no `>`
|
||||
or `&` — a typed command using either raises "no key for" and, worse,
|
||||
can leave an **unterminated quote** in the shell's input buffer (a
|
||||
stray `"` from a half-typed command), after which every later command
|
||||
you type gets silently absorbed into that one open string instead of
|
||||
running. Symptom: the prompt shows a bare `>` continuation instead of
|
||||
`[user@host ~]$`. Fix: send Ctrl+C first (`send-key` with `ctrl`+`c`),
|
||||
then retype. Prefer `| tee` over `> file`, and multiple `head`/`tail`
|
||||
calls over one long redirected command. Calamares page buttons
|
||||
(Volgende/Terug) move vertically as page content grows/shrinks: re-shot
|
||||
and re-locate the button after every page change, don't reuse a fixed
|
||||
y-coordinate. A second `pkexec-wrapper calamares -D6` while an earlier
|
||||
failed instance's window is still open just prints "Calamares is already
|
||||
running.": close that window first (its Cancel/Afbreken button, then
|
||||
confirm the "really cancel?" dialog).
|
||||
- Run `pkexec-wrapper calamares -D6` in its **own Konsole tab**: it stays
|
||||
in the foreground and blocks that tab's shell. Typing into the same
|
||||
tab afterwards just echoes the text inertly into Calamares' stdin
|
||||
(no error, nothing runs). Open "New Tab" in Konsole for commands.
|
||||
|
||||
## Keeping this cheap: don't poll screenshots
|
||||
|
||||
Repeated `qmpshot.py` + image analysis to detect "is it booted yet" burns a
|
||||
lot of tokens per image. Prefer cheap, text-only signals instead:
|
||||
|
||||
- **Boot readiness**: pass `VM_SERIAL=<dir>/serial` to `run.sh`/`vmisoboot.sh`
|
||||
(it logs `console=ttyS0` to a plain text file via a QMU chardev socket).
|
||||
Wait with a background `tail -F <dir>/serial | grep -E "Reached target
|
||||
Graphical Interface|sddm|plasmashell|login:|Failed|panic"` — one line of
|
||||
text per event, no image tokens at all.
|
||||
- **Driving the wizard/Calamares**: script a fixed sequence of
|
||||
`qmpkey.py`/`qmptype.py`/`qmpclick.py` calls from a known page layout
|
||||
instead of screenshotting after every step to "see where we are". Only
|
||||
take a screenshot at real decision points (to show the user, or to verify
|
||||
a page layout changed) — not as a polling mechanism.
|
||||
- **When the user is driving by hand** (as in a live install), just ask them
|
||||
what they see / what step they're on instead of screenshotting the VM
|
||||
yourself — they're already looking at it.
|
||||
- If you must poll for "is the screen non-black yet", a single low-res
|
||||
screenshot at the end of a serial-log-driven wait is enough; don't loop
|
||||
screenshots every few seconds.
|
||||
- Calamares' Welcome page applies its keyboard layout to the live session
|
||||
immediately (a live typing preview). If it's not US (e.g. Dutch after
|
||||
picking Nederlands), `qmptype.py`'s US-layout key map silently sends
|
||||
the wrong characters afterwards: `/` becomes `-`, `(`/`)` get garbled,
|
||||
with no "no key for" error. `setxkbmap us` does nothing on Wayland.
|
||||
Probe with a symbol-free command (`echo test123`) after any
|
||||
language/keyboard step, and fix the layout from the live session's own
|
||||
settings before typing paths or shell syntax again.
|
||||
|
||||
## Reading an installed VM's disk (no screenshots)
|
||||
|
||||
Also while the VM runs, read-only, without touching it:
|
||||
|
||||
```bash
|
||||
sudo modprobe nbd max_part=8
|
||||
sudo qemu-nbd -r -c /dev/nbd0 --image-opts driver=qcow2,force-share=on,file.driver=file,file.filename=$PWD/disk.qcow2
|
||||
sudo mount -o ro,rescue=nologreplay,subvol=@log /dev/nbd0p2 /mnt/x # logs; subvol=@ for the rest
|
||||
sudo cat /mnt/x/steamify-install.log
|
||||
sudo umount /mnt/x; sudo qemu-nbd -d /dev/nbd0
|
||||
```
|
||||
|
||||
(`qemu-nbd -U` doesn't exist here; `force-share=on` in `--image-opts` does
|
||||
the same.) For a running live session, start the VM with `VM_SERIAL=<file>`
|
||||
and end typed commands with `| sudo tee /dev/ttyS0`: the output lands in
|
||||
that file as text.
|
||||
@@ -0,0 +1,279 @@
|
||||
---
|
||||
name: steam-machine-testing
|
||||
description: Use when testing or developing Steamify CachyOS (repo steamify-cachyos, steamify.sh) directly on the real Steam Machine (Valve Fremont running CachyOS) - either in a Claude Code session on the machine itself or over SSH - running the wizard with scripted menu input, checking each component's state, gaming mode, HDMI-CEC, LEDs, kernel pin, BIOS and reboot checks.
|
||||
---
|
||||
|
||||
# Testing Steamify CachyOS on the real Steam Machine
|
||||
|
||||
This is the real-hardware twin of the `cachyos-vm-testing` skill. The big
|
||||
difference: **there are no snapshots**. Every change is on the actual
|
||||
machine, so undo what you turn on, never flash the BIOS without the user
|
||||
saying so, and ask before anything that could leave it unbootable (kernel
|
||||
pin, removing kernel headers, DKMS).
|
||||
|
||||
What the Steam Machine gives you that the VM can't: gaming mode that
|
||||
actually renders (Steam's settings, HDMI-CEC section, refresh rates), real
|
||||
LEDs, a real TV over HDMI-CEC, and the real BIOS version check.
|
||||
|
||||
## Keep everything in ~/projects
|
||||
|
||||
The machine is the user's own. Everything you put on it lives in
|
||||
`~/projects` and nowhere else in the home folder: the repo copy
|
||||
(`~/projects/steamify-cachyos`), the dev repo with the test VM
|
||||
(`~/projects/steamify-cachyos-dev`), module builds, scratch checkouts
|
||||
(`~/projects/<name>`). Temporary files go in `/tmp` and are removed again.
|
||||
Never create folders like `~/steamify-test` or `~/s4mod`. What Steamify
|
||||
itself installs (units, `~/.local/...`) is fine: that's the product. When
|
||||
you're done, remove what you left in `~/projects` that isn't a repo.
|
||||
|
||||
## Test VM on the Steam Machine
|
||||
|
||||
The Mac can't run the VM (no KVM); the Steam Machine can (31 GB RAM, 12
|
||||
threads, KVM). `qemu-desktop` and `edk2-ovmf` are installed; `run.sh` finds
|
||||
Arch's OVMF in `/usr/share/edk2/x64`. Run it from
|
||||
`~/projects/steamify-cachyos-dev` with
|
||||
`REPO=~/projects/steamify-cachyos` (the synced branch) and follow the
|
||||
`cachyos-vm-testing` skill, with `ssh steammachine` in front of its
|
||||
commands where they run on the host.
|
||||
|
||||
## Two ways to work
|
||||
|
||||
**A. Claude Code on the Steam Machine itself** (preferred for development):
|
||||
run commands directly, no SSH. Clone the repo there, e.g.
|
||||
`~/projects/steamify-cachyos`, and run the wizard from it. Where this skill
|
||||
says `sm bash -s << 'EOF'`, just run the body locally.
|
||||
|
||||
**B. From another machine over SSH.** Never guess the host: find the
|
||||
candidates, then ask the user (next section). Once chosen, set per shell:
|
||||
|
||||
```bash
|
||||
export SM_HOST=<chosen host> SM_USER=<chosen user>
|
||||
sm() { ssh -o BatchMode=yes "$SM_USER@$SM_HOST" "$@"; }
|
||||
```
|
||||
|
||||
### Finding the Steam Machine
|
||||
|
||||
Skip if the user already named a host, or `~/.ssh/config` has a
|
||||
`Host steammachine` entry (use that). Otherwise scan the LAN for SSH hosts,
|
||||
all read-only and quick:
|
||||
|
||||
```bash
|
||||
# mDNS: hosts announcing SSH (macOS: dns-sd; Linux: avahi-browse)
|
||||
if command -v dns-sd >/dev/null; then dns-sd -B _ssh._tcp local > /tmp/sm-mdns 2>&1 & p=$!; sleep 3; kill $p; awk 'NR>4{print $NF}' /tmp/sm-mdns | sort -u
|
||||
elif command -v avahi-browse >/dev/null; then avahi-browse -rtp _ssh._tcp 2>/dev/null | awk -F';' '/^=/{print $7" "$8}' | sort -u; fi
|
||||
# hosts with port 22 open on the local /24 (nmap if present, else nc; macOS has no `timeout`)
|
||||
net=$( (ipconfig getifaddr en0 || ip -4 route get 1 | awk '{print $7;exit}') 2>/dev/null | cut -d. -f1-3)
|
||||
if command -v nmap >/dev/null; then nmap -p22 --open -oG - "$net.0/24" | awk '/22\/open/{print $2" "$3}'
|
||||
else for i in $(seq 1 254); do (nc -z -w1 -G1 "$net.$i" 22 2>/dev/null || nc -z -w1 "$net.$i" 22 2>/dev/null) && echo "$net.$i" & done; wait; fi
|
||||
```
|
||||
|
||||
A CachyOS Steam Machine usually shows up by its hostname (e.g. `*.local`
|
||||
with "steam", "fremont" or "cachyos" in it). Put the best matches first
|
||||
and ask with AskUserQuestion: up to 3 candidates as options (label = host
|
||||
name or IP, description = what was found), "Other" lets the user type one.
|
||||
Ask the user name the same way (default: the local `$USER`).
|
||||
|
||||
Then check key login: `ssh -o BatchMode=yes -o ConnectTimeout=5 "$SM_USER@$SM_HOST" true`.
|
||||
If it fails, tell the user to run `! ssh-copy-id $SM_USER@$SM_HOST` (it needs
|
||||
their password; sshd must be on: `sudo systemctl enable --now sshd` on the
|
||||
machine). Offer to add a `Host steammachine` entry to `~/.ssh/config` so the
|
||||
next session skips the scan.
|
||||
|
||||
The dev repo's `scripts/*.sh` (vmstate, vmrun, cmp, vmshot, vmcec) all go
|
||||
through `vm_ssh`, so they also work against the Steam Machine with
|
||||
`VM_HOST=$SM_HOST VM_PORT=22 VM_USER=$SM_USER scripts/vmstate.sh`.
|
||||
Don't use `vmreset.sh`, `vmwake.sh` or `run.sh`: those are QEMU-only.
|
||||
`vmcec.sh` needs the `vivid` fake TV; on real hardware test CEC with the
|
||||
real TV (below) instead.
|
||||
|
||||
The user's shell may be fish: pass remote commands as bash heredocs, never
|
||||
as inline strings containing `$`.
|
||||
|
||||
Keep the repo in sync with the working copy on the machine (A: `git pull`;
|
||||
B: `rsync -a --exclude .git ./ "$SM_USER@$SM_HOST:~/projects/steamify-cachyos/"`).
|
||||
Below, `$REPO` is that checkout on the Steam Machine.
|
||||
|
||||
## Visible terminal (let the user watch)
|
||||
|
||||
When the user is watching the Steam Machine's screen (in person or through
|
||||
Moonlight/Sunshine), run commands in a Konsole window on its desktop
|
||||
instead of hidden over SSH: they see every command and its output. A tmux
|
||||
session makes that window scriptable.
|
||||
|
||||
Set it up (Plasma desktop running; `sudo pacman -S --needed tmux` once):
|
||||
|
||||
```bash
|
||||
sm bash -s << 'EOF'
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u) WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus
|
||||
tmux kill-session -t claude 2>/dev/null
|
||||
# Plain bash, not the user's login shell: fish rejects bash syntax ($?) and
|
||||
# swallows Enter keys sent while it's still drawing its prompt.
|
||||
tmux new-session -d -s claude -x 200 -y 50 'bash --norc --noprofile -i'; sleep 1
|
||||
tmux send-keys -t claude "PS1='[claude] \\w\\$ '; clear" Enter
|
||||
systemd-run --user -q --setenv=XDG_CURRENT_DESKTOP=KDE konsole --separate -e tmux attach -t claude
|
||||
EOF
|
||||
```
|
||||
|
||||
Run a command and wait for it: end it with a marker, then poll the pane.
|
||||
|
||||
```bash
|
||||
sm bash -s << 'EOF'
|
||||
tmux send-keys -t claude 'clear; sudo pacman -S --needed --noconfirm foo; echo "== EXIT $?"' Enter
|
||||
for i in $(seq 200); do tmux capture-pane -p -t claude -S -400 | grep -q '^== EXIT' && break; sleep 3; done
|
||||
tmux capture-pane -p -t claude -S -400 | grep -v '^\s*$' | tail -30
|
||||
EOF
|
||||
```
|
||||
|
||||
- `clear` first, or an old marker still in the pane ends the wait at
|
||||
once; with several runs, count the markers (`grep -c`).
|
||||
- Longer scripts: write them to `/tmp/<name>.sh` in the heredoc, then
|
||||
`tmux send-keys -t claude 'clear; bash /tmp/<name>.sh' Enter`.
|
||||
- For runs of 10+ minutes, poll from a `run_in_background` Bash call and
|
||||
report when it finishes.
|
||||
- A reboot ends the session (and the window): after every reboot, set it
|
||||
up again before sending anything, or `send-keys` fails
|
||||
(`error connecting to /tmp/tmux-1000/default`) while a wait loop spins.
|
||||
- Watching remotely: Sunshine on the machine (`cachyos` repo, user unit
|
||||
`app-dev.lizardbyte.app.Sunshine.service`, `sunshine --creds` for the web
|
||||
UI, ufw: TCP 47984-47990 and 48010, UDP 47998-48010 from the LAN only) and
|
||||
Moonlight on the user's computer. Pairing: the user reads Moonlight's PIN,
|
||||
then `curl -k -u admin:<pw> https://<host>:47990/api/pin` (GET lists the
|
||||
pending `pairing_id`) and POST `{"pin","name","pairing_id"}` to it. The
|
||||
"Desktop" app streams any session; avoid "Low Res Desktop" (xrandr).
|
||||
|
||||
## Menu
|
||||
|
||||
1. SteamOS conversion (boot into gaming mode via autologin, Return to Gaming Mode shortcut, Steam desktop autostart)
|
||||
2. └ Boot into: gamescope / desktop (sub-option of 1)
|
||||
3. SteamOS theme (`cachyos-vapor` + Vapor global theme; needs a running Plasma session)
|
||||
4. Steam Deck/Machine icons (`STEAM_GAMEPADUI_ARGS -steamos3`)
|
||||
5. Single user mode (SDDM, no lock screen/user switching/log out, Valve's empty KDE wallet)
|
||||
6. Steamify shortcut
|
||||
7. HDMI-CEC (pre-ticked on a first run on Fremont)
|
||||
8. Steam Machine support (leds-valve-dkms-git, `ensure-kernel-headers.service`, udev rule, steamos-manager, powerdevilrc)
|
||||
9. └ Power-off fix (DKMS `steamify-fremont-poweroff`; ticked with 8, opt-out)
|
||||
10. └ Update BIOS (opt-in, never pre-ticked; tickable only when Valve has a newer BIOS)
|
||||
|
||||
Since 2.2.0 "Pin the kernel" and "HDMI refresh boost" are only shown while
|
||||
something of them is left, unticked, so a normal run removes them. The menu
|
||||
marks components a normal run will update with "(update)" (feature versions
|
||||
in `~/.local/state/cachyos-gamescope-boot/features.state`; set an entry to an
|
||||
older version, e.g. `machine=2.1.0`, to test the update flow).
|
||||
|
||||
Numbers shift when a parent is unticked (its sub-option is hidden), so read
|
||||
the ticks with `'q\n'` first. Undo journals: `~/.local/state/cachyos-gamescope-boot/`.
|
||||
|
||||
## Running the wizard
|
||||
|
||||
Visible, in the desktop session (best when the user is watching the screen):
|
||||
|
||||
```bash
|
||||
sm bash -s << 'EOF'
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u) WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus
|
||||
printf 'q\n' | "$HOME/projects/steamify-cachyos/steamify.sh" 2>&1 | tee /tmp/wizard.log
|
||||
EOF
|
||||
```
|
||||
|
||||
Newest release instead of the checkout:
|
||||
`curl -fsSL https://github.com/<owner>/steamify-cachyos/releases/latest/download/steamify.sh -o /tmp/steamify.sh`
|
||||
then `printf '<input>' | WIZARD_KEEP_STDIN=1 bash /tmp/steamify.sh`.
|
||||
|
||||
Menu input: a number toggles, an empty line continues, `a` re-applies what is
|
||||
on, `q` quits; `y` answers "Go ahead?". After a run: Enter (menu) or
|
||||
`[m]`/`[r]` when a restart is needed. `q` with a restart pending asks
|
||||
"Restart now? [Y/n]" (default **yes** - on real hardware that really
|
||||
reboots). End scripted input right after the last `q`, or with `n`.
|
||||
|
||||
| Input | Meaning |
|
||||
|---|---|
|
||||
| `'q\n'` | just show the menu (always do this first) |
|
||||
| `'\ny\nm\nq\nn\n'` | first run, accept the pre-ticked items, don't reboot |
|
||||
| `'3\n\ny\n'` | toggle the theme (while 1 is ticked) |
|
||||
| `'7\n\ny\nm\nq\nn\n'` | toggle HDMI-CEC (while 1 is ticked) |
|
||||
| `'a\ny\nm\nq\nn\n'` | re-apply what is on |
|
||||
|
||||
When everything is off the menu treats it as a first run and pre-ticks.
|
||||
`a` doesn't retry a component that failed.
|
||||
|
||||
Long runs (packages, yay, DKMS) take 10+ minutes: use `run_in_background`
|
||||
and wait with an until-loop.
|
||||
|
||||
## Checking state
|
||||
|
||||
`scripts/vmstate.sh` (mode B with the env above), or in mode A run the body
|
||||
of its heredoc locally: display manager and autologin, session sync bridge,
|
||||
shortcut, Steam autostart, theme keys, lock restrictions, panel, kickoff,
|
||||
LED driver/DKMS/`/sys/class/leds/valve-leds*` owner, steamos-manager,
|
||||
journals and duplicate journal keys. `scripts/cmp.sh save` / `cmp.sh` diff
|
||||
the KDE configs.
|
||||
|
||||
## Real-hardware checks (what the VM can't do)
|
||||
|
||||
- **Gaming mode**: reboot with Boot into gamescope; Steam starts in Big
|
||||
Picture/gamepad UI, Steam Deck icons with item 4, "Switch to Desktop"
|
||||
lands in Plasma and "Return to Gaming Mode" goes back.
|
||||
- **HDMI-CEC with the real TV**: `cec-ctl -d /dev/cec0 --playback -S`
|
||||
(topology), TV remote keys reach Steam, Steam's settings > Display >
|
||||
HDMI-CEC toggles (remote, WakeTv, SuspendTv, SuspendDevice) change
|
||||
`com.steampowered.SteamOSManager1.HdmiCec2` properties, sleep turns the TV
|
||||
off, wake turns it on, TV standby suspends the machine when SuspendDevice is on.
|
||||
`steamosctl get/set-hdmi-cec-*` don't work (steamos-manager 26.4.1).
|
||||
- **LEDs**: `leds_valve` loaded, `/sys/class/leds/valve-leds*` owned by the
|
||||
user, writing `brightness` visibly changes them.
|
||||
- **Power-off fix**: `dmesg | grep 'GPIO 18'` shows the pin's register and
|
||||
whether the S4/S5 wake bit is set (recent kernels: set, "cleared at
|
||||
power-off"); `/sys/kernel/debug/gpio` has an S4/S5 column. The real test
|
||||
needs the user at the machine: shut down and check it stays off (30 s+),
|
||||
2-3 times; control run: `sudo rmmod steamify_fremont_poweroff`, shut down,
|
||||
it should boot again right away. Do this for every new major kernel.
|
||||
Install a test kernel next to the current one under another package name
|
||||
(e.g. `linux-cachyos-bore`) so the user can pick the old one in Limine.
|
||||
- **BIOS**: item 10 is tickable only if Valve has a newer BIOS than
|
||||
`cat /sys/class/dmi/id/bios_version`. Walk the flow with
|
||||
`WIZARD_BIOS_DRY_RUN=1` (never flashes). **Never run a real flash unless
|
||||
the user explicitly asks in this session**, and only on AC power.
|
||||
|
||||
## Reboot checks
|
||||
|
||||
The session reboots; in mode A the Claude session ends. Tell the user
|
||||
before rebooting and what to check afterwards.
|
||||
|
||||
- Single user mode on: SDDM autologin without greeter (`pgrep sddm-greeter` empty).
|
||||
- Everything off: plasmalogin greeter shown.
|
||||
- Stuck in a gamescope relogin loop: over SSH
|
||||
`sudo /usr/lib/steamos/steam-set-session plasma.desktop && sudo systemctl restart display-manager`.
|
||||
|
||||
## Restoring the machine
|
||||
|
||||
No snapshots, so before a test run note the starting state (`vmstate.sh`,
|
||||
`cmp.sh save` into `~/.cache/vm-baseline`) and afterwards turn back off
|
||||
what the test turned on through the menu (the undo journals do the rest).
|
||||
Check `cmp.sh` is clean except KDE's own default-valued keys.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- To see the plan, answer "n" at "Go ahead?" (`'\nn\nq\n'`). Piping a "y"
|
||||
applies it, on real hardware; never wrap a run that may call pacman in
|
||||
`timeout`: killing pacman after its transaction but before its hooks
|
||||
leaves a stale boot entry and `db.lck` (fix: remove the lock, reinstall the
|
||||
same packages so the hooks run).
|
||||
- The user's login shell is fish: inline `ssh host '...'` commands with `$`
|
||||
or `\$` break; always send bash heredocs (`sm bash -s << 'EOF'`).
|
||||
- pacman 404 on a `.sig`: the local sync database is stale (the mirror moved
|
||||
on to a newer version). A full `pacman -Syu` fixes it; don't fetch
|
||||
signatures by hand.
|
||||
- CachyOS's `linux-cachyos` is clang-built, `-bore` GCC-built: when building
|
||||
a module by hand, pass `LLVM=1` only for clang kernels (DKMS picks itself).
|
||||
|
||||
- `pkill -f '<pattern>'` also matches the shell running it; kill by PID.
|
||||
- Apps started straight from SSH lack the session's Qt theme or core-dump
|
||||
(spectacle): start them with `systemd-run --user`.
|
||||
- Screenshots: `systemd-run --user --wait -q spectacle -b -n -f -o /tmp/x.png`
|
||||
with the session env above; in gaming mode spectacle can't see gamescope,
|
||||
use Steam's own screenshot (Steam + R1) instead.
|
||||
- `makepkg -i` runs `sudo -k` and always asks for the password again; the
|
||||
real machine has no NOPASSWD rule, so scripted runs that need sudo stop
|
||||
at the prompt. Run those in a terminal the user can type into, or have the
|
||||
user add a temporary sudoers rule and remove it afterwards.
|
||||
- Check the bundle like CI: `.github/tools/bundle.sh`, then
|
||||
`shellcheck -S warning -e SC2034,SC2154 dist/steamify.sh`.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: steamify-branch-cleanup
|
||||
description: Clean up git branches in steamify-cachyos (and sibling steamify repos) following the release-branch flow. Use when the user asks to clean up / prune branches.
|
||||
---
|
||||
|
||||
# Steamify branch cleanup
|
||||
|
||||
Rules:
|
||||
- ALWAYS keep `main`. A `release/X.Y.Z` branch is deleted (local and remote) once its release is published and its tip is contained in a tag or in `main` (the user said so on 2026-10-02: the tags keep the history). Check each one first (`git tag --contains <tip>`, `git merge-base --is-ancestor`): a release branch with a commit that is in no tag and not in `main` (`release/2.9.2`: a docs commit) is kept and reported. The open release's branch stays until its PR is merged.
|
||||
- Delete `feature/*` and `bugfix/*` branches (local and remote) only when merged into `origin/main` or the current `origin/release/*` branch.
|
||||
- Delete local branches whose remote is gone (`[origin/...: gone]`) only if they are `feature/*` or `bugfix/*`; local stale `release/*` branches are kept unless the user says otherwise.
|
||||
- Never touch unmerged branches; list them and report instead.
|
||||
- Never commit or merge to main.
|
||||
|
||||
Merging a feature/bugfix PR yourself (into a release branch, or `master` in the ISO repo): delete the
|
||||
branch at once, remote and local (`gh pr merge N --merge --delete-branch`, then `git branch -D`, `git fetch
|
||||
--prune`); the user asked for this on 2026-09-29. Release branches are deleted after their release (see the rules above; the tag keeps the history). Unmerged old branches (`feat/vram-booster`, `refactor/qml-screens`, `backup/*`) are reported, not
|
||||
touched. The GitHub-side `release delete <tag> --cleanup-tag` is how test ISO releases go.
|
||||
|
||||
Steps:
|
||||
1. `git fetch --all --tags --prune`
|
||||
2. `git checkout main && git merge --ff-only origin/main`
|
||||
3. List candidates: `git branch -vv`, `git branch -r --merged origin/main`, and same for the current release branch.
|
||||
4. Delete local (`git branch -d`) and remote (`git push origin --delete <b>`) feature/bugfix branches per the rules above.
|
||||
5. Report what was deleted and what was kept.
|
||||
|
||||
Note: remote deletions may be blocked by the auto-mode classifier; if so, stop and let the user approve a permission rule or run it via `!`.
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: steamify-iso-release
|
||||
description: Use when releasing, debugging or changing how the Steamify ISO is built and published - the GitHub tag that names it, the Gitea mirror (git.upriser.nl) that builds and attaches the 3.2 GB ISO, the Steamify workflows that start it, and every trap met while setting it up (413, privileged runner, sudo dropping variables, mirror syncs deleting tags, upload-artifact@v3, GitHub 429). Also for the Gitea side of steamify-cachyos' own releases.
|
||||
---
|
||||
|
||||
# Steamify ISO release pipeline (set up 2026-09-29, first ISO out the same night)
|
||||
|
||||
Tested end to end with the first naming (`v2.9.3-dev.2026.09.29-2044`); since 2026-09-30 the names are by the day, like
|
||||
CachyOS (below; workflow changes in ISO repo PR #15, steamify-cachyos PR #69). The workflows always run from the ISO
|
||||
repo's `master`; `feat/steamify` is no longer needed.
|
||||
|
||||
## The flow (one tag names everything)
|
||||
|
||||
1. **GitHub, `iso-1-github-tag.yml`** (ISO repo `theupriser/steamify-cachyos-live-iso`, `workflow_dispatch` only, input
|
||||
`kind`: `release`, `dev` or `auto` = release on master, dev elsewhere): takes Steamify's newest release (`X.Y.Z`),
|
||||
the day **now in UTC** and makes an **annotated tag** plus a GitHub release with the changelog notes and the
|
||||
*direct* download link on Gitea. Like CachyOS by the day, no time: real `vX.Y.Z-YYMMDD`, dev (pre-release)
|
||||
`vX.Y.Z-dev-YYMMDD`. GitHub is the only place that reads a clock.
|
||||
- **One ISO per day and kind** (space): a second build the same day deletes the earlier GitHub release and tag,
|
||||
syncs the mirror and waits until the mirror dropped the tag (it drops tag, release and ISO together), then tags
|
||||
again (else the new tag is only an update there and starts no build).
|
||||
- **Retention:** at most 10 dev and 10 real releases (`gh release delete --cleanup-tag`); the mirror follows.
|
||||
2. **The mirror syncs** (git.upriser.nl is a pull mirror of GitHub with **no interval of its own**: every repo's
|
||||
`git-upriser-sync.yml` calls its mirror-sync API on every branch and tag push, and iso-1's last step after the tag;
|
||||
secrets `GIT_UPRISER_URL`, `GIT_UPRISER_TOKEN`): the tag arrives.
|
||||
3. **Gitea, `iso-2-gitea-build.yml`** (`workflow_dispatch` only, started by iso-1's last step through Gitea's dispatch API with
|
||||
`{"ref": "refs/tags/<tag>"}` (the **full** ref: a bare tag name answers 404) and the input `steamify_ref`; a tag push
|
||||
starts nothing; skipped on GitHub by `github.server_url`): parses the
|
||||
tag, builds the ISO on the runner with exactly that Steamify (`STEAMIFY_VERSION`), the tag's time (label) and
|
||||
the tag without its `v` (`STEAMIFY_ISO_VERSION`: file name, boot menu, `/etc/steammachine-iso-build`), then
|
||||
attaches ISO + `.sha256` + `.sha1` + `.pkgs.txt` to the mirror's release for that tag
|
||||
(`akkuman/gitea-release-action`, the run's own token). A leftover release of the same tag is replaced. Two jobs,
|
||||
build and release; no tests there (the runner is small): VM tests run locally, `scripts/vmtest.sh`.
|
||||
4. **Trigger:** by hand (*Actions -> ISO 1/2 · Tag and release (GitHub) -> Run workflow*, or
|
||||
`gh workflow run iso-1-github-tag.yml -R theupriser/steamify-cachyos-live-iso --ref master -f kind=dev|release`), or
|
||||
by a new version: `steamify-cachyos`' `bundle.yml` step "Start the Steamify ISO's release" (a version published
|
||||
from `main` -> `kind=release`, an unreleased version pushed to `release/**` -> `kind=dev`; secret
|
||||
`ISO_DISPATCH_TOKEN` there: fine-grained token, only the ISO repo, *Actions: read and write*; without it the
|
||||
step is skipped). A push does **not** release (a build is 20 minutes and 3.2 GB).
|
||||
5. Names, for tag `v2.9.6-dev-260930`: file `steamify-cachyos-2.9.6-dev-260930-x86_64.iso`,
|
||||
label `STEAMIFY_2_9_6_260930` (ISO 9660: 32 chars, `A-Z 0-9 _`). By hand, without a tag: `local`
|
||||
(`steamify-cachyos-local-x86_64.iso`, `STEAMIFY_<version>_LOCAL`). The build reads no clock.
|
||||
|
||||
Total from a Steamify release to the ISO on Gitea: about 20 minutes (release, tag, sync in seconds, build ~18).
|
||||
|
||||
## Which Steamify goes on the ISO, and the build badge
|
||||
|
||||
- `iso-1` input **`steamify_ref`** (a Steamify branch or tag; empty = the newest *published* release). A dev ISO from a
|
||||
release branch passes the branch (`bundle.yml` does), so `release/2.9.6` gives a 2.9.6 ISO before `v2.9.6` exists;
|
||||
without it every dev ISO carries the last published version (it kept producing 2.9.5). iso-1 reads `VERSION` from
|
||||
that ref's `steamify.sh`; iso-2 clones the ref and runs `steamify-prepare.sh <checkout>`.
|
||||
- **Build badge in the GitHub release:** iso-1 writes `` at the top of the notes; iso-2's
|
||||
last job `report` (`if: always()`) turns it into succeeded / failed / cancelled (shields.io static badge, linked to the
|
||||
Gitea run) through the GitHub API. Secret **`GH_RELEASE_TOKEN`** on the Gitea repo (Settings -> Actions -> Secrets):
|
||||
fine-grained GitHub token, only `steamify-cachyos-live-iso`, *Contents: read and write*; expires, so renew it. Without
|
||||
it the badge stays "running". Gitea's own `badge.svg` says "no status" for these runs (they run on a tag ref).
|
||||
- Secrets in all: GitHub `steamify-cachyos`: `ISO_DISPATCH_TOKEN`, `GIT_UPRISER_URL`, `GIT_UPRISER_TOKEN`; GitHub ISO
|
||||
repo: `GIT_UPRISER_URL`, `GIT_UPRISER_TOKEN`; Gitea ISO repo: `GH_RELEASE_TOKEN`.
|
||||
|
||||
## Why it is split like this (learned the hard way)
|
||||
|
||||
- **A release made only on the mirror disappears** at the next sync: the pull mirror removes tags GitHub doesn't
|
||||
have, and the release goes with it (one that has files may linger, tagless, and sorts oddly: delete it). A
|
||||
release on a tag that **comes from GitHub survives** every sync, files included (tested with a 0-byte asset).
|
||||
Hence: GitHub makes the tag, Gitea only attaches the file. You can't delete a tag on a mirror by hand either.
|
||||
- GitHub releases take **2 GB per file**; the ISO is 3.2 GB. So GitHub gets notes and a link, Gitea the file.
|
||||
- **A replaced tag starts no run on the mirror.** One ISO per day and kind: iso-1 deletes the day's GitHub release and
|
||||
tag and tags again; the mirror takes the new tag object (same name) but Gitea starts nothing and keeps the old release
|
||||
(same id, new creation time) with the old ISO. So the build is started by dispatch, after the mirror has the new tag
|
||||
object (`GET .../tags/<tag>` id == `git rev-parse refs/tags/<tag>`); iso-2 deletes a leftover release of the tag before
|
||||
it publishes. Retention: at most 10 dev + 10 real releases.
|
||||
- **Gitea runner and parallel jobs:** it ignores `max-parallel`, and jobs that start in the same second clone the same
|
||||
action into one shared folder and break each other (`actions/cache` failed): keep parallel jobs free of `uses:`.
|
||||
(The VM tests no longer run there at all: they run locally, `scripts/vmtest.sh`.)
|
||||
- **Annotated tags**: a lightweight tag has only its commit's date, so tags on the same commit sort randomly
|
||||
on Gitea. UTC is fine (the release name says "UTC"; it is 2 h behind Dutch summer time, and nobody cared).
|
||||
- The GitHub release's link is built from the tag alone (`.../releases/download/<tag>/steamify-cachyos-<tag
|
||||
without v>-x86_64.iso`), so the Gitea file name must be that: check it with `curl -I -L` (the monitor in the
|
||||
session did: HTTP 200 = the whole chain is right).
|
||||
|
||||
## Gitea side: what the runner and the server need
|
||||
|
||||
- **Runner** (act_runner in Docker Compose, network `gitea_gitea`): `config.yaml` -> `container: privileged: true`
|
||||
(the workflow's `options: --privileged` is **ignored** otherwise; symptom: `mount: .../airootfs/proc: permission
|
||||
denied`, `failed to setup chroot`; the job log shows `Privileged:false`). ~20 GB free disk.
|
||||
- **Upload limit: `[repository.release] FILE_MAX_SIZE = 10240`** in `app.ini` (MB; default 2048), restart Gitea.
|
||||
`[attachment] MAX_SIZE` is for issues, **not** releases: symptom `status: 413` on `.../releases/<id>/assets`.
|
||||
The release is created *before* the upload, so a failed run leaves an empty release: delete it.
|
||||
- Logs of a public repo's run are readable without login: `.../actions/runs/<run>/jobs/<job>/logs` (job numbers
|
||||
count up; re-runs get a new job number). Good for a monitor; no `gh` for Gitea.
|
||||
- Mirror sync is set to 10 min by the user with their own token; "Synchronize Now" or the API
|
||||
(`POST /api/v1/repos/<repo>/mirror-sync`) for now. `GIT_UPRISER_TOKEN` (secret in the ISO repo) would make GitHub
|
||||
trigger it; not needed.
|
||||
- **Build traps in the container:** `sudo mkarchiso` in `util-iso.sh` **drops environment variables**:
|
||||
it needs `sudo --preserve-env=STEAMIFY_ISO_VERSION,STEAMIFY_BUILD_STAMP` (the first Gitea ISO came out as
|
||||
`-local-`). Step names are evaluated **before** earlier steps set env: don't put `${{ env.X }}` in a name.
|
||||
Lots of `error: command failed to execute correctly` (pacman hooks wanting systemd) are harmless.
|
||||
The bash test of the naming must go through `sudo` too, not only call `profiledef.sh` directly.
|
||||
- Kernel: the ISO is built from the generic `x86_64` repo (must boot on any PC), which lags the `v3/v4/znver4`
|
||||
repos by a day or so: 7.2.7 on the ISO while installed systems get 7.2.8. Normal, not a bug: the installer
|
||||
switches to the CPU's optimised repo (znver4 on the 9800X3D) and the first update brings the newer kernel.
|
||||
|
||||
## Steamify's own workflow (`steamify-cachyos/.github/workflows/bundle.yml`), one file for both servers
|
||||
|
||||
- Build + check run everywhere. shellcheck through `ludeeus/action-shellcheck@2.0.0` (brings its own binary:
|
||||
act_runner's image has none; GitHub's has). The artifact: `actions/upload-artifact@v4` on GitHub,
|
||||
`christopherhx/gitea-upload-artifact@v4` elsewhere. **Never write `upload-artifact@v3`**: GitHub fails the
|
||||
whole workflow for merely mentioning a deprecated version, even in a skipped step.
|
||||
- Publishing: GitHub release (`gh`, tag + `latest`) only on GitHub; on Gitea the release is `akkuman/gitea-release-action`
|
||||
with a Gitea-API version check (`RELEASE_TOKEN` secret optional, else the run's token). Steps are guarded by
|
||||
`github.server_url == 'https://github.com'` (or `!=`). YAML check without PyYAML on the Mac:
|
||||
`ruby -ryaml -e 'YAML.load_file(...)'`.
|
||||
- Steamify releases still follow the AGENTS.md flow (`release/X.Y.Z`, `bugfix/`/`feature/` branch, PR into the
|
||||
release branch, merged and **deleted** by the agent, the release PR into `main` is the user's). Even a CI-only
|
||||
change is a release (2.9.2, 2.9.3).
|
||||
|
||||
## GitHub rate limit that started this (HTTP 429)
|
||||
|
||||
`raw.githubusercontent.com` refused Valve's CEC driver source after a day of test installs (each CEC turn-on
|
||||
downloaded it again; the limit is per address, ~1 per 5 minutes seen). Fixed in Steamify 2.9.1: the driver is
|
||||
cached in `/var/cache/steamify/cros-ec-cec-<sha256:12>.c` (named after its checksum: a new pin downloads once and
|
||||
deletes the old file; a damaged file is fetched again). The test VMs share the host's `~/vms/pkg-cache/steamify`
|
||||
(bound over `/var/cache/steamify` by `vmsuite.sh`, `vmbootloadertest.sh`, `vminstall-live.sh`); hw block 50 tests
|
||||
the cache offline (`file://`). The source repo `evlaV/linux-integration` is an unofficial mirror without releases:
|
||||
if it ever vanishes, ship the file with Steamify.
|
||||
|
||||
## Checklist for a release
|
||||
|
||||
1. Steamify released? (`gh release list -R theupriser/steamify-cachyos`). The ISO gets the newest.
|
||||
2. `gh workflow run iso-1-github-tag.yml -R theupriser/steamify-cachyos-live-iso --ref master -f kind=dev` (test) or
|
||||
`-f kind=release`. Normally `bundle.yml` does this when a version is published (a release branch push -> dev).
|
||||
3. Watch: tag on Gitea (`.../api/v1/repos/theupriser/steamify-cachyos-live-iso/tags`), the run's log (above),
|
||||
then `curl -I -L` the GitHub link. Retention deletes the oldest automatically (10 dev, 10 real). By hand: `gh release delete <tag>
|
||||
--cleanup-tag -y`; the sync removes the tag on Gitea; delete a leftover tagless Gitea release by hand.
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
name: vm-install
|
||||
description: Use when there is no Steamify test VM yet, the VM should be reinstalled from scratch, or an ISO (CachyOS, later the Steamify CachyOS ISO) should be installed unattended into a VM - scripts/vminstall.sh installs it without any manual step and leaves the snapshots clean and ssh-ready.
|
||||
---
|
||||
|
||||
# Installing the test VM unattended
|
||||
|
||||
`scripts/vminstall.sh` turns an ISO into a ready test VM, no clicking:
|
||||
|
||||
```bash
|
||||
VM_DIR=~/vms/steamify-vm scripts/vminstall.sh --fremont # run.sh flags pass through
|
||||
VM_DIR=~/vms/iso-vm scripts/vminstall.sh --iso ~/Downloads/steamify-cachyos-<date>-x86_64.iso
|
||||
```
|
||||
|
||||
Result in `$VM_DIR`: `disk.qcow2` with the snapshots `clean` (just
|
||||
installed) and `ssh-ready` (booted once, SSH/sudo/Plasma checked), their
|
||||
`vars.*.fd`, `run.sh` (this repo's), `vm-user`, `ssh-key`, `repo-path`,
|
||||
`known_hosts`, `install.log`. Every other script (`vmreset.sh`, `vmwatch.sh`,
|
||||
...) reads the user, key and repo from there: pass the same `VM_DIR`.
|
||||
|
||||
## What gets installed
|
||||
|
||||
CachyOS with the ISO's own headless installer (`cachyos-installer`,
|
||||
`"headless_mode": true`): KDE Plasma, plasma-login-manager, btrfs with the
|
||||
default subvolumes, Limine, `linux-cachyos` + `linux-cachyos-lts`, fish.
|
||||
Then `share/vminstall-post.sh` in the new system: hostname `steamify-vm`,
|
||||
sshd with the chosen key, passwordless sudo, the test autologin into Plasma
|
||||
(`/etc/plasmalogin.conf.d/00-test-autologin.conf`), `en_US.UTF-8`, the host's
|
||||
timezone, tmux + shellcheck, Limine `default_entry: 2` (the installer's
|
||||
config points at the folder, so the first boot would wait in the menu).
|
||||
|
||||
## Login (by hand)
|
||||
|
||||
- User: your host username (`id -un`), or `VM_USER=...`.
|
||||
- Password: **`steamify`** (user and root), or `VM_PASSWORD=...`.
|
||||
- SSH: `~/.ssh/steamify-vm_ed25519`. Made once, on the first install (the
|
||||
script asks: that new key, or one of your own `~/.ssh/*.pub`), then always
|
||||
used. An existing key is never overwritten. The VM's host key is kept in
|
||||
`$VM_DIR/known_hosts`, never in `~/.ssh/known_hosts`.
|
||||
|
||||
## How it works
|
||||
|
||||
1. The ISO's kernel and initramfs are copied out (`udisksctl loop-setup`, no
|
||||
root) and booted directly (`VM_KERNEL`/`VM_INITRD`/`VM_APPEND` in
|
||||
`run.sh`) with `systemd.run=` (+ `systemd.wants=kernel-command-line.service`,
|
||||
or the unit is only generated) running `curl http://10.0.2.2:<port>/vminstall-live.sh | bash` as root.
|
||||
2. `scripts/vminstall-server.py` serves the live script, `settings.json`,
|
||||
`vminstall.env` and the post script on 127.0.0.1; the guest reaches the
|
||||
host at 10.0.2.2 (QEMU user networking, any host network;
|
||||
`VM_HOST_IP` for a bridged setup). The live system PUTs its log
|
||||
(`install-www/install.log`, every 20 s) and the status back.
|
||||
3. The live desktop shows a Konsole following the install log; the serial
|
||||
console is logged to `$VM_DIR/serial.log`, and root logs in there
|
||||
without a password over `$VM_DIR/serial.log.sock` (for debugging).
|
||||
4. After `== ok` the VM powers off, `clean` is taken, it boots once for the
|
||||
checks, powers off, `ssh-ready` is taken.
|
||||
|
||||
Takes 15-30 minutes (mirrors, packages). Run it with `run_in_background`
|
||||
and wait with an until-loop on the log, e.g.
|
||||
`until grep -qaE '^== (ok|failed)' $VM_DIR/install-www/install.log; do sleep 20; done`.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Refuses an existing `disk.qcow2` without `--force` (asks YES), and a
|
||||
running VM (port 2222).
|
||||
- Never edit a script in place while it runs (bash reads it as it goes; Python `open(p,"w")` and editors that rewrite do this). `sed -i` or writing a copy and `mv` swaps the file: the running one keeps its old copy.
|
||||
- Don't match your own wait loop with `pgrep -f`/`pkill -f` on a pattern that
|
||||
is part of that loop's command line.
|
||||
- Mirror 404s during the install are normal for an older ISO (pacman moves
|
||||
on); `fatal library error, lookup self` in the chroot is harmless.
|
||||
- QMP screenshots don't work (virgl, "no surface"): watch the window, the
|
||||
serial log, or the install log.
|
||||
- `pkill -f`/`pgrep -f` with a pattern that also appears in your own command
|
||||
line kills or matches your own shell (exit 144). Use `[q]emu` bracket
|
||||
patterns, or kill by PID from `ps -eo pid,args`, in a separate command.
|
||||
- A wait loop that runs `pgrep -f "x"` over ssh matches its own ssh command:
|
||||
write `"[x]"`. And `systemctl --wait start` on a unit that already
|
||||
finished waits for ever; poll `systemctl is-active` instead.
|
||||
- The live script must not wait for `systemctl is-system-running --wait`: it
|
||||
is itself the running `kernel-command-line.service` job (deadlock). It waits
|
||||
for `pacman-init.service` to be active instead (else "no secret key
|
||||
available to sign with").
|
||||
- Only one VM at a time (port 2222); a second `vminstall.sh` refuses while
|
||||
qemu runs. Run several installs one after the other from a script, and
|
||||
give `--force` runs a terminal or delete `disk.qcow2` first (the YES
|
||||
prompt reads /dev/tty).
|
||||
- The guest's login shell is fish: send bash through stdin
|
||||
(`ssh ... 'bash -s' < script.sh`), not loops on the command line.
|
||||
- `/boot` is root-only: use `sudo` for `ls`/`cat` there, or checks report
|
||||
"no entries" for nothing.
|
||||
- `ssh` "timed out during banner exchange" only means QEMU accepted the
|
||||
forwarded port: the guest's sshd isn't answering (still booting, waiting in
|
||||
a boot menu, or a firewall). It says nothing more.
|
||||
- Never run `systemctl reboot --firmware-setup --dry-run` to "check": it
|
||||
still sets the EFI flag and the next boot lands in the UEFI setup menu.
|
||||
`bootctl status` shows `Boot into FW: supported` normally, `active` = set.
|
||||
|
||||
## The Steamify CachyOS ISO
|
||||
|
||||
`--iso <steamify iso>` (archiso with `cachyos-installer`). The headless
|
||||
installer doesn't run Calamares, so `vminstall-live.sh` runs the ISO's
|
||||
`steamify-install /mnt $VM_USER "$VM_STEAMIFY"` itself after the install
|
||||
(empty `VM_STEAMIFY` = the Steamify page's default, everything on; ids to
|
||||
narrow; `skip` = plain CachyOS) and fails the install if its log doesn't end
|
||||
`exit: 0`. The log ends up in `/var/log/steamify-install.log`. The post script
|
||||
keeps Steamify's gaming-mode login (SDDM,
|
||||
`/etc/sddm.conf.d/10-gamescope-autologin.conf`) instead of forcing the Plasma
|
||||
autologin, and the first-boot check then wants the display manager, not
|
||||
plasmashell (gamescope doesn't render in the VM: a black window is normal).
|
||||
|
||||
## Other options
|
||||
|
||||
- `VM_BOOTLOADER=limine|systemd-boot|grub` (default limine) goes into the
|
||||
installer's settings. Use one `VM_DIR` per loader (`~/vms/bl-grub`, ...).
|
||||
cachyos-installer leaves systemd-boot with `#timeout 3` and no default (it
|
||||
waits in the menu for ever): the post script writes `timeout 3` and
|
||||
`default linux-cachyos.conf`, like it sets Limine's `default_entry`.
|
||||
- The installer enables **ufw**, which drops the host's ssh
|
||||
(`[UFW BLOCK] DPT=22` in the kernel log): the post script runs
|
||||
`ufw allow 22/tcp`.
|
||||
- `VM_CACHE=<dir>` (default `~/vms/pkg-cache`, empty = off) is a host package
|
||||
cache shared as 9p tag `cache`, bound over pacman's cache in the live system
|
||||
and in the new one (Steamify's packages): the first install fills it.
|
||||
|
||||
## The whole automated test: `scripts/vmtest.sh`
|
||||
|
||||
`scripts/vmtest.sh [--install] [--window] [--screen] [boot | <loader>... | <suite>...]`: no argument runs
|
||||
everything: the boot loader test (below) and every suite in `share/vmtest/` (`cli`, `menu`, `hw`, `installer`,
|
||||
`toggles`: the TESTPLAN.md rows that need no person, run by `scripts/vmsuite.sh` on the plain VM
|
||||
`~/vms/steamify-vm`, each block from a fresh `ssh-ready`). `--screen` runs it in a detached `screen`
|
||||
session (`screen -r vmtest`); output goes to `~/vms/test.log` (one shared Konsole follows it when headless
|
||||
on a desktop), the summary to `~/vms/last-test.txt`, exit status = failed checks. Adding a check: a block file
|
||||
in `share/vmtest/<suite>/NN-name.sh` (guest side, prelude helpers `pass/fail/skip/info/sf/is/expect_on`) or
|
||||
`NN-name.host.sh` (host side, for reboots and host scripts); `# env: VAR=x` in a block sets it for the VM
|
||||
start; `ONLY=<prefix> scripts/vmsuite.sh <suite>` runs one block. Rows that need a person (U1-U9, R2.2,
|
||||
H-Real) are printed as SKIP at the end. Pitfalls met while building it: atomic file swaps (`os.replace`)
|
||||
lose the executable bit (`chmod --reference` or call scripts with `bash`); a suite block that reboots must
|
||||
be a `.host.sh` block; ssh has no Plasma session, the prelude exports one; a one-off network failure can fail
|
||||
an install step (LED driver from the AUR), rerun the block before suspecting Steamify.
|
||||
|
||||
Gotchas found on 2026-09-29: never ssh to a VM with its `VM_DIR` settings while it installs (a leftover loop, also
|
||||
one started from a laptop command that was stopped, saves the *live ISO's* host key in `$VM_DIR/known_hosts` and
|
||||
the install then hangs at its first-boot ssh check: `ssh-keygen -R "[localhost]:<port>" -f $VM_DIR/known_hosts`);
|
||||
`vminstall.sh` now never uses root as the guest user (WSL runs as root: `theupriser`), takes the ISO's loop
|
||||
device under a lock (parallel installs collided), tolerates the poweroff dropping ssh, waits only for its own
|
||||
qemu and stops at once when the live system reports `== failed: ...`. A failed install used to look like
|
||||
"still installing" for 90 minutes: `vmprogress.sh` shows `INSTALL FAILED: <why>`.
|
||||
|
||||
Following a run for the user (a table per VM with a bar and a total, only on a change; their terminal and
|
||||
the VM's screen): the `progress-report` skill, with `scripts/vmprogress.sh` as the monitor.
|
||||
|
||||
## Testing a boot loader: `scripts/vmtest.sh boot`
|
||||
|
||||
Run this instead of doing the steps below by hand: `scripts/vmtest.sh [--install] [--window] [loader...]`
|
||||
(default: every loader the ISO advertises, read from `calamares-online.sh`; it fails when an advertised
|
||||
loader has no test). One VM per loader in `~/vms/bl-<loader>` (`--install` builds them from the newest
|
||||
Steamify ISO, ~8 min each, ~3 min per test), headless with a Konsole on the logs (`--window` shows the VMs),
|
||||
one summary line per loader, `~/vms/bl-<loader>.test.log`, exit status = failed checks. Checks live in
|
||||
`share/bootloader-test/*.sh` (one PASS/FAIL line each); the driver is `scripts/vmbootloadertest.sh`.
|
||||
Last result (2026-09-29, ISO with Steamify 2.9.1): Limine 43, systemd-boot 42, GRUB 42 checks, 0 failed.
|
||||
The boot entry check (B1, `boot-check.sh`, after every reboot): the system booted through the loader's **own**
|
||||
EFI entry (`BootCurrent`, not `auto_created_boot_option`, a real partition GUID not `HD(0,GPT,0000...)`). It
|
||||
found that the installer never registered systemd-boot (bootctl skips the EFI variables in a chroot): OVMF then
|
||||
tried PXE/HTTP boot/EFI shell first, 4-5 minutes per boot, or "no bootable device". The ISO's `steamify-install`
|
||||
now runs `efibootmgr` from the live system (a real partition, first in the order) and mounts the new system's
|
||||
`/var/log` (`@log` subvolume) first so its logs (`steamify-install.log`, `steamify-bootentry.log`) survive.
|
||||
To read a VM's boot entries without booting it: copy `vars.fd`, `virt-fw-vars -i <copy> --print` (package
|
||||
`virt-firmware`). The power-off check accepts "loaded" or "tried at boot: No such device" (journal, not dmesg:
|
||||
the VM has no AMD GPIO controller, the module refuses on purpose).
|
||||
What only shows up per loader: Limine keeps its images under `/boot/<machine-id>/<kernel>/` and copies one
|
||||
only when its content changed (same version = untouched), and `remember_last_entry: yes` overrides
|
||||
`default_entry` (the test turns it off and restores it); GRUB's other kernel is picked with
|
||||
`GRUB_DEFAULT="1>N"` + `grub-mkconfig` (key presses miss its 5 s menu); systemd-boot with
|
||||
`bootctl set-oneshot <entry>.conf`. The "kernel update" reinstalls what the installed system's database lists,
|
||||
which can be older than what the installer got (mirror skew: 7.2.8 installed, 7.2.7 in the database): a
|
||||
downgrade, but still a version change through the same hooks.
|
||||
|
||||
Manual version of the same, for one loader:
|
||||
|
||||
Steamify's kernel-side parts (`steamify-fremont-poweroff`, `steamify-cros-ec-cec`,
|
||||
`leds-valve-dkms`) are DKMS modules for every installed kernel, only installed
|
||||
when the VM reports Fremont: start the VM with `./run.sh --fremont`, then run
|
||||
`steamify.sh --defaults --options gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff`
|
||||
from the shared repo (`sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt`).
|
||||
Then per loader: `dkms status` (3 modules x each kernel), reinstall both kernels
|
||||
+ headers (`pacman -S linux-cachyos linux-cachyos-lts` + headers: rebuilds
|
||||
DKMS, initramfs, and for GRUB the config), reboot into the default kernel and
|
||||
into the other one (systemd-boot: `bootctl set-oneshot <entry>.conf`; GRUB or
|
||||
Limine: send keys with `scripts/qmpkey.py <vm>/qmp.sock down ret ...`, or
|
||||
change `default_entry`), and check `uname -r`, `lsmod`, and `dmesg | grep steamify`.
|
||||
The legacy `drm.edid_firmware` removal (`hdmi_remove_boot_param`) is the code
|
||||
that differs per loader: seed the parameter in `/etc/default/grub`,
|
||||
`/etc/sdboot-manage.conf` or `/etc/default/limine`, source `lib/common.sh`,
|
||||
`state.sh`, `hdmi-refresh.sh` from `/mnt`, and run it. To see why a VM won't
|
||||
boot without a console, stop it, `qemu-img dd` the first 2 GB to a raw file,
|
||||
`dd skip=1M` to cut the ESP, `udisksctl loop-setup` + `mount` it (no root),
|
||||
and read `loader/loader.conf` and the entries; or boot the kernel directly with
|
||||
`VM_KERNEL/VM_INITRD/VM_APPEND=... console=ttyS0` and `VM_SERIAL=` for a log.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
name: wsl-build-host
|
||||
description: Use when building or testing the Steam Machine ISO, or running the VM tests (vmtest.sh), on the user's Windows PC - Arch Linux in WSL2 (`ssh wsl`), with podman, QEMU + KVM and WSLg windows on the Windows desktop. Covers access, syncing working trees from the laptop, the podman ISO build as root, VM windows vs headless, showing output in the user's WSL terminal, freeing disk space, and the WSL pitfalls (closing the window kills it, no GPU passthrough).
|
||||
---
|
||||
|
||||
# WSL build host (Arch on WSL2, the Windows PC)
|
||||
|
||||
The laptop is an arm64 Mac: x86_64 QEMU or containers there run emulated,
|
||||
far too slow for Plasma/Calamares or an ISO build. The PC is the x86 host:
|
||||
same workflows as `steam-machine-iso`, `vm-install` and `vmtest.sh`, with
|
||||
the differences below. Found and tried on 2026-09-28 (ISO build, ISO VM with
|
||||
a window, the loop-mount fallback). 2026-09-29: the whole `vmtest.sh` ran here, 328 checks pass in
|
||||
18 minutes (3 VMs at a time).
|
||||
|
||||
## Access
|
||||
|
||||
- `ssh wsl` = `root@10.0.0.36`, port 22 (alias in the laptop's
|
||||
`~/.ssh/config`). That's Arch's own sshd inside WSL, not Windows' OpenSSH.
|
||||
- `%UserProfile%\.wslconfig`: `networkingMode=mirrored` (WSL shares the PC's
|
||||
LAN IP), `nestedVirtualization=true` (`/dev/kvm`), `memory=` (WSL2 defaults
|
||||
to half the RAM: 30 GB seen). `/etc/wsl.conf`: `[boot] systemd=true`.
|
||||
- Installed: podman, qemu-full, edk2-ovmf (`/usr/share/edk2/x64/OVMF*.4m.fd`,
|
||||
run.sh finds it), git, rsync, python, python-pillow (`qmpshot.py` writes a
|
||||
.ppm without it). vmtest.sh also wants `screen`.
|
||||
- **The Arch window must stay open.** Closing the last WSL terminal shuts
|
||||
the distro down a little later, with every build, VM and sshd in it: SSH
|
||||
times out while the PC still pings. Ask the user to keep it open during
|
||||
runs and to reopen it after a Windows reboot. After such a restart, delete
|
||||
half-written VM disks before rerunning (`vminstall.sh` refuses an
|
||||
existing `disk.qcow2`, and `--force` asks).
|
||||
|
||||
## Differences from a normal Linux host
|
||||
|
||||
- User is **root**: no `sudo`, `systemd-run` without `--user`, paths under
|
||||
`/root/projects/` and `/root/vms/`. Units have no `$HOME`: set `HOME=/root`
|
||||
for scripts started with `systemd-run` (vminstall.sh needs it).
|
||||
- udisks: `vminstall.sh` uses `udisksctl` when it's there (udisks2 is
|
||||
installed on this box now, and works) and falls back to `losetup -P` +
|
||||
`mount` as root without it. The ISO's loop device is set up under a lock
|
||||
(parallel installs collided: "Device or resource busy").
|
||||
- Root is the host user: `vminstall.sh` then makes `theupriser` the guest
|
||||
user (never root).
|
||||
- `pkill -x qemu-system-x86_64` never matches (names over 15 characters):
|
||||
`pkill -f '^[q]emu-system'`.
|
||||
|
||||
## Getting the code there
|
||||
|
||||
The WSL clones have no GitHub key: sync the laptop's working trees
|
||||
(uncommitted changes included) instead of pulling. `vmtest-remote.sh`
|
||||
pulls the pushed branch on the remote, so it needs a GitHub key there first
|
||||
(and `REMOTE_REPO=/root/projects/steamify-cachyos-dev`).
|
||||
|
||||
```bash
|
||||
cd ~/Projects && rsync -az --exclude out --exclude build \
|
||||
steamify-cachyos-live-iso steamify-cachyos steamify-cachyos-dev wsl:projects/
|
||||
ssh wsl 'find ~/projects -name __pycache__ -prune -exec rm -rf {} +; chown -R root: ~/projects'
|
||||
```
|
||||
|
||||
Both fixups are needed: rsync keeps the laptop's uid (git then refuses with
|
||||
"dubious ownership"), and a synced `patches/__pycache__` breaks
|
||||
`steamify-prepare.sh` ("Is a directory").
|
||||
|
||||
## Building the ISO (~8 minutes)
|
||||
|
||||
```bash
|
||||
ssh wsl 'cd ~/projects/steamify-cachyos-live-iso && ./steamify-prepare.sh ~/projects/steamify-cachyos'
|
||||
ssh wsl 'cd ~/projects/steamify-cachyos-live-iso && rm -rf build out && systemd-run --collect -q -u isobuild-$(date +%s) --working-directory=$PWD bash -c "podman run --rm -t --pids-limit=-1 --ulimit nofile=65536:65536 --privileged --network=host -v /root/projects/iso-cache:/var/cache/pacman/pkg -v $PWD:/iso -w /iso docker.io/cachyos/cachyos:latest bash -c '\''pacman-key --init && pacman-key --populate && pacman -Syu --noconfirm --needed archiso mkinitcpio-archiso git squashfs-tools grub sudo && ./build-live-modules.sh && ./build-calamares-modules.sh && { ./buildiso.sh -p desktop -w || ./buildiso.sh -p desktop -c -w; }'\'' > /root/projects/iso-build.log 2>&1"'
|
||||
```
|
||||
|
||||
Wait in one background command, not a polling loop:
|
||||
`ssh wsl 'until ! podman ps -q | grep -q .; do sleep 20; done; tail -c 1500 ~/projects/iso-build.log; ls -la ~/projects/steamify-cachyos-live-iso/out/desktop/'`.
|
||||
Never start a build while `podman ps` shows one. Output:
|
||||
`/root/projects/steamify-cachyos-live-iso/out/desktop/steamify-cachyos-local-x86_64.iso` (a build by hand
|
||||
has no release tag, so it's named `local`, label `STEAMIFY_<version>_LOCAL`; releases are built on the Gitea
|
||||
mirror from a GitHub tag, see the ISO repo's `iso-1-github-tag.yml`) (from
|
||||
Windows Explorer: `\\wsl$\<distro>\root\projects\...`). The trailing
|
||||
`chown: missing operand` / "unknown error" is harmless. mksquashfs shows no
|
||||
progress in the log; the growing `build/iso/arch/x86_64/airootfs.sfs`
|
||||
(~3.1 GB when done) is the measure.
|
||||
|
||||
## VM windows on the Windows desktop (WSLg)
|
||||
|
||||
There is a desktop after all: WSLg (`/tmp/.X11-unix/X0`,
|
||||
`/mnt/wslg/runtime-dir/wayland-0`) puts QEMU's GTK window on the user's
|
||||
Windows desktop. SSH sessions and units lack the variables, so set them:
|
||||
`DISPLAY=:0 WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir`.
|
||||
|
||||
- A VM for the user to watch or drive: that env plus e.g.
|
||||
`VM_SERIAL=/root/vms/iso-vm/serial ISO_VM_DIR=/root/vms/iso-vm scripts/vmisoboot.sh --fresh --iso <iso>`.
|
||||
The user may click through it themselves: check the disk size / serial
|
||||
log before assuming it still sits at the live session.
|
||||
- Automated runs stay headless (`--headless` / `VM_HEADLESS=1`, vmtest.sh's
|
||||
default); `qmpshot.py` works then.
|
||||
- GPU: no passthrough of the NVIDIA card. WSL2 only has the paravirtual
|
||||
`/dev/dxg`, no PCI device for VFIO, and Windows keeps driving the card.
|
||||
run.sh's default virgl (`virtio-vga-gl`, `gl=on`) renders through WSLg's
|
||||
d3d12 Mesa on it, but QMP screenshots then give "no surface".
|
||||
|
||||
## Freeing disk space
|
||||
|
||||
Everything here is re-creatable (VMs, `~/vms/pkg-cache`, `~/projects/iso-cache`, `build/`, the podman image):
|
||||
`rm -rf` them when the VMs aren't running (`pgrep -f "^[q]emu"`), `podman system prune -a -f`,
|
||||
`pacman -Scc --noconfirm`, `fstrim -av`. That freed 68 GB inside WSL (81 -> 13 GB), but Windows only gets it
|
||||
back when the vhdx is sparse: in an admin PowerShell `wsl --shutdown`, `wsl --manage archlinux --set-sparse
|
||||
true` (seconds), reopen the Arch window, `fstrim -av` again; else `compact vdisk` in diskpart. A rebuild costs
|
||||
the caches: a full `vmtest.sh --install` (three loader VMs ~12 min in parallel, the suite VM ~12 min from a
|
||||
CachyOS ISO the user keeps in `/root/`), and an ISO build ~6-8 min. Don't `wsl --shutdown` while a job runs.
|
||||
Hand-built ISOs are called `steamify-cachyos-local-x86_64.iso`; releases are built on the Gitea mirror
|
||||
(`steamify-iso-release` skill). Windows starting WSL at logon is the user's own business, not Steamify's.
|
||||
|
||||
## Running the tests here
|
||||
|
||||
`scripts/vmtest.sh --screen` from `~/projects/steamify-cachyos-dev` (as root,
|
||||
`HOME=/root`), then read `~/vms/last-test.txt`; follow it as the
|
||||
`progress-report` skill says. Needs `~/vms/steamify-vm` (`VM_STEAMIFY=skip
|
||||
VM_DIR=/root/vms/steamify-vm scripts/vminstall.sh --iso <CachyOS ISO>`, the
|
||||
user keeps one in `/root/`) and `~/vms/bl-<loader>` (`vmbootloadertest.sh
|
||||
<loader> --install`, or `vmtest.sh --install`). Memory: WSL sees 30 GB, fine
|
||||
for 3 VMs at 4 GB (and the 8 GB install VMs); raise `memory=` in
|
||||
`.wslconfig` before `MAX_PARALLEL=4` or `VM_MEM=8G`.
|
||||
|
||||
## Showing output in the user's WSL terminal
|
||||
|
||||
Their visible terminal is the `-bash` whose parent is WSL's `/init`
|
||||
(`ps -eo pid,ppid,tty,args --forest`), usually `/dev/pts/0`. Not the
|
||||
`login -- root` one: a hidden console getty. Stream a log into it as a unit
|
||||
so it can be stopped:
|
||||
`systemd-run --collect -q -u showlog bash -c "tail -n 20 -F <log> > /dev/pts/0 2>&1"`;
|
||||
stop with `systemctl stop showlog` (Ctrl+C there doesn't). Tell the user
|
||||
not to type there while it runs. A header printed before a busy log scrolls
|
||||
away at once.
|
||||
|
||||
Reporting progress (the table, only on a change), a headless VM's screen in a
|
||||
window (`scripts/vmview.py`) and looking into a stuck job: the
|
||||
`progress-report` skill.
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Sync git.upriser.nl mirror
|
||||
|
||||
# Trigger the Gitea pull-mirror immediately instead of waiting for its interval,
|
||||
# so Gitea builds/tests right after any branch or tag lands on GitHub (the mirror has no sync interval of its own).
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags: ['*']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
# Gitea also reads .github/workflows on the mirror; only run on GitHub
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger mirror-sync
|
||||
env:
|
||||
GITEA_URL: ${{ secrets.GIT_UPRISER_URL }}
|
||||
GIT_UPRISER_TOKEN: ${{ secrets.GIT_UPRISER_TOKEN }}
|
||||
# Gitea "owner/repo"; defaults to the same path as on GitHub
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.repository }}
|
||||
run: |
|
||||
curl --fail-with-body -sS --retry 5 --retry-delay 10 --retry-all-errors -X POST \
|
||||
-H "Authorization: token ${GIT_UPRISER_TOKEN}" \
|
||||
"${GITEA_URL%/}/api/v1/repos/${GITEA_REPO}/mirror-sync"
|
||||
+11
-3
@@ -5,16 +5,23 @@
|
||||
!/README.md
|
||||
!/CHANGELOG.md
|
||||
!/run.sh
|
||||
!/run.ps1
|
||||
!/get-iso.sh
|
||||
!/get-iso.ps1
|
||||
!/add-ssh-key.ps1
|
||||
!/TESTPLAN.md
|
||||
!/TODO.md
|
||||
!/tests
|
||||
!/nvidia/
|
||||
!/AGENTS.md
|
||||
!/.github/
|
||||
!/scripts/
|
||||
!/share/
|
||||
!/.claude/
|
||||
|
||||
share/*
|
||||
!share/guest-ssh-setup.sh
|
||||
!share/vminstall-live.sh
|
||||
!share/vminstall-post.sh
|
||||
!share/bootloader-test/
|
||||
!share/vmtest/
|
||||
|
||||
# Belt and braces, anywhere in the tree
|
||||
*.iso
|
||||
@@ -26,3 +33,4 @@ share/*
|
||||
*.log
|
||||
*.tmp
|
||||
host-keys.pub
|
||||
__pycache__/
|
||||
@@ -0,0 +1,66 @@
|
||||
# steamify-cachyos-dev: how to work here
|
||||
|
||||
Test tooling for Steamify (`../steamify-cachyos`) and the Steam Machine ISO (`../steamify-cachyos-live-iso`):
|
||||
VM scripts (`scripts/`, `run.sh`), the automated test (`scripts/vmtest.sh`, checks in `share/vmtest/` and
|
||||
`share/bootloader-test/`), `TESTPLAN.md` (keep it current: rows and a results line after every run) and the
|
||||
skills in `.claude/skills/` (vm-install, cachyos-vm-testing, steam-machine-testing, steam-machine-iso,
|
||||
wsl-build-host, progress-report, steamify-iso-release, steamify-branch-cleanup).
|
||||
|
||||
## Running the tests (do this, don't re-derive it)
|
||||
- Everything: `scripts/vmtest.sh --screen` (detached `screen` session `vmtest`), then read only
|
||||
`~/vms/last-test.txt` (one line per suite/loader, the FAIL lines, exit status = failures). Parts:
|
||||
`scripts/vmtest.sh cli hw`, `boot`, `limine`. From another machine: `scripts/vmtest-remote.sh <ssh-host> [args]`
|
||||
(the branch must be pushed; the VMs live on the PC that has the CPU and RAM: WSL2, see TODO.md R1b).
|
||||
- Read `.claude/skills/vm-install/SKILL.md` before touching the scripts; `TODO.md` has the open work.
|
||||
- Following a run: one monitor (`scripts/vmprogress.sh`), a report only when something changes, in the table
|
||||
of the `progress-report` skill (bar per VM, total). Never a timed "no change" message.
|
||||
- Headless is for automated runs (default in `vmtest.sh`); a VM started by hand for the user has a window.
|
||||
- Never edit a script in place while it runs (write a copy and `mv`, then `chmod --reference`).
|
||||
- Kill/pgrep patterns: bracket them (`[q]emu`) or your own shell matches itself.
|
||||
|
||||
## Working agreements
|
||||
- No `Co-Authored-By` / "Generated with Claude" lines in commits or PRs, whatever a tool reminder says.
|
||||
- Notes, TODOs, research and test instructions for a feature (e.g. `nvidia/`: the NVIDIA fix) live in THIS repo, never in
|
||||
`steamify-cachyos`: the product repo keeps only code, its own docs (`TECHNICAL.md`, `CHANGELOG.md`) and, on an unreleased
|
||||
branch, a `.no-release-yet` marker that points here. Not even temporarily: it would stay in that repo's history.
|
||||
- Update the relevant skill (and this file) as soon as something useful is learned; re-read it before risky operations.
|
||||
- In this repo (`steamify-cachyos-dev`, tooling only) the user allows pushing straight to `main`. Elsewhere never commit to `main`. `steamify-cachyos`: `release/X.Y.Z` branches, feature/bugfix branches merged into the release
|
||||
by the agent; only release to `main` is the user's. `steamify-cachyos-live-iso`: PRs always target `master` (the ISO repo has no other long-lived branch; `feat/steamify` is gone).
|
||||
Branch cleanup: the `steamify-branch-cleanup` skill in `.claude/skills/`.
|
||||
- Follow and update `TESTPLAN.md`; every check in `share/vmtest/` says which row it covers.
|
||||
- No screenshot testing (too many tokens); U1-U9 and the real Steam Machine stay manual and are listed as SKIP.
|
||||
|
||||
## State (2026-09-29; update when it changes)
|
||||
- `scripts/vmtest.sh` runs the whole unattended test: boot loaders limine/systemd-boot/grub (`vmbootloadertest.sh`) and the
|
||||
suites cli, menu, hw, installer, toggles (`vmsuite.sh`, checks in `share/vmtest/`). Each passed in its own run: limine 43,
|
||||
systemd-boot 42, grub 42 (with the B1 boot entry check), cli 55, menu 36, hw 77, installer 4, toggles 42 checks.
|
||||
- 2026-09-29, on the PC (WSL2): the first complete parallel run, **328 pass, 0 fail in 18 minutes** (base image +
|
||||
qcow2 overlays, own ssh port per job, package cache, `VM_MEM=4G`, `MAX_PARALLEL=3`; 5 worked on the 9800X3D). On a new machine: one full
|
||||
`scripts/vmtest.sh --screen`, read `~/vms/last-test.txt`, fix what fails.
|
||||
- Open work: `TODO.md` (the CEC driver download, the GitHub Actions workflow, P3).
|
||||
|
||||
## Releases (2026-09-29; details in the `steamify-iso-release` skill)
|
||||
- Steamify: `release/X.Y.Z` -> PR into `main` (the user's) -> GitHub release, and the same workflow on the Gitea
|
||||
mirror. A new Steamify release starts the ISO's release (`ISO_DISPATCH_TOKEN`); a CEC driver download is cached.
|
||||
- The ISO: GitHub makes the tag `vX.Y.Z-[dev.]YYYY.MM.DD-HHMM` (UTC) and a release with the notes; the Gitea
|
||||
mirror (git.upriser.nl, pull mirror, 10 min) builds on that tag and attaches the 3.2 GB ISO, named after the tag.
|
||||
A release that exists only on the mirror is deleted by its next sync: the tag must come from GitHub.
|
||||
- Merged feature/bugfix branches are deleted at once (`gh pr merge --delete-branch`). Push every commit.
|
||||
|
||||
## Where the tests run
|
||||
The tests run on the user's PC (Ryzen 9800X3D, 64 GB, **WSL2**), started from a laptop over ssh (the user's own skill, or
|
||||
`scripts/vmtest-remote.sh <ssh-host> [args]`: pulls the pushed branch there, starts `vmtest.sh --screen`, waits for
|
||||
`~/vms/last-test.txt`). WSL specifics (access `ssh wsl` = root@10.0.0.36, keep the Arch window open or WSL shuts
|
||||
down, syncing from the laptop, the podman ISO build, VM windows through WSLg, output in the user's WSL terminal, no GPU
|
||||
passthrough): `.claude/skills/wsl-build-host/SKILL.md`. `vminstall.sh` works there without udisks (losetup as root).
|
||||
|
||||
## Problems found in the Steamify ISO (fix in `steamify-cachyos-live-iso`, PRs into `master`)
|
||||
- Fixed (PR #2): cachyos-installer's `bootctl install` runs in a chroot and writes no EFI boot entry, so systemd-boot
|
||||
only booted through the disk's fallback path (in OVMF after 4-5 minutes of network boot, every boot).
|
||||
`steamify-install` now registers it with efibootmgr from the live system.
|
||||
- Fixed (PR #3): `/var/log` (`@log`) wasn't mounted when `steamify-install` ran, so its logs were hidden after boot.
|
||||
- cachyos-installer leaves systemd-boot with `#timeout 3` and no default entry: a real machine waits in the menu for ever
|
||||
(the test setup papers over it in `share/vminstall-post.sh`).
|
||||
- It enables ufw, which drops ssh from the host (the test setup runs `ufw allow 22/tcp`).
|
||||
- An installed system's package database can be older than what the installer got (mirror skew): a kernel "update" may
|
||||
downgrade. Not a Steamify bug; noted in the vm-install skill.
|
||||
@@ -4,6 +4,58 @@ All notable changes, per version and per commit. Versions follow
|
||||
[Semantic Versioning](https://semver.org/) and were numbered from the start
|
||||
of the history.
|
||||
|
||||
## 0.5.0 - 2026-10-02
|
||||
|
||||
The whole automated test (boot loaders plus suites), the Steamify ISO pipeline,
|
||||
the NVIDIA notes, and the Steamify unit tests and hardware test moved here.
|
||||
|
||||
- **feat: `scripts/vmtest.sh` runs everything: the boot loader test for every loader the ISO advertises, the plain-PC install check and the `cli`, `menu`, `hw`, `installer`, `toggles` and `nvidia` suites** (`ee20bd9`, `c91277a`, `fe5251f`, `b91ef91`, `6d264ac`, `343ff1e`, `5d168ea`)
|
||||
- `vmbootloadertest.sh --quick [--generic]`: only what the ISO's install must get right, a Steam Machine gets all its items and modules, a plain PC none (`fafa38f`, `459832c`)
|
||||
- faster and parallel runs (`MAX_PARALLEL`), a remote runner, progress lines while the tests run (`0c84b8b`, `540139b`)
|
||||
- the boot entry check (BootCurrent) after every reboot (`27b7445`), the CEC driver cache blocks (`e6119ca`), `vmview.py` (`4cc4046`)
|
||||
- **feat: `scripts/vminstall.sh` installs any boot loader and runs the Steamify ISO's Steamify step, with a package cache** (`8646b3c`)
|
||||
- parallel installs under a lock, never root as the guest user, headless in CI, stops at once when the live system reports a failed install (`9d53740`, `d5cfadf`, `e929444`, `a34efb0`)
|
||||
- **feat: `scripts/vmisobuild.sh` builds the Steamify ISO in the test VM** (`bc06bc2`)
|
||||
- **feat: the `progress-report` skill and `scripts/vmprogress.sh`** (`fd9ecf4`)
|
||||
- **docs: skills and AGENTS.md: the ISO release pipeline, the WSL build host, the VM tests, token-cheap ISO-VM testing** (`0d0279b`, `a1f8089`, `cb247b9`, `0052f1b`)
|
||||
- **docs: `nvidia/`: TODO, research and test instructions for the NVIDIA fix, with the results of the hardware test on the RTX 5080 PC** (`d045b82`, `0052566`, `fa67d65`)
|
||||
- **test: the Steamify unit tests (`controllers-test.sh`, `nvidia-test.sh`) and the NVIDIA hardware test move here from steamify-cachyos, in `tests/`; `REPO` points at the checkout** (`899e9af`)
|
||||
- **test: `vmstate.sh` reports Steam's autostart entry (silent, plain, none) instead of the old systemd unit** (`66ec27d`)
|
||||
- Steamify 2.11.0 tested here before its release: three ISO installs (defaults, everything on, Fremont everything on) and the full suite on a plain CachyOS VM, 269 checks passed, 0 failed, 1 known skip. Found on the way: the first-login skip failed in a bundle (`SCRIPT_DIR` unbound) and the Steam Machine serial step failed in the installer's chroot (read-only `/sys`); both fixed in Steamify.
|
||||
- Known: `vmtest.sh` and `vmsuite.sh` write fixed files in `/tmp` (`/tmp/vmtest-*.out`, `/tmp/vmsuite-*-reset.out`, `/tmp/vminstall-iso.lock`): a second user on the same host gets "Permission denied" on files the first one left.
|
||||
|
||||
## 0.4.0 - 2026-09-28
|
||||
|
||||
An unattended VM install, a test plan, and the lessons from testing
|
||||
Steamify 2.7.0.
|
||||
|
||||
- **feat: scripts/vminstall.sh installs an ssh-ready test VM unattended; TESTPLAN.md; vm-install skill**
|
||||
- `scripts/vminstall.sh [--force] [--iso <file>] [--fremont]`: CachyOS from
|
||||
the ISO's headless `cachyos-installer` (kernel booted directly with
|
||||
`systemd.run=` + `systemd.wants=`, files and log over
|
||||
`scripts/vminstall-server.py`), then `share/vminstall-post.sh`: sshd,
|
||||
passwordless sudo, test autologin, Limine `default_entry`. Snapshots
|
||||
`clean` and `ssh-ready`. User: the host's username, password `steamify`;
|
||||
key `~/.ssh/steamify-vm_ed25519` (made once, never overwritten).
|
||||
- `scripts/common.sh`: `VM_DIR`, and the VM's user, key and known_hosts from
|
||||
it; `run.sh`: `VM_ISO`, `VM_KERNEL`/`VM_INITRD`/`VM_APPEND`, `VM_SERIAL`,
|
||||
`repo-path`.
|
||||
- `scripts/steammachine-addkey.sh`: the VM key on the Steam Machine too.
|
||||
- `TESTPLAN.md`: regression, the app, features per release, Steam Machine
|
||||
hardware (faked and real), results log. `scripts/qmpkey.py` presses keys
|
||||
in the VM (drives the app).
|
||||
- Fixes: `vmreset.sh` doesn't restart plasmalogin when the snapshot already
|
||||
logged in (HELPER_TTY_ERROR); `vmstate.sh` reads `~/.local/state/steamify`;
|
||||
`vminstallsim.sh` passes `LANG=C.UTF-8` like the ISO.
|
||||
- Skills: update `ssh-ready` at the start of every session; the ISO is built
|
||||
in the test VM.
|
||||
|
||||
## 0.3.0 - 2026-09-27
|
||||
|
||||
The Steam Machine ISO and Steamify's install-time mode.
|
||||
|
||||
- `f8e2473` **feat: steam-machine-iso skill (ISO build in podman on the Steam Machine, installer simulation, first-login test) and scripts/vminstallsim.sh**
|
||||
|
||||
## 0.2.0 - 2026-09-24
|
||||
|
||||
Watchable test runs and the lessons from testing cachyos-gamescope-boot 0.7.0
|
||||
|
||||
@@ -11,116 +11,116 @@ tracked file to it explicitly.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux host with KVM, `qemu-system-x86_64` (GTK/OpenGL display, virtfs/9p) and `qemu-img`
|
||||
- An x86_64 Linux host with KVM (not Windows or macOS: `run.sh` relies on KVM,
|
||||
memfd and GTK/virgl, and CachyOS only exists for x86_64),
|
||||
`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 <vm-user>`.
|
||||
|
||||
## Quick start
|
||||
## Quick start: unattended install
|
||||
|
||||
```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
|
||||
./get-iso.sh # latest CachyOS desktop ISO (verified)
|
||||
VM_DIR=~/vms/steamify-vm scripts/vminstall.sh --fremont # 15-30 min, watch the VM's window
|
||||
```
|
||||
|
||||
Install CachyOS with the **KDE Plasma** desktop and **plasma-login-manager**
|
||||
as login manager. Power off and snapshot (only while the VM is off):
|
||||
Installs CachyOS from the ISO without any manual step, with CachyOS's own
|
||||
headless installer (KDE Plasma, plasma-login-manager, btrfs, Limine,
|
||||
`linux-cachyos` + `-lts`), and leaves the snapshots `clean` and `ssh-ready`:
|
||||
sshd with your key, passwordless sudo, autologin into Plasma, English, the
|
||||
host's timezone. Details: [`.claude/skills/vm-install`](.claude/skills/vm-install/SKILL.md).
|
||||
|
||||
- **Login by hand**: your host username, password **`steamify`** (root too).
|
||||
- **SSH**: `~/.ssh/steamify-vm_ed25519`, made on the first install (it asks:
|
||||
that key, or one of your own `~/.ssh/*.pub`), then always used. Existing
|
||||
keys are never overwritten; the VM's host key lives in `$VM_DIR/known_hosts`,
|
||||
not in `~/.ssh/known_hosts`.
|
||||
- `$VM_DIR` also records the user, key and Steamify checkout (`vm-user`,
|
||||
`ssh-key`, `repo-path`); pass the same `VM_DIR` to the other scripts.
|
||||
- `--iso <file>` installs another archiso-based ISO the same way (the Steam
|
||||
Machine ISO, later); `--force` replaces an existing VM (asks first).
|
||||
|
||||
Snapshots (only while the VM is off; keep vars.fd with the disk):
|
||||
|
||||
```bash
|
||||
qemu-img snapshot -c clean disk.qcow2 && cp vars.fd vars.clean.fd
|
||||
qemu-img snapshot -a ssh-ready disk.qcow2 && cp vars.ssh-ready.fd vars.fd # restore
|
||||
```
|
||||
|
||||
Boot the installed system (`./run.sh`) and, in the guest, enable SSH with key
|
||||
auth and passwordless sudo through the read-only `vmtools` share:
|
||||
### Manual install (fallback)
|
||||
|
||||
`./run.sh install` boots the ISO (a first run creates `disk.qcow2`, 60G, and
|
||||
`vars.fd`). Install CachyOS with **KDE Plasma** and **plasma-login-manager**
|
||||
through Calamares, power off, `qemu-img snapshot -c clean disk.qcow2 && cp
|
||||
vars.fd vars.clean.fd`. Boot it (`./run.sh`) and, in the guest:
|
||||
|
||||
```bash
|
||||
sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh
|
||||
```
|
||||
|
||||
### Adding your SSH key
|
||||
(sshd, the host's `~/.ssh/*.pub` from `share/host-keys.pub`, passwordless
|
||||
sudo). Power off, `qemu-img snapshot -c ssh-ready disk.qcow2 && cp vars.fd
|
||||
vars.ssh-ready.fd`.
|
||||
|
||||
`guest-ssh-setup.sh` authorizes the host's public keys. `run.sh` copies them
|
||||
at every start from `~/.ssh/*.pub` into `share/host-keys.pub` (git-ignored),
|
||||
which the guest sees as `/media/host-keys.pub`. So:
|
||||
|
||||
1. Make sure you have a key on the host; create one if `ls ~/.ssh/*.pub`
|
||||
shows nothing:
|
||||
### The same key on the Steam Machine
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519
|
||||
scripts/steammachine-addkey.sh [user@host] # default: steammachine (~/.ssh/config)
|
||||
```
|
||||
|
||||
2. Start (or restart) the VM with `./run.sh`, so the key is copied into the
|
||||
share.
|
||||
3. In the guest, run the setup script (above). It installs and starts
|
||||
`sshd`, opens port 22 if a firewall is active, adds every host key to
|
||||
`~/.ssh/authorized_keys` (without duplicates) and gives the guest user
|
||||
passwordless sudo.
|
||||
4. Test from the host: `ssh -p 2222 <vm-user>@localhost true` should return
|
||||
without asking for a password.
|
||||
|
||||
If you ran the setup script before the key existed (it prints "No host keys
|
||||
were shared"), or you want to add another key later, either restart the VM
|
||||
and run the setup script again, or copy the key over SSH with the guest
|
||||
user's password:
|
||||
|
||||
```bash
|
||||
ssh-copy-id -p 2222 <vm-user>@localhost
|
||||
```
|
||||
|
||||
On Windows, use `.\add-ssh-key.ps1 -User <vm-user>` instead (it creates a key
|
||||
if needed). If SSH hangs at "banner exchange", `sshd` isn't running in the
|
||||
guest or a firewall blocks port 22; check with `systemctl is-active sshd` in
|
||||
the guest.
|
||||
|
||||
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 <user>@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`.
|
||||
authorizes `~/.ssh/steamify-vm_ed25519` on the Steam Machine too (asks its
|
||||
password once), so one key reaches the VM and the real machine.
|
||||
|
||||
### run.sh options
|
||||
|
||||
```
|
||||
[REPO=/path/to/cachyos-gamescope-boot] ./run.sh [install] [--nvidia] [--vulkan] [--fremont]
|
||||
[REPO=/path/to/steamify-cachyos] ./run.sh [install] [--nvidia] [--vulkan [--amd]] [--fremont]
|
||||
```
|
||||
|
||||
- `install`: boot the installer ISO
|
||||
- `install`: boot the installer ISO (`VM_ISO`, default `cachyos.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` (the local clone of
|
||||
[steamify-cachyos](https://github.com/theupriser/steamify-cachyos))
|
||||
- `REPO`: the Steamify checkout shared as 9p `repo` (in the guest: `sudo
|
||||
mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt`); default
|
||||
`repo-path` next to the disk, else `$HOME/projects/cachyos-gamescope-boot`
|
||||
- `VM_KERNEL`/`VM_INITRD`/`VM_APPEND` (boot a kernel directly) and
|
||||
`VM_SERIAL` (serial console log + socket): used by `vminstall.sh`
|
||||
|
||||
## Helper scripts
|
||||
|
||||
- `scripts/vminstall.sh [--force] [--iso <file>] [--fremont]`: unattended install (above)
|
||||
- `scripts/vmreset.sh [--fremont]`: restore `ssh-ready`, boot, mount the repo, autologin into Plasma
|
||||
(and skip the broken krfoss mirror, install shellcheck)
|
||||
- `scripts/vmrun.sh '<menu input>'`: run the wizard in the guest's Plasma session with scripted input
|
||||
- `scripts/vmwatch.sh [--release] '<menu input>' [label]`: same, but in a visible Konsole window
|
||||
in the VM; `--release` runs the newest GitHub release instead of the mounted repo
|
||||
- `scripts/vminstallsim.sh [--fresh] [--defaults options]`: Steamify's install-time mode for a
|
||||
user who never logged in (the Steam Machine ISO's installer step)
|
||||
- `scripts/vmshot.sh [--clean] <out.png>`: screenshot the guest's desktop (`--clean` closes
|
||||
Steam, CachyOS Hello and Konsole first)
|
||||
- `scripts/vmstate.sh`: print the state of every wizard component
|
||||
- `scripts/vmcec.sh [--no-sleep]`: HDMI-CEC against a fake TV (vivid)
|
||||
- `scripts/qmptype.py <qmp.sock> "<text>"` / `scripts/qmpkey.py <qmp.sock> <keys>`: type text / press
|
||||
keys in the VM (e.g. drive the Steamify app: `down right ctrl-ret ret`)
|
||||
- `scripts/cmp.sh [save]`: save / diff the guest's KDE configs against a baseline
|
||||
- `scripts/steammachine-addkey.sh [user@host]`: the VM key on the Steam Machine (above)
|
||||
|
||||
They use `VM_USER` (default `theupriser`), `VM_PORT` (`2222`) and `VM_HOST` (`localhost`).
|
||||
They use `VM_DIR` (default: this repo if it has `disk.qcow2`, else
|
||||
`~/vms/cachyos-test`), and from it `VM_USER` (`vm-user`, else `theupriser`)
|
||||
and `VM_SSH_KEY` (`ssh-key`); `VM_PORT` (`2222`), `VM_HOST` (`localhost`).
|
||||
|
||||
## Claude Code skill
|
||||
## Test plan
|
||||
|
||||
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.
|
||||
[`TESTPLAN.md`](TESTPLAN.md): what to test per release (regression, the app,
|
||||
new features, Steam Machine hardware faked and real) and the results log.
|
||||
|
||||
## Claude Code skills
|
||||
|
||||
- [`cachyos-vm-testing`](.claude/skills/cachyos-vm-testing/SKILL.md): the test workflow in the VM
|
||||
- [`vm-install`](.claude/skills/vm-install/SKILL.md): creating the VM unattended
|
||||
- [`steam-machine-testing`](.claude/skills/steam-machine-testing/SKILL.md): on the real Steam Machine
|
||||
- [`steam-machine-iso`](.claude/skills/steam-machine-iso/SKILL.md): the Steam Machine ISO
|
||||
|
||||
Claude Code picks them up when run from this repo.
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
# Steamify test plan
|
||||
|
||||
What to test before a Steamify release, in the VM (`--fremont`) and on the
|
||||
real Steam Machine. How to run each step: `.claude/skills/cachyos-vm-testing`.
|
||||
**Keep this file up to date**: add a section when a feature is added, and a
|
||||
line to the results log after every test run.
|
||||
|
||||
Every VM run starts with the session start of the skill (`ssh-ready` brought
|
||||
up to date), and each numbered block below starts from a fresh `ssh-ready`
|
||||
(`scripts/vmreset.sh --fremont`, `REPO=` the branch checkout). Runs are
|
||||
visible in a Konsole on the VM's desktop.
|
||||
|
||||
## R. Regression (every release)
|
||||
|
||||
### R1. Menu on an existing desktop (user `theupriser`)
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| R1.1 | `'q\n'` | first-run ticks: everything but Boot into desktop, BIOS |
|
||||
| R1.2 | `'\ny\nm\nq\nn\n'` | every item OK, back in the menu with all on |
|
||||
| R1.3 | `scripts/vmstate.sh` | SDDM + autologin, Vapor, 17 LEDs owned by the user, DKMS `installed` for every kernel, steamos-manager active |
|
||||
| R1.4 | rerun `'\nq\n'` | "Everything is already the way you want it" |
|
||||
| R1.5 | re-apply `'a\ny\nm\nq\nn\n'`, `vmstate.sh` | no errors, no `DUPLICATES` |
|
||||
| R1.6 | `steamify.sh --backend status` | valid JSON, current `VERSION`, states match R1.3 |
|
||||
| R1.7 | close the app first, then `sudo modprobe vivid; scripts/vmcec.sh --no-sleep` | all PASS (2 SKIP in the VM: wake from TV, sleep). The fake remote's Select arrives as Enter: an open app on "All done" restarts the PC |
|
||||
| R1.8 | toggles (theme, single user, CEC, Steam Machine support off/on) | full matrix in the skill; `cmp.sh` clean after theme off |
|
||||
| R1.10 | set an item older in `~/.local/state/steamify/features.state` (e.g. `cec=2.1.0`), `'q\n'`, then `'\ny\nm\nq\nn\n'` | menu shows "HDMI-CEC (update)" ticked; the plan lists it as "update"; after the run the file has the current version |
|
||||
| R1.9 | reboot checks | single user: SDDM logs straight in; everything off: greeter |
|
||||
|
||||
### U. The app (`/mnt/ui/steamify-ui`, on the VM's desktop)
|
||||
|
||||
Drive it with `scripts/qmpkey.py <vm dir>/qmp.sock <keys>` (QEMU qcodes:
|
||||
`up down left right ret esc ctrl-ret ctrl-q r`; wait ~1 s between keys and
|
||||
~12 s after starting the app) and check every screen with a screenshot
|
||||
(`spectacle -b -n -a` for the active window; the progress screen of a quick
|
||||
change only shows right after Enter).
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| U1 | start after R1.5 | header `v<VERSION>`, Steam Machine chip, every row with "now on", Kernel / LED bar (17) / HDMI-CEC / Version panel |
|
||||
| U2 | Up/Down over the rows | detail panel follows: body text and "What it changes" per row |
|
||||
| U3 | an updated feature (bump `FEATURE_VERSION`, or `features.state` older) | `UPDATE` badge, "1 change" |
|
||||
| U4 | Boot into: Right / Left | Gaming / Desktop, detail panel `DESKTOP`, change count +1 |
|
||||
| U5 | Enter on a toggle, Enter again | off, back on, count back |
|
||||
| U6 | Ctrl+Enter | review lists exactly the changes, summary counts right; Esc goes back |
|
||||
| U7 | Enter (apply) | progress per step ("Boot into desktop", update badge), details log, "All done, N changes applied", Restart now / Back to the menu |
|
||||
| U8 | after U7 | the changes are on the system (e.g. boot-desktop unit, `features.state`) |
|
||||
| U9 | `r` (re-apply), Ctrl+Q | re-apply review; the app quits |
|
||||
|
||||
### R2. Installer mode (`scripts/vminstallsim.sh`, user `isotest`, no session)
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| R2.1 | plain `--defaults` | every item OK, `exit: 0`, first-login autostart present |
|
||||
| R2.2 | first desktop login (steam-machine-iso skill) | Vapor layout, `primaryActions=3`, autostart gone, app opens |
|
||||
|
||||
### F2.8.0 `--defaults --list`
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| G1 | `steamify.sh --defaults --list` | exit 0, valid JSON array, no stderr, no header text mixed in |
|
||||
| G2 | items | actions (bios), retired (kpin, hdmi) excluded; unavailable here (vram, steamgame) excluded; poweroff has parent machine, boot has parent gaming and kind choice |
|
||||
| G3 | menu / plain `--defaults` still work | unaffected (regression) |
|
||||
|
||||
## F. Features per release
|
||||
|
||||
### F2.10.0 Gaming on NVIDIA (`feature/nvidia-gaming-fix`; suite `nvidia`, fake NVIDIA PC)
|
||||
|
||||
gamescope's session is broken on NVIDIA (see `nvidia/HARDWARE-RESULTS.md`), so on an NVIDIA PC the conversion is replaced by
|
||||
"Gaming on NVIDIA" (Steam on the Plasma desktop, started at login, optionally in Big Picture) and single user mode logs in to Plasma.
|
||||
The VM has no NVIDIA GPU: `fake_nvidia` (`share/vmtest/prelude.sh`) fakes one through Steamify's `NVIDIA_DRM_DIR` test hook and installs
|
||||
fake NVIDIA modules for every kernel. What no VM can show (real driver, the picture, Big Picture on a real GPU) is checked on the real PC.
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| N1 | `--defaults --list` and `--backend status` with the fake GPU (`nvidia/10-n1-menu.sh`) | `nvidia`, `bigpicture` (parent `nvidia`), `single` offered and preselected; `gaming`, `boot`, `glyphs` hidden; without the fake GPU the conversion is offered and `nvidia` is not |
|
||||
| N2 | `--defaults --options nvidia,bigpicture,single` (`nvidia/20-n2-n5-lifecycle.host.sh`) | exit 0; Steam installed; unit with `-gamepadui` enabled; `loginMode=emptySession`; parameters in `/etc/default/limine` and `/boot/limine.conf`; early-load file, hook and script; modules in the initramfs; SDDM enabled and `zzz-steamify-autologin.conf` (Plasma session) |
|
||||
| N3 | reboot | Plasma straight in (no greeter), parameters on `/proc/cmdline`, the Steam unit started, `loginMode` kept |
|
||||
| N4 | `--defaults --options notify` | exit 0; Steam removed (Steamify installed it), unit and autologin file gone, `loginMode` back to not set, parameters/early-load/hook gone, plasma-login-manager the login manager again |
|
||||
| N5 | reboot | Plasma up through the test autologin, no NVIDIA parameters |
|
||||
| N6 | real RTX 5080 PC | **done by hand 2026-10-01** (earlier prototype): Big Picture on the Plasma desktop smooth and clean; the apply/boot of the final component is the user's next step |
|
||||
|
||||
### F2.10.0 Extended controller support (`feature/extended-controller-support`; suite `cli`, block `60-c1-controllers.sh`)
|
||||
|
||||
Opt-in item: `xpadneo-dkms`, `xone-dkms` and `xone-dongle-firmware` from the CachyOS repo, DKMS-built for every kernel. The VM has no
|
||||
dongle or controller: it covers the packages, the headers and the builds, not the hardware.
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| C1 | `--defaults --options extended_controller_support` | exit 0; the three packages installed; `dkms status` shows xone and xpadneo installed for every kernel; the modules (`xone_dongle`, `hid_xpadneo`) exist for each |
|
||||
| C2 | the same again | quiet, nothing installed again |
|
||||
| C3 | `--defaults --options notify` | exit 0; the three packages removed again |
|
||||
| C4 | real PC with the Xbox dongle | **by hand, 2026-10-01 earlier**: the dongle works with the AUR `xone-dkms-git` (modules `xone_dongle`, `xone_gip_gamepad` loaded); status counts `xone-dkms-git` as on |
|
||||
|
||||
### F2.7.0 `--defaults --options` / `--boot`
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| F1 | `--options` (no value), `bogus`, `boot`, `bios`, `--boot` (no value), `--boot sideways`, `--skip x` | each `exit: 1` with a clear error, nothing changed |
|
||||
| F2 | `--options theme --boot desktop` (either order) | `exit: 1`: desktop needs the conversion |
|
||||
| F3 | `--options gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff --boot desktop` | all on, boot-desktop unit enabled; VRAM left out with a warning in the VM |
|
||||
| F4 | `--options theme,single,machine --boot desktop` | single brings the conversion; poweroff, CEC, glyphs, launcher, notify recorded `off` |
|
||||
| F5 | `--options launcher,vram,poweroff` | poweroff brings Steam Machine support; conversion off, plasmalogin unchanged |
|
||||
| F6 | `steamify.sh --boot desktop` without the conversion | `exit: 1` |
|
||||
| F7 | on an install: `--boot desktop`, again, `--boot gamescope` | only Boot into changes; "Already starting in desktop"; menu ticks unchanged |
|
||||
|
||||
## H. Steam Machine hardware
|
||||
|
||||
### H-VM. With the faked Steam Machine (`--fremont`)
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| H1 | LED driver | `leds_valve` loaded, 17 `valve-leds*` nodes owned by the user (no light to see) |
|
||||
| H2 | HDMI-CEC | R1.7 (vivid: fake TV and remote) |
|
||||
| H3 | BIOS update | `BIOS_VERSION=F7F0107 scripts/vmreset.sh --fremont`, `WIZARD_BIOS_DRY_RUN=1`: whole flow, never flashes |
|
||||
| H4 | Boot into desktop, reboot | Plasma straight after boot; Boot into gaming: SDDM autologin into gamescope (black in the VM, see the skill; check the session file) |
|
||||
| H5 | power-off module | DKMS built for every kernel, module loads, "No such device" (no GPIO wake bit in the VM) |
|
||||
|
||||
### H-Real. Only on the real Steam Machine
|
||||
|
||||
- Power-off fix: stays off after shutdown, for every new major kernel.
|
||||
- VRAM booster (the VM has no `dmem` VRAM region).
|
||||
- Gamescope itself: gaming mode, Switch to Desktop / Return to Gaming Mode.
|
||||
- LED bar light, real TV over HDMI-CEC (remote, TV on/off, sleep/wake).
|
||||
|
||||
## B. Boot loaders (`scripts/vmtest.sh`, unattended)
|
||||
|
||||
Every loader the ISO advertises (Limine, systemd-boot, GRUB), each on a VM installed from the Steamify ISO
|
||||
and started with `--fremont`. Automated: `scripts/vmtest.sh` (see the vm-install skill).
|
||||
|
||||
| # | Step | Expect |
|
||||
|---|---|---|
|
||||
| B1 | first boot after the unattended install, and every reboot | boots by itself through the loader's own EFI boot entry (`BootCurrent`, a real partition; checked after every reboot), no network boot first (systemd-boot: the entry comes from the ISO's `steamify-install` since PR #2; `timeout`/`default` set by the test setup), SSH answers |
|
||||
| B2 | Steamify's state, as its own `*_status` functions | gaming, theme, glyphs, single, launcher, notify, poweroff, cec all on |
|
||||
| B3 | OS name | `os-release` NAME/PRETTY_NAME are CachyOS's, `lsb-release` has no "with Steamify"; Limine has `TARGET_OS_NAME` |
|
||||
| B4 | loader identity, firmware setup | the expected loader; `Boot into FW: supported` (BIOS item) |
|
||||
| B5 | DKMS modules (`leds-valve`, `cros-ec-cec`, `steamify-fremont-poweroff`) | installed for every kernel, files present, `modules-load.d` entry |
|
||||
| B6 | legacy `drm.edid_firmware` removal (`hdmi_remove_boot_param`) | detected, removed, initramfs and entries rebuilt, no edid left |
|
||||
| B7 | kernel update (both kernels + headers) | DKMS rebuilds 3 modules per kernel, initramfs rebuilt, loader config regenerated |
|
||||
| B8 | reboot into the default and the other kernel | both boot, no failed units, power-off fix and LED modules loaded on each |
|
||||
|
||||
## Known gaps
|
||||
|
||||
- "Add as non-Steam game" can't be set up at install time (no Steam account
|
||||
yet); not covered by any installer test.
|
||||
- ISO: `steamify-install` and the packagechooser pages still pass `--skip`
|
||||
(removed in 2.7.0); to be replaced by the Steamify page.
|
||||
|
||||
- `/etc/plasmalogin.conf` missing before Steamify (a VM from vminstall.sh,
|
||||
not Calamares): turning the conversion off leaves an empty file instead of
|
||||
none (harmless; Calamares installs have one, restored from the backup).
|
||||
- R2.2: the first login's app is the newest *release* (`run-app`), so before
|
||||
a release it shows the old version; start the branch's app by hand
|
||||
(steam-machine-iso skill).
|
||||
|
||||
- CEC: the driver source comes from an unofficial mirror of Valve's kernel on GitHub; since 2.9.1 it is
|
||||
downloaded once and cached (/var/cache/steamify; the VMs share the host's), so only a first download
|
||||
can hit GitHub's rate limit (HTTP 429).
|
||||
|
||||
## Results log
|
||||
|
||||
| Date | Branch / version | Blocks | Result |
|
||||
|---|---|---|---|
|
||||
| 2026-09-28 | `feat/defaults-options` 2.7.0 (uncommitted `--options`, `--boot`) | R1.1-R1.6, R2.1, F1-F7 | all pass (VM `--fremont`) |
|
||||
| 2026-09-28 | same + CEC socket fix, UI label fix | R1.7, R1.10, U1-U8, H1, H4 (desktop), H5 | R1.7 first failed: `cec-audio-control.socket` never enabled (old bug, fixed: `cec_enable` enables it, `FEATURE_VERSION[cec]=2.7.0`); after: 53 PASS / 0 FAIL / 2 SKIP. UI: progress row said "Boot into Boot into" (fixed). Rest pass. Not run: R1.8, R1.9, R2.2, U9, H3 |
|
||||
| 2026-09-28 | `release/2.7.0` (VM from vminstall.sh, `~/vms/steamify-vm`) | R1.2 (fresh, CEC socket), R1.8, R1.9, R1.5 via U9, U9, R2.1, R2.2, H3 | all pass except one old bug: theme on (single on) → single off → theme off left single's launcher keys; fixed (`58e43d4`, PR #34) and retested. BIOS dry run F7F0107 → F7F0108 passed; greeter and SDDM reboot checks passed |
|
||||
| 2026-09-28 | `release/2.7.0` | H-Real | counted as passed: power-off fix, VRAM booster, LEDs, gamescope and Steam Machine support are unchanged since 2.6.0, which passed on the real Steam Machine. HDMI-CEC's change (enabling `cec-audio-control.socket`) passed with the fake TV in the VM; check the TV remote's volume keys on the real TV after updating (HDMI-CEC shows as update) |
|
||||
| 2026-09-29 | `feature/vminstall-bootloader` (Steamify ISO 2026.09.28, `--fremont`) | B1-B8 | Limine 41 / systemd-boot 40 / GRUB 40 checks pass. Found on the way (test setup, not Steamify): ufw blocks ssh, systemd-boot has no timeout/default after cachyos-installer |
|
||||
| 2026-09-28 | `release/2.8.0` (`feature/defaults-list`) | G1-G3, R1.1, R1.2 | all pass (VM `--fremont`, fake Steam Machine). Bundle shellcheck clean |
|
||||
| 2026-09-29 | Steamify 2.9.0 (`steamify-cachyos` main), ISO `feat/steamify` with PR #2 (2026.09.29), dev `main` 5b867f8; on the PC (WSL2) | everything automated: B1-B8 (3 loaders), cli, menu, hw, installer, toggles (`scripts/vmtest.sh`, parallel, `MAX_PARALLEL=3`) | **328 pass, 0 fail** (toggles 42 +1 expected skip, hw 70, cli 55, menu 36, installer 4, Limine 41, systemd-boot 40, GRUB 40) in 18 minutes: the first complete parallel run. Found on the way: systemd-boot had no EFI boot entry (installer's `bootctl` in a chroot), fixed in the ISO (PR #2); `vminstall.sh` fixes for parallel installs and a root host user |
|
||||
| 2026-09-29 | ISO `feat/steamify` + PR #3 (`/var/log`), dev `main` with the B1 boot entry check | B1-B8 | Limine 43 / GRUB 42 pass (boot entry check passes: `Limine`, `cachyos`). systemd-boot (reinstalled): 36 pass, 8 fail, all CEC: the driver download got HTTP 429 from GitHub (rate limit); boot entry check passes (`Linux Boot Manager`); `/var/log/steamify-install.log` and `steamify-bootentry.log` readable after boot |
|
||||
| 2026-09-29 | Steamify `release/2.9.1` (CEC driver cache), ISO `feat/steamify` (PRs #2, #3), dev `main` | everything automated incl. hw block 50 (CEC cache) and the B1 boot entry check | **341 pass, 0 fail** in 19 minutes (toggles 42 +1 expected skip, hw 77, cli 55, menu 36, installer 4, Limine 43, systemd-boot 42, GRUB 42) |
|
||||
| 2026-09-30 | Steamify 2.9.6 (`steamify-cachyos` main `d93f9ea`), the official ISO `steamify-cachyos-2.9.6-260930-x86_64.iso` from git.upriser.nl (sha256 `4a94f308…aec41`), dev `main`; on the PC, headless (`CI=1`/`VM_HEADLESS=1`), `MAX_PARALLEL=5` | everything automated: B1-B8 (3 loaders, `--install` from the new ISO), cli, menu, hw, installer, toggles (base VM from the same ISO with `VM_STEAMIFY=skip`) | **341 pass, 0 fail** (toggles 42 +1 expected skip, hw 77, cli 55, menu 36, installer 4, Limine 43, systemd-boot 42, GRUB 42). Found on the way (test setup, not Steamify or the ISO): with five VMs at once the loaders' first boot took longer than `waitssh` waited (5 min), so all three stopped with `FAIL no SSH` although the VMs were up; `waitssh` now waits up to 15 minutes and the loaders were run again (install kept) |
|
||||
| 2026-10-01 | Steamify `feature/extended-controller-support` (7dae14c; on top of `feature/nvidia-gaming-fix` d0e4d8c, 2.9.7), plain CachyOS ISO 260809 (VM `~/vms/steamify-vm`, `vminstall.sh --iso`), dev `main` + the nvidia suite; on the PC (native CachyOS, KVM), headless | N1-N5 (new suite `nvidia`), C1-C3 (cli block 60), R1.1-R1.6/R1.10, H1-H5 and the CEC/CEC cache blocks, R2.1, toggles | **269 pass, 0 fail**: cli 65, nvidia 43, menu 37, hw 78, installer 4, toggles 42. Found on the way (test side, not Steamify): R1.1 and H3 pressed menu rows by number and the new Extended controller support row shifted them (now by name); the N4 check for the parameter in `/boot/limine.conf` must ignore snapper's snapshot entries, which keep the command line of their time. The kernel part (parameters, early-load file, hook, initramfs) behaves in a real Limine VM; the real driver and picture are only checked on the RTX 5080 PC |
|
||||
@@ -0,0 +1,88 @@
|
||||
# TODO
|
||||
|
||||
## Tests in CI: dropped 2026-09-30, the VM tests run locally
|
||||
|
||||
Decision: `scripts/vmtest.sh` runs on the PC before the release PR, not on GitHub or Gitea runners (too slow, too heavy, and
|
||||
not reliable: some GitHub runners had no `/dev/kvm`, nested virtualization made installs slow, the 3 GB ISO had to come
|
||||
from the mirror on every run; the Gitea machine has 8 threads). The attempt (steamify-cachyos PR #66, closed, branch
|
||||
`feature/vmtest-ci`) left these behind: `vminstall.sh` reads the ISO with `bsdtar` (no loop devices), `CI=1` makes it
|
||||
headless, the VM's ssh key is looked up at every call, an aborted test counts as a failure, progress lines while a test runs.
|
||||
|
||||
- [x] TESTPLAN.md: section B for the boot loader test (B1-B8), pointing at `scripts/vmtest.sh`
|
||||
- [x] `vmbootloadertest.sh --quick [--generic]`: only what the ISO's install must get right (B1-B5) on a Steam Machine VM
|
||||
(`--fremont`) or a plain PC (`--generic`: the Steam Machine items and modules must be absent)
|
||||
- [ ] A helper that fetches the newest ISO release of the mirror (git.upriser.nl) into
|
||||
`../steamify-cachyos-live-iso/out/desktop/`, for `vmtest.sh --install` (the ISO is no longer kept locally)
|
||||
- [ ] Document `--quick` / `--generic` in the `vm-install` skill and TESTPLAN.md (which B rows it covers)
|
||||
- [ ] Steamify `AGENTS.md`: "run `scripts/vmtest.sh` before the release PR into main" (decided 2026-09-30)
|
||||
|
||||
## Full automated test: everything in TESTPLAN.md that needs no person
|
||||
|
||||
One command, `scripts/vmtest.sh [suite...]` (suites: `boot`, `cli`, `menu`, `hw`, `installer`; default all),
|
||||
each check prints `PASS`/`FAIL`, the summary counts them, exit status = failures. Each block starts from a
|
||||
fresh `ssh-ready` (`scripts/vmreset.sh --fremont`), as TESTPLAN.md says. GitHub runs the same command.
|
||||
Guest-side checks live in `share/vmtest/<suite>/`, one file per TESTPLAN block. Adding a row to
|
||||
TESTPLAN.md means adding its check here (say which row each check covers in a comment: `# F4`).
|
||||
|
||||
Automatable (encode as checks): R1.1-R1.6, R1.7 (`vmcec.sh` already prints PASS/FAIL), R1.8 (toggle matrix),
|
||||
R1.9, R1.10, R2.1, G1-G3, F1-F7, H1, H3, H4 (session file), H5, and the boot loader matrix (done).
|
||||
Not automatable without a person (stay manual, listed in the summary as SKIP; no screenshot testing): U1-U9,
|
||||
R2.2 (first desktop login look), H-Real (real Steam Machine).
|
||||
|
||||
- [x] S1. Framework (vmtest.sh runs boot + every suite, --screen, summary in ~/vms/last-test.txt): `scripts/vmtest.sh` takes suites, resets the VM per block, tallies PASS/FAIL/SKIP,
|
||||
prints the skipped manual rows; move the loader matrix under suite `boot`
|
||||
- [x] S2. Suite `cli` (55 checks pass): G1-G3, F1-F7 (exit codes, `features.state`, boot-desktop unit)
|
||||
- [x] S3. Suite `menu` (36 checks pass): R1.1-R1.6, R1.10 with scripted menu input (`vmrun.sh`) and `vmstate.sh` asserts
|
||||
- [x] S4. Suite `hw` (70 checks pass incl. H3 BIOS dry-run): H1, H3 (`WIZARD_BIOS_DRY_RUN=1`), H4, H5, R1.7 (`vmcec.sh --no-sleep`)
|
||||
- [x] S5. Suite `installer` (4 pass): R2.1 (`vminstallsim.sh`)
|
||||
- [x] S6. R1.8 toggle matrix as suite `toggles` (42 pass, 1 expected skip); R1.9 reboot checks still to add
|
||||
- [ ] S7. TESTPLAN.md: mark which rows are automated (a column or a tag), keep the results log automatic:
|
||||
the script appends a line to it (date, branch, suites, counts)
|
||||
- [ ] S8. Update the `steam-machine-testing` and `cachyos-vm-testing` skills: "run `scripts/vmtest.sh`" first
|
||||
|
||||
## Speed: the full run takes about an hour (target 20-25 min)
|
||||
|
||||
Where the time goes: boot loader VMs ~3 min each, every suite block restores a snapshot and reboots
|
||||
(~1 min), and every block that applies Steamify downloads Steam and the rest again from the mirrors.
|
||||
|
||||
- [x] P1 (proven 2026-09-29: full run in 18 min on the PC). Package cache for the suite VM: `VM_CACHE` shared as 9p and bound over `/var/cache/pacman/pkg`
|
||||
in `vmreset.sh`/the prelude (as vminstall-live.sh does); downloads only once
|
||||
- [x] P2 (base image + overlays, own port per suite, MAX_PARALLEL=3, VM_MEM=4G; first complete parallel run 2026-09-29: 328 pass, 18 min). Parallel: each suite (and each boot loader) on its own copy of the VM (`cp --reflink` of the disk and vars),
|
||||
own `VM_PORT` and `VM_DIR`; `vmtest.sh` starts them together and waits; watch host CPU (16 cores, 6 vCPUs each)
|
||||
- [ ] P3. Fewer resets: merge blocks that only read state (cli 10 + the menu R1.1)
|
||||
|
||||
## Run the tests on the PC (9800X3D, 64 GB) from a laptop
|
||||
|
||||
- [ ] R1. `scripts/vmtest-remote.sh <ssh-host>` written; try it once: the PC needs the three repos, qemu + OVMF + screen,
|
||||
KVM (on Windows: WSL2 with nested virtualization), the VMs in `~/vms` (`vmtest.sh --install` builds the boot loader
|
||||
VMs, `vminstall.sh --iso <CachyOS ISO>` with `VM_STEAMIFY=skip` the plain one), and the Steamify ISO
|
||||
- [x] R1b (done 2026-09-29: full vmtest.sh run there, 328 pass in 18 min; setup in the wsl-build-host skill). The PC is WSL2 (Windows). To check there: `/dev/kvm` exists (Windows 11, virtualization on in the BIOS,
|
||||
nested virtualization on); `%UserProfile%\.wslconfig` `memory=` is raised (WSL2 defaults to half the RAM = 32 GB;
|
||||
3-4 VMs at 4-8 GB want ~48 GB) and `processors=16`; keep `~/vms` and the repos on WSL's own ext4, not under `/mnt/c`;
|
||||
the distro is Arch (root): `pacman -S qemu-full edk2-ovmf screen`; automated runs are headless with no Konsole
|
||||
(`view_start` returns early; watch with `screen -r vmtest` or `tail -f ~/vms/test.log`), a VM for the user gets a
|
||||
window through WSLg (see the wsl-build-host skill). Still to do: a first full `vmtest.sh` run there
|
||||
- [x] R1c. `vminstall.sh` without udisks (WSL): `losetup -P` + `mount` when `udisksctl` is missing and it runs as root
|
||||
- [x] R2. `MAX_PARALLEL=5` ran fine on the PC (9800X3D, 8c/16t, 64 GB; reported 2026-09-30). The default stays 3 for smaller
|
||||
machines. Not tried: 8 GB VMs (`VM_MEM=8G`).
|
||||
|
||||
## Found 2026-09-29 (first full run on the PC)
|
||||
|
||||
- [x] B1 check (share/bootloader-test/boot-check.sh, after every reboot; passes on all three): the loader's own EFI boot entry exists, points to a real partition (not `HD(0,GPT,0000...)`)
|
||||
and was used (`efibootmgr` `BootCurrent`); systemd-boot booted through the fallback path unnoticed
|
||||
- [x] ISO: mount `@log` at `$ROOT/var/log` before `steamify-install` writes its logs (PR #3 merged)
|
||||
- [x] Steamify 2.9.1 (PR #53 into release/2.9.1; hw block 50 tests it): the CEC driver comes from one download (raw.githubusercontent.com, no retry, no fallback);
|
||||
a GitHub rate limit (HTTP 429, seen 2026-09-29 after a day of test installs) leaves CEC off. Ship the
|
||||
pinned source with Steamify instead (steamify-cachyos, via a release branch). Done as a cache in
|
||||
/var/cache/steamify (named after its checksum); the VMs share the host's (~/vms/pkg-cache/steamify).
|
||||
Later maybe: ship the file itself (the source repo is an unofficial mirror)
|
||||
- [x] ISO: systemd-boot's EFI boot entry (efibootmgr from the live system), PR #2 merged
|
||||
- [x] vminstall.sh: a poweroff that drops ssh is no failure, wait only for its own VM, ISO loop device under
|
||||
a lock, never root as the guest user
|
||||
|
||||
## Boot loader test (local)
|
||||
|
||||
- [x] `scripts/vmtest.sh [--install] [loader...]` runs all advertised loaders, `vmbootloadertest.sh` one
|
||||
- [x] guest checks in `share/bootloader-test/` (state, modules, legacy param, kernel update, boot check)
|
||||
- [x] first full run of all three through `vmtest.sh`: Limine 41 / systemd-boot 40 / GRUB 40, 0 failed
|
||||
- [ ] test the `VM_CACHE` speed-up with a second install
|
||||
@@ -1,9 +0,0 @@
|
||||
# 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 <vm-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"
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
# 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" }
|
||||
+4
-2
@@ -4,7 +4,8 @@
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
mirror="${MIRROR:-https://nl.mirror.cx/cachyos}"
|
||||
# CachyOS's own mirror: nl.mirror.cx gave about 1 MB/s here, this one 80 MB/s (MIRROR=... to use another).
|
||||
mirror="${MIRROR:-https://mirror.cachyos.org}"
|
||||
|
||||
# The download page embeds the current ISO URL; take the release from it.
|
||||
release="$(curl -fsL https://cachyos.org/download/ |
|
||||
@@ -22,8 +23,9 @@ if [[ -f cachyos.iso && "$(cat cachyos.iso.version 2>/dev/null)" != "$name" ]];
|
||||
rm -f cachyos.iso
|
||||
fi
|
||||
|
||||
curl -fL -C - -o cachyos.iso "$url"
|
||||
# The version is written before the download: a partial file of this release is resumed, not deleted as "an older release".
|
||||
echo "$name" > cachyos.iso.version
|
||||
curl -fL -C - -o cachyos.iso "$url"
|
||||
|
||||
echo "Verifying checksum..."
|
||||
curl -fsL "$url.sha256" | awk '{print $1" cachyos.iso"}' | sha256sum -c
|
||||
@@ -0,0 +1,70 @@
|
||||
# NVIDIA fix: results on the RTX 5080 PC (2026-10-01)
|
||||
|
||||
PC: RTX 5080 (GB203, 10de:2c02) + AMD Granite Ridge iGPU, MSI MAG 341C OLED on the NVIDIA DP-1 (3440x1440, 175 Hz),
|
||||
CachyOS, Limine, kernel 7.2.8-1-cachyos, nvidia-open 615.71.09, gamescope 3.16.31-1, gamescope-session-cachyos 1.1.6-1.
|
||||
|
||||
## Result: the kernel-parameter fix does not cure the picture
|
||||
- Baseline (before): gaming mode corrupted without HDR; with HDR it worked but not at 175 Hz and not full screen.
|
||||
- After `apply` + reboot (`modeset = Y`, `fbdev = Y`, NVIDIA modules in the initramfs): flickers heavily, artifacts, still no 175 Hz.
|
||||
`modeset`/`fbdev` were already `Y` live before the fix (driver 615), so the fix changed little.
|
||||
- Cause is not the parameters: NVIDIA's open bug "display modes above 2560x1440@120 with HDR cause flickering/corruption within
|
||||
gamescope-session" (internal bug 5240452, April 2025, no fix as of March 2026; 555 up to 595+, RTX 3070 Ti/4090/5090; corruption is
|
||||
display-side only):
|
||||
https://forums.developer.nvidia.com/t/display-modes-above-2560x1440p-120hz-with-hdr-enabled-cause-flickering-corruption-within-gamescope-session/295314
|
||||
|
||||
## Bugs found in the fix on this PC (fixed in commit a5e9015 on feature/nvidia-gaming-fix)
|
||||
- CachyOS writes Limine's line as `KERNEL_CMDLINE[default]+="..."`; the sed only matched `=`, so apply stopped with "Couldn't add the NVIDIA parameters".
|
||||
- The kernel treats `nvidia_drm.modeset=1` and `nvidia-drm.modeset=1` alike; the check only knew the hyphen.
|
||||
- tests/nvidia-hardware-test.sh did not set `SCRIPT_DIR`, so patch_file read `/patches/...`.
|
||||
|
||||
## What gamescope did (journal, gaming mode)
|
||||
- Picks the RTX 5080 (card1), DP-1, mode list has 3440x1440@175/144/100. It selected @60 by default; with
|
||||
`-W 3440 -H 1440 -r 175` added (a copy of /usr/lib/steamos/gamescope-session, whose last line is `-O ... \` so args need a copy)
|
||||
it selected @175 and did not crash, but the picture was the same. Steam then sets the Xwayland size to 1920x1080 (not full screen).
|
||||
- `vkGetPhysicalDeviceFormatProperties2 returned zero modifiers` for 2 16-bit formats: harmless here.
|
||||
- A gamescope SIGABRT (`double free or corruption`) at session exit, backtrace through libnvidia-eglcore and libvulkan_radeon.
|
||||
- The PC froze once and was reset after a gaming-mode session (no kernel error logged).
|
||||
|
||||
## What works: nested gamescope inside KDE (Wayland), borderless
|
||||
- `gamescope -W 3440 -H 1440 -r 175 -f ...`: artifacts once the window is focused and fullscreen (unfocused it is fine).
|
||||
Hypothesis, not proven: KWin's direct scanout of the fullscreen window.
|
||||
- `gamescope -W 3440 -H 1440 -r 175 -b -e --hdr-enabled -- steam -gamepadui` (borderless): clean, full screen, about 175 Hz.
|
||||
- So on NVIDIA the separate gamescope session (the SteamOS conversion) is blocked by the driver bug; a nested borderless gamescope works.
|
||||
Whether Steamify gets a nested option for NVIDIA is the user's decision.
|
||||
|
||||
## Cause confirmed: KWin's direct scanout; a session without Plasma works (2026-10-01, later)
|
||||
- A TTY session of only `kwin_wayland --drm` + `gamescope -b` + `steam -gamepadui` (no Plasma shell, no panel) artifacted too
|
||||
(KWin hands the covering window straight to the display).
|
||||
- Same session with `KWIN_DRM_NO_DIRECT_SCANOUT=1` (exists in KWin 6.7.5's libkwin; not in the kwin_wayland binary's strings):
|
||||
NO artifacts, full screen, about 175 Hz (gamescope logs `Changed refresh to: 174.962hz`). Animations felt a bit clunky (cause unknown:
|
||||
an extra composition step without scanout, no CAP_SYS_NICE for gamescope, no VRR, nested frame pacing; other switches to try:
|
||||
`KWIN_DRM_DISABLE_TRIPLE_BUFFERING`, `KWIN_DRM_PREFER_COLOR_DEPTH`).
|
||||
- Launcher used for the test: `~/.local/bin/steamify-nvidia-session-test` (run from a free TTY; KWin as the compositor,
|
||||
`gamescope -W 3440 -H 1440 -r 175 -b -e --hdr-enabled -- steam -gamepadui`, a watchdog ends it after 90 s).
|
||||
Lesson: the first version's timeout left KWin/gamescope/Steam running (the cleanup came after a pipeline that waits for them);
|
||||
`pkill -f '<pattern>'` in a command that contains the pattern kills the caller.
|
||||
- Open: a real session entry (`/usr/share/wayland-sessions/`, needs sudo) and the product decision: an NVIDIA session mode in Steamify
|
||||
instead of the gamescope DRM session. Not built, not in the product repo.
|
||||
|
||||
## Smoothness: the TTY-only KWin session stutters, Plasma does not; Big Picture in Plasma is perfect (2026-10-01, evening)
|
||||
- TTY session (`kwin_wayland --drm` under `dbus-run-session`, no Plasma): the mouse and windows stutter every 1-2 s, with gamescope
|
||||
(clunky) and without it (`steam -gamepadui` as KWin's only client). Same in both: not gamescope, not Steam. Plasma's own KWin is smooth.
|
||||
- Tried without effect: `KWIN_DRM_DISABLE_TRIPLE_BUFFERING=1`. Measured: the test KWin used about 2% CPU, no spikes, but its main thread
|
||||
dropped to 0% about every 2 s (blocked, not busy); scheduling (RR main thread, nice -12) and capabilities equal to Plasma's KWin;
|
||||
NVIDIA is boot_vga; no relevant environment differences found. Cause of the stutter not found.
|
||||
- Plain `steam -gamepadui` (Big Picture, no gamescope) in the normal Plasma session: **perfect** (smooth, no artifacts, no env var needed).
|
||||
- Direction: for NVIDIA PCs a Plasma session that shows only Steam's Big Picture (no desktop, no panel) instead of the gamescope
|
||||
session. Gives up gamescope's features (HDR handling, FSR, overlay; most `-steamos3` menus need gamescope).
|
||||
|
||||
## Plasma session with Big Picture started at login: works (2026-10-01, night)
|
||||
- `~/.config/autostart/steamify-bigpicture.desktop` (`Exec=steam -gamepadui`) in the normal Plasma Wayland session, SDDM autologin set to
|
||||
`plasma.desktop` (`pkexec /usr/lib/steamos/steam-set-session plasma.desktop`): Big Picture opens by itself, smooth, no artifacts, no
|
||||
KWin environment variables needed. Exiting Steam leaves the Plasma desktop, no relogin.
|
||||
- A separate session entry (prototype in the scratchpad, not installed: `steamify-bigpicture-session` marker + autostart helper, session
|
||||
file in `/usr/share/wayland-sessions`) only decides WHEN Big Picture starts; the plain autostart gave the same result.
|
||||
|
||||
## State of the PC after the test (to undo)
|
||||
- The fix is applied (Limine file backup `/etc/default/limine.bak-gamescope-wizard`, early-load file, pacman hook): undo in INSTRUCTIONS.md.
|
||||
- Test launcher `~/.local/bin/steamify-nvidia-session-test`, log `~/steamify-nvidia-session-test.log`.
|
||||
- User override of the gaming session: `~/.config/systemd/user/gamescope-session.service.d/steamify-nvidia-test.conf` and
|
||||
`~/.local/share/steamify/nvidia-test/`.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Testing the NVIDIA fix on a real PC
|
||||
|
||||
For the PC with an RTX 5080 (or any RTX 20 series or newer card) that shows a corrupted picture when
|
||||
gaming mode (gamescope) starts. Product branch: `feature/nvidia-gaming-fix` in steamify-cachyos, not released.
|
||||
This file lives in steamify-cachyos-dev (`nvidia/INSTRUCTIONS.md`); background and open work: `nvidia/TODO.md`; research
|
||||
with sources: `nvidia/RESEARCH.md` (github.com/theupriser/steamify-cachyos-dev).
|
||||
|
||||
## What the fix does
|
||||
With a supported NVIDIA GPU (RTX 20 series or newer, same check as the VRAM booster) and its driver installed it
|
||||
- adds `nvidia-drm.modeset=1 nvidia-drm.fbdev=1` to the kernel command line (Limine, systemd-boot or GRUB;
|
||||
the file is backed up as `<file>.bak-gamescope-wizard`),
|
||||
- loads the NVIDIA modules in the initramfs, but only while every installed kernel has them (a pacman hook,
|
||||
`/etc/pacman.d/hooks/85-steamify-nvidia-initramfs.hook`, decides again at every kernel change),
|
||||
- rebuilds the initramfs and boot entries and checks that the parameters really are in them.
|
||||
It takes effect after a reboot. Older cards (GTX 10 series and older) are left alone.
|
||||
|
||||
## Before you start
|
||||
- CachyOS with the NVIDIA driver installed (`nvidia-open`), a user with sudo, `git`.
|
||||
- Be at the PC and able to see the screen: the last step needs your eyes.
|
||||
- Know the way back (below) in case the PC doesn't boot to a picture: a TTY (Ctrl+Alt+F3) or the boot menu's
|
||||
other kernel entry.
|
||||
|
||||
## Steps
|
||||
1. Get the code:
|
||||
```
|
||||
git clone -b feature/nvidia-gaming-fix https://github.com/theupriser/steamify-cachyos
|
||||
cd steamify-cachyos
|
||||
```
|
||||
(An existing checkout: `git fetch && git checkout feature/nvidia-gaming-fix && git pull`.)
|
||||
2. Look first, change nothing:
|
||||
```
|
||||
bash tests/nvidia-hardware-test.sh check
|
||||
```
|
||||
3. Apply the fix (asks for your sudo password), then **reboot**:
|
||||
```
|
||||
bash tests/nvidia-hardware-test.sh apply
|
||||
```
|
||||
4. After the reboot, check again:
|
||||
```
|
||||
bash tests/nvidia-hardware-test.sh check
|
||||
```
|
||||
5. Start gaming mode, look at the screen, then (from the desktop or a TTY):
|
||||
```
|
||||
bash tests/nvidia-hardware-test.sh visual
|
||||
```
|
||||
It asks whether the picture was clean and for one line on what you saw.
|
||||
6. Send back `~/steamify-nvidia-report.txt` (every run adds to it), or start a new session in the checkout and
|
||||
say "read nvidia/TODO.md in steamify-cachyos-dev and the report".
|
||||
|
||||
(`./steamify.sh` from the same checkout also applies the fix, automatically, as part of the SteamOS conversion:
|
||||
there is no menu option for it yet. The script above shows more.)
|
||||
|
||||
## Reading `check`
|
||||
| Line | Good result | If not |
|
||||
|---|---|---|
|
||||
| `PASS a supported NVIDIA GPU ... is detected` | your 5080 | `FAIL ... older than RTX 20`: the card is skipped on purpose. `FAIL no NVIDIA GPU with its driver`: driver not installed (nouveau?) |
|
||||
| `NVIDIA PCI device id 0x... (not on a legacy list ...)` | RTX 20 or newer | on a legacy list: older card |
|
||||
| `no chwd legacy lists found` | (note only) | the check cannot tell old from new cards; every card counts as supported. Tell me, it's open item 4 in `nvidia/TODO.md` |
|
||||
| `PASS nvidia-drm.modeset=1 is on the running kernel's command line` (and `fbdev=1`) | after step 3 and the reboot | "in the boot loader's file but not booted yet": reboot, or the boot entry used is another one |
|
||||
| `PASS nvidia_drm modeset = Y` (and `fbdev = Y`) | after the reboot | `unreadable`: it needs sudo, enter the password |
|
||||
| `... early-load drop-in is used / skipped` | note only: skipped means a kernel without the NVIDIA modules is installed | nothing to do |
|
||||
|
||||
## Reading the result
|
||||
- Parameters on, `modeset = Y`, `fbdev = Y`, picture clean: the fix works.
|
||||
- Parameters on and `Y`, picture still corrupted: not this fix. The report has the gamescope and NVIDIA log lines and
|
||||
the versions of `nvidia-open`, `nvidia-utils`, `gamescope` and `gamescope-session-cachyos`: the cause is probably the
|
||||
gamescope/driver combination for Blackwell (see `nvidia/RESEARCH.md`).
|
||||
- No picture at all after the reboot: undo it (next section) from a TTY or the other kernel entry, and send the report.
|
||||
|
||||
## Undo
|
||||
Turning the SteamOS conversion off in the wizard removes the fix too. On its own, from the checkout:
|
||||
```
|
||||
SCRIPT_DIR=$PWD bash -c 'source lib/common.sh; source lib/hdmi-refresh.sh; source lib/vram-booster.sh; source lib/nvidia.sh; nvidia_disable'
|
||||
```
|
||||
It removes the parameters, the early-load file, the pacman hook and its script, and rebuilds the boot entries (reboot after).
|
||||
By hand: delete ` nvidia-drm.modeset=1 nvidia-drm.fbdev=1` from `/etc/default/limine` (`KERNEL_CMDLINE[default]`),
|
||||
`/etc/sdboot-manage.conf` (`LINUX_OPTIONS`) or `/etc/default/grub` (`GRUB_CMDLINE_LINUX_DEFAULT`), or restore the
|
||||
`.bak-gamescope-wizard` copy next to it; remove `/etc/mkinitcpio.conf.d/90-steamify-nvidia.conf`,
|
||||
`/etc/pacman.d/hooks/85-steamify-nvidia-initramfs.hook` and `/usr/local/libexec/steamify-nvidia-initramfs`; then
|
||||
`sudo limine-mkinitcpio` (Limine) or `sudo mkinitcpio -P` and `sudo sdboot-manage gen` / `sudo grub-mkconfig -o /boot/grub/grub.cfg`.
|
||||
|
||||
## What is and isn't proven
|
||||
Proven in VMs (QEMU/KVM, CachyOS ISO 260809): the parameters land and survive a reboot on Limine, systemd-boot and GRUB;
|
||||
disabling restores the original; on Limine the pacman hook keeps the early-load file right across kernel reinstalls
|
||||
(with fake NVIDIA modules). Not proven: that the fix cures the 5080's picture, the real NVIDIA modules in the
|
||||
initramfs, DKMS hook order, systemd-boot/GRUB with a pacman transaction, and the RTX 20+ check against a real chwd list.
|
||||
Unit test (no hardware): `bash tests/nvidia-test.sh`.
|
||||
@@ -0,0 +1,64 @@
|
||||
# NVIDIA and gamescope: research notes (for steamify-cachyos branch `feature/nvidia-gaming-fix`)
|
||||
|
||||
Written 2026-10-01 for the NVIDIA fix (`lib/nvidia.sh`). Delete the `nvidia/` folder when the feature is released, or move what is still useful to steamify-cachyos' `TECHNICAL.md`.
|
||||
"Verified" means seen in this session (a VM run or a fetched page); the rest is from search results or memory.
|
||||
|
||||
## The user's PC
|
||||
RTX 5080 (Blackwell). Blackwell only works with NVIDIA's open kernel modules (`nvidia-open`), driver 570+.
|
||||
The corrupted picture at gaming mode start is not diagnosed yet: the fix adds kernel parameters, the real cause
|
||||
may be the gamescope/driver combination. `tests/nvidia-hardware-test.sh` collects the facts.
|
||||
|
||||
## What the fix changes and why (reasoning, not proven for gamescope)
|
||||
- gamescope runs the display itself through DRM/KMS. With NVIDIA that needs `nvidia-drm.modeset=1`; `nvidia-drm.fbdev=1`
|
||||
gives the console a framebuffer from `nvidia-drm`, so the hand-off from the console to gamescope is clean.
|
||||
- Loading the NVIDIA modules in the initramfs (early KMS) only changes how early boot looks (native resolution,
|
||||
splash, passphrase prompt), not speed. It is not needed for gamescope, which starts after login (believed, not proven).
|
||||
- No source found that mentions these parameters for gamescope; the fix is a standard NVIDIA/Wayland setup, applied
|
||||
automatically. Whether it cures the 5080's picture is only known after `tests/nvidia-hardware-test.sh`.
|
||||
|
||||
## Which cards (decision 2026-10-01: RTX 20 series or newer only, same check as the VRAM booster)
|
||||
The code skips a card whose PCI id is on chwd's legacy lists (`vram_nvidia_legacy_id`); the notes below are why older
|
||||
cards are not promised.
|
||||
- Open kernel modules: Turing (RTX 20, GTX 16) and newer only; they need the GSP processor first built into Turing.
|
||||
Maxwell, Pascal and Volta (GTX 900/10 series, Titan V) only work with the proprietary driver, whose legacy branch
|
||||
is 580. Source: [NVIDIA README, open kernel modules](https://download.nvidia.com/XFree86/Linux-x86_64/560.35.03/README/kernel_open.html),
|
||||
[NVIDIA datacenter driver guide, kernel modules](https://docs.nvidia.com/datacenter/tesla/driver-installation-guide/kernel-modules.html).
|
||||
- `nvidia-drm.modeset` and `fbdev` are options of the `nvidia-drm` module in both flavours, so the fix applies to
|
||||
any card whose driver provides `nvidia_drm` (what `nvidia_present` tests: GPU vendor 0x10de + `modinfo nvidia_drm`).
|
||||
Whether gamescope then works on an older card is a different question, and no source says the fix helps there.
|
||||
- Not NVIDIA's proprietary driver: nouveau has no `nvidia_drm`, so the fix does nothing. The CachyOS handheld ISO boots
|
||||
GTX 10xx and older with nouveau until the NVIDIA driver is installed
|
||||
([CachyOS forum](https://discuss.cachyos.org/t/information-experimental-cachyos-handheld-edition/203)).
|
||||
|
||||
## Known gamescope problems that are not this fix
|
||||
- **GTX 1050 Ti (Pascal), driver 580.119.02, gamescope newer than 3.16.16:** the CachyOS handheld session fails to start when
|
||||
a display is on the GPU's HDMI port (back to the TTY, loop). Workarounds: downgrade `gamescope` and `lib32-gamescope`
|
||||
to 3.16.16, or use the motherboard's video port. Fixed upstream? unknown.
|
||||
[CachyOS forum](https://discuss.cachyos.org/t/no-display-on-cachyos-handheld-edition-on-nvidia-with-gamescope-3-16-16/20935)
|
||||
- **VRS on PCs:** CachyOS' gamescope-session enables variable rate shading (`STEAM_USE_DYNAMIC_VRS=1`,
|
||||
`RADV_FORCE_VRS_CONFIG_FILE`, `echo 1x1 > ...` in `/usr/lib/steamos/gamescope-session`). It broke rendering (missing
|
||||
floors/lighting, bad post-processing) on a PC with an AMD RX 6800 XT. RADV is AMD's driver: not the NVIDIA corruption.
|
||||
Workaround: comment those three lines, or per game `env -u RADV_FORCE_VRS_CONFIG_FILE STEAM_USE_DYNAMIC_VRS=0 %command%`.
|
||||
[CachyOS forum](https://discuss.cachyos.org/t/cachyos-gamescope-session-asset-missing-vrs-fix/34971)
|
||||
- gamescope's DRM backend needs Vulkan DRM format modifiers; with the open Mesa driver NVK that came in Mesa 24.1
|
||||
([GamingOnLinux](https://www.gamingonlinux.com/2024/05/nvk-driver-gets-drm-format-modifiers-to-work-with-gamescope-in-mesa-24-1)).
|
||||
The proprietary driver had format-modifier trouble with gamescope too (GitHub issues
|
||||
[ValveSoftware/gamescope#1662](https://github.com/ValveSoftware/gamescope/issues/1662),
|
||||
[#1516](https://github.com/ValveSoftware/gamescope/issues/1516): titles seen only, not read).
|
||||
|
||||
## What was verified in VMs (QEMU/KVM, CachyOS ISO 260809)
|
||||
- Kernel parameters land and survive a reboot on Limine, systemd-boot and GRUB; disable restores the original.
|
||||
- Limine's tool is a compiled program: an appended `KERNEL_CMDLINE[default]+=" ..."` line was pasted into the kernel
|
||||
command line as text; the fix edits the existing `KERNEL_CMDLINE[default]="..."` line.
|
||||
- `mkinitcpio` fails on a MODULES entry a kernel lacks; `limine-mkinitcpio` then skips that kernel's initramfs and boot
|
||||
entry (parameters included); systemd-boot/GRUB image: "may not be complete" (not run with failing modules).
|
||||
- Real CachyOS/Limine pacman hooks: `10-limine-snapper-lock`, `60-limine-mkinitcpio-remove-pre`, `60-mkinitcpio-remove`,
|
||||
`80-limine-efi-deploy`, `90-limine-mkinitcpio-remove-post`, `90-mkinitcpio-install`; Steamify's hook is `85-`
|
||||
(DKMS's is `71-dkms-install`, not installed in the VM). A mirror older than the ISO "downgrades" kernels on reinstall.
|
||||
- The hook keeps the early-load drop-in right across kernel changes (see `nvidia/TODO.md`).
|
||||
|
||||
## Still unknown (needs the hardware)
|
||||
- Does the fix cure the 5080's corrupted picture? Which gamescope/driver versions does the PC have?
|
||||
- Does it help, harm or do nothing on Maxwell/Pascal/Volta cards? No older card available yet.
|
||||
- Real NVIDIA modules in the initramfs (VMs only had renamed fake modules); DKMS hook order on a PC with
|
||||
`nvidia-open-dkms`.
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# TODO: NVIDIA (status 2026-10-01, evening: READ THIS FIRST, the sections below are the older plan)
|
||||
|
||||
Version: bumped to **2.10.0** (2026-10-01, "chore: Version 2.10.0" on `feature/nvidia-gaming-fix`; `feature/extended-controller-support` is stacked on it).
|
||||
The old plan below says 2.9.7 / `release/2.9.7`: read that as 2.10.0 / `release/2.10.0`, which doesn't exist yet (create it from `main` when releasing).
|
||||
|
||||
The kernel-parameter fix did not cure gamescope's picture; the cause is NVIDIA's own bug (see `HARDWARE-RESULTS.md`). The product now does this
|
||||
instead (branch `feature/nvidia-gaming-fix`, 2.10.0, still behind `.no-release-yet`):
|
||||
- On an NVIDIA PC the SteamOS conversion (`gaming`, `boot`, `glyphs`) is hidden (shown only while already on, so it can be turned off).
|
||||
- `nvidia` "Gaming on NVIDIA": Steam installed when missing, started at login on the Plasma desktop (user unit); sub-option `bigpicture`
|
||||
(a checkbox, not yet the choice row "How should Steam start up: Normal / Big Picture"): `-gamepadui` + Plasma starts with an empty session.
|
||||
RTX 20+ also keeps the old kernel parameters and early-load initramfs (user asked for it: "it may need it").
|
||||
- `single` (single user mode) also works there: its own SDDM autologin into the Plasma session (`zzz-steamify-autologin.conf`).
|
||||
- Tested in VMs (QEMU/KVM, fake NVIDIA GPU through `NVIDIA_DRM_DIR`, fake modules): suite `nvidia`, TESTPLAN N1-N5, 43 pass; regression suites green.
|
||||
- Tested by hand on the RTX 5080 (earlier prototypes): Big Picture in the normal Plasma session smooth and clean; autologin and the final component NOT yet run there.
|
||||
Open: run the final wizard on the RTX 5080 PC (turn the conversion off, tick Gaming on NVIDIA + Single user mode, reboot); the real choice row
|
||||
instead of the checkbox (the choice row is hard-wired to `boot` in the TUI, the app and `--boot`); games don't start fullscreen under Plasma
|
||||
(idea: a KWin rule forcing fullscreen for `steam_app_*`, may bring the direct-scanout artifacts back: test on the PC); apps that start later
|
||||
(CachyOS Hello) may still cover Big Picture; hint in the menu on why the item is missing while the conversion is on; changelog hashes;
|
||||
screenshot retake and the ISO's installer page for the new rows before a release. Separate branch `feature/extended-controller-support`
|
||||
(xone dongle + xpadneo, opt-in) is stacked on this one.
|
||||
|
||||
# TODO: NVIDIA fix for gaming mode
|
||||
|
||||
Product code: branch `feature/nvidia-gaming-fix` in steamify-cachyos (github.com/theupriser/steamify-cachyos). These notes live here, in
|
||||
steamify-cachyos-dev, on purpose: they are not part of the product and must not be in its history. The branch only has
|
||||
a `.no-release-yet` marker pointing here.
|
||||
|
||||
When the feature is released, move what is still useful to steamify-cachyos' `TECHNICAL.md` and delete this folder (`nvidia/`).
|
||||
Written 2026-10-01 so a new session can pick this up.
|
||||
|
||||
## Problem
|
||||
A PC with an NVIDIA RTX 5080 (Blackwell, open kernel modules) shows a corrupted
|
||||
image when gaming mode (gamescope) starts. Cause not confirmed. Steamify had no
|
||||
NVIDIA handling at all. Likely: `nvidia-drm.modeset=1` / `nvidia-drm.fbdev=1`
|
||||
missing, or a gamescope/driver mismatch.
|
||||
|
||||
## What is built (all on the product branch, pushed)
|
||||
- `lib/nvidia.sh`: part of the SteamOS conversion (`gaming_enable` / `gaming_disable`).
|
||||
With an NVIDIA GPU + driver it adds `nvidia-drm.modeset=1 nvidia-drm.fbdev=1` to the
|
||||
kernel command line (Limine, systemd-boot, GRUB; backup first) and, only when every
|
||||
installed kernel has `nvidia_drm`, `/etc/mkinitcpio.conf.d/90-steamify-nvidia.conf`.
|
||||
Rebuilds initramfs + boot entries, verifies the config and the generated entries
|
||||
have the parameters (error otherwise). Undone on disable. Description: `TECHNICAL.md`.
|
||||
- `FEATURE_VERSION[gaming]=2.9.7` so installs that already have the conversion re-apply it.
|
||||
- Test hooks `NVIDIA_DRM_DIR`, `NVIDIA_MODULES_DIR`, `NVIDIA_LIMINE_CONF`,
|
||||
`NVIDIA_SDBOOT_DIR`, `NVIDIA_GRUB_CFG`: only set by tests; defaults are the real paths.
|
||||
|
||||
## Tested
|
||||
- `bash tests/nvidia-test.sh`: 22 checks against a fake RTX 5080 beside an iGPU, a stub
|
||||
`modinfo`, temp boot loader files. All pass.
|
||||
- Real VMs (QEMU/KVM, Limine, systemd-boot, GRUB): enable, reboot, kernel command line
|
||||
has the parameters; disable, reboot, original command line. All pass. Found and fixed:
|
||||
Limine pasted an appended `+=` line as text into the cmdline; a kernel without the
|
||||
NVIDIA modules made limine-mkinitcpio skip its boot entry.
|
||||
|
||||
## Kernel changes (done, VM-tested 2026-10-01, Limine)
|
||||
The early-load drop-in is kept right by a pacman hook (`patches/steamify-nvidia-initramfs.{sh,hook}`,
|
||||
installed by `nvidia_enable`, `/etc/pacman.d/hooks/85-steamify-nvidia-initramfs.hook`). Real pacman
|
||||
transactions in a Limine VM: the hook runs between "Updating module dependencies" and "Updating
|
||||
linux initcpios" (real hook names there: 60-*-remove, 80-limine-efi-deploy, 90-limine-mkinitcpio-remove-post,
|
||||
90-mkinitcpio-install; DKMS would be 71-): the drop-in is present while every kernel has the modules, removed
|
||||
when the LTS kernel loses them, back when they return; initramfs nvidia files 1 -> 5 -> 1 -> 5 -> 1
|
||||
(1 = baseline, 4 modules added); no ERROR/skipping in any rebuild; cmdline kept the parameters after a reboot;
|
||||
disable removes hook, script and drop-in. Found on the way: an empty hook/script was installed when
|
||||
`patch_file` failed (now an error). Only tested with fake modules (renamed copies of a small module), on
|
||||
Limine; systemd-boot and GRUB use the same hook but were not run with pacman transactions.
|
||||
|
||||
## Opt-out toggle (new request, 2026-10-01: not built yet)
|
||||
When a compatible NVIDIA card is listed there should be a menu option, on by default (opt-out), named
|
||||
"NVIDIA compatibility", as a sub-option of the SteamOS conversion, directly under "Boot into" (`boot`).
|
||||
Today the fix runs unconditionally inside `gaming_enable`/`gaming_disable`.
|
||||
- [ ] New component `nvidia` in `lib/menu.sh`: `COMPONENTS` right after `boot`, `PARENT[nvidia]=gaming`, `LABEL`,
|
||||
`FEATURE_VERSION[nvidia]`, `component_available` -> `nvidia_available` (= `nvidia_present`); not in
|
||||
`NO_PRESELECT` (ticked by default, `feature_new` ticks it for installs that already have the conversion).
|
||||
`nvidia_status` from the system (hook + parameters present), `nvidia_enable`/`nvidia_disable` = the code now
|
||||
called from `gaming_enable`/`gaming_disable` (remove those calls). Check `toggle_component` dependencies,
|
||||
`feature_record_unticked`, the plan texts ("This will: ...").
|
||||
- [ ] The app: `ui/qml/Texts.qml` item texts, `AppState.qml`, the screen rows, `lib/backend.sh` if it lists ids;
|
||||
`--defaults [--options <ids>]` (the Steam Machine ISO's installer pages: it names the ids) and `--skip`.
|
||||
- [ ] README rows/screenshot rule (a release that adds a menu row retakes `assets/screenshot-menu.png`), TECHNICAL.md,
|
||||
CHANGELOG, `tests/nvidia-test.sh` (status/enable/disable through the component), VM menu test in
|
||||
steamify-cachyos-dev (`share/vmtest/menu`, TESTPLAN.md row).
|
||||
- [x] What "compatible" means (decided 2026-10-01): RTX 20 series or newer, the same check as the VRAM booster
|
||||
(`vram_nvidia_legacy_id`: a card on chwd's legacy lists `/var/lib/chwd/ids/nvidia-*.ids` is older; without the
|
||||
lists every card counts). `nvidia_present` = such a card + `nvidia_drm` available; the toggle's `nvidia_available`
|
||||
is the same function. Older cards (GTX 10/9 series) are left alone: no promise they work with gamescope.
|
||||
|
||||
## Older cards and other research
|
||||
Decision 2026-10-01: support RTX 20 series or newer only, with the VRAM booster's check (done in `lib/nvidia.sh`).
|
||||
The user had hoped older GPUs would work too; not promised. Full notes with sources: **`nvidia/RESEARCH.md`**.
|
||||
Short version: open kernel modules need Turing+, Maxwell/Pascal/Volta need the proprietary driver (580 legacy branch);
|
||||
`nvidia-drm.modeset/fbdev` exist in both, so the fix applies to any card with `nvidia_drm`, but nothing found says gamescope
|
||||
works better or worse there (a GTX 1050 Ti has a known gamescope>3.16.16 start problem on HDMI, not ours). Cannot be promised:
|
||||
needs a test on a real older card. Design for the toggle: on by default for any card with `nvidia_drm`, the label says
|
||||
what it does, `tests/nvidia-hardware-test.sh` reports GPU and driver so results can be compared.
|
||||
|
||||
## Still to do
|
||||
1. [ ] **On the NVIDIA PC** (only place the real fix can be judged; the full steps, how to read the results and the undo
|
||||
are in `nvidia/INSTRUCTIONS.md`), from the steamify-cachyos checkout on that branch:
|
||||
`bash tests/nvidia-hardware-test.sh check` (before), `... apply` (asks sudo), reboot,
|
||||
`... check` (after: `nvidia_drm modeset/fbdev = Y`), boot gaming mode, then
|
||||
`... visual` (was the picture clean?). Everything goes to `~/steamify-nvidia-report.txt`:
|
||||
paste it into the session.
|
||||
2. [ ] If the parameters apply but the picture is still corrupted: look at the report's
|
||||
gamescope/NVIDIA log lines and the versions (`nvidia-open`, `nvidia-utils`, `gamescope`,
|
||||
`gamescope-session-cachyos`); the cause is then probably a gamescope/driver mismatch
|
||||
for Blackwell, not this fix.
|
||||
3. [ ] Not covered by any test: the early-load drop-in with the REAL NVIDIA modules (the VMs only have renamed
|
||||
fake ones), and systemd-boot/GRUB with a pacman transaction (only Limine was run).
|
||||
4. [ ] **Verify the RTX 20+ check on the PC** (decided 2026-10-01, only unit-tested with a fake chwd list; this laptop and the VMs
|
||||
have no `/var/lib/chwd/ids`): `bash tests/nvidia-hardware-test.sh check` prints the NVIDIA PCI id, whether it is on a chwd legacy
|
||||
list, and whether the lists exist at all. Expect for the RTX 5080: "supported", "not on a legacy list". If it says "no chwd
|
||||
legacy lists found" the check cannot tell old from new there (every card then counts as supported, like the VRAM booster):
|
||||
look at what `chwd`/`/var/lib/chwd/ids` provide on CachyOS and whether another source (the open-driver device list, the
|
||||
`nvidia-open` package) would be better for both the VRAM booster and this fix. Also try `check` on any older NVIDIA card
|
||||
that turns up (GTX 10/9 series): it must say "older than RTX 20 ... not applied" and `apply` must change nothing.
|
||||
5. [ ] Changelog: fill in the commit hashes of this feature's lines (next commit, per AGENTS.md).
|
||||
6. [ ] Build the opt-out toggle (section above) before release.
|
||||
7. [ ] When complete and tested: ask the user whether it may be released. On a yes delete
|
||||
`.no-release-yet` (and this folder in the dev repo), open the PR into `release/2.9.7` (local branch only so
|
||||
far, one commit `chore: Version 2.9.7`; push it then) and merge it. The user merges
|
||||
`release/2.9.7` into `main`.
|
||||
|
||||
## Working rules (steamify-cachyos `AGENTS.md` and this repo's `AGENTS.md`)
|
||||
- Never work on `main` or a `release/*` branch; no `Co-Authored-By` / "Generated with
|
||||
Claude" lines in commits or PRs. No git identity on the dev laptop: use
|
||||
`git -c user.name="Rick Peters" -c user.email="rickpeters@upriser.nl" commit`.
|
||||
- Don't push `release/2.9.7` before the PR (a push can start a dev ISO build).
|
||||
|
||||
## If the VM tests need repeating (dev laptop, Ubuntu)
|
||||
- `scripts/vminstall.sh` (steamify-cachyos-dev) with `VM_BOOTLOADER=limine|systemd-boot|grub`,
|
||||
`CI=1`, a short `VM_DIR` (`~/vms/...`: socket paths over 108 bytes fail) and `VM_SSH_KEY`.
|
||||
- This session's shell lacked the `kvm` group: run QEMU steps as `sg kvm -c "..."`.
|
||||
- `/tmp/vminstall-iso.lock` belongs to another user and there is no `bsdtar`: use a copy of
|
||||
`vminstall.sh` with another lock path and a `bsdtar` shim (Python `pycdlib` in a venv).
|
||||
- Delete everything afterwards (`~/vms` incl. `pkg-cache`, the 3 GB ISO, keys).
|
||||
@@ -1,29 +0,0 @@
|
||||
# CachyOS test VM for steamify.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 <user>@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
|
||||
@@ -1,11 +1,19 @@
|
||||
#!/bin/bash
|
||||
# CachyOS test VM for steamify.sh.
|
||||
# [REPO=/path/to/cachyos-gamescope-boot] ./run.sh [install] [--nvidia] [--vulkan] [--fremont]
|
||||
# [REPO=/path/to/cachyos-gamescope-boot] ./run.sh [install] [--nvidia] [--vulkan] [--amd] [--fremont] [--headless]
|
||||
# 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)
|
||||
# --amd with --vulkan: venus on the host AMD iGPU (RADV) instead of NVIDIA
|
||||
# --fremont report the Valve Steam Machine's DMI data (for testing the wizard)
|
||||
# --headless no window (unattended tests, CI); a VM you start by hand has one. VM_HEADLESS=1 does the same
|
||||
# REPO defaults to $HOME/projects/cachyos-gamescope-boot.
|
||||
# VM_ISO=<path> boots that ISO for `install` instead of cachyos.iso; with
|
||||
# VM_KERNEL/VM_INITRD/VM_APPEND its kernel is booted directly with those
|
||||
# parameters (scripts/vminstall.sh, the unattended install).
|
||||
# VM_MEM=<size>: guest RAM (8G; the automated tests use 4G so three VMs fit next to a desktop).
|
||||
# VM_CPUS=<n>: guest CPUs (6); ISO builds use ~75% of host cores (nproc * 3 / 4), so the host stays usable.
|
||||
# VM_PORT=<n>: the host port for the guest's SSH (2222), e.g. a second VM.
|
||||
# BIOS_VERSION=F7F0107 makes the guest report that BIOS version (DMI), e.g.
|
||||
# to test the wizard's BIOS update item; the firmware itself doesn't change.
|
||||
# The repo is shared into the guest; mount it there with:
|
||||
@@ -14,39 +22,66 @@
|
||||
# sudo mount -t 9p -o trans=virtio,version=9p2000.L vmtools /media && /media/guest-ssh-setup.sh
|
||||
# SSH from the host: ssh -p 2222 <user>@localhost
|
||||
cd "$(dirname "$0")"
|
||||
# REPO: from the environment, else what scripts/vminstall.sh recorded next
|
||||
# to the disk (repo-path), else the old default.
|
||||
[[ -z "${REPO:-}" && -f repo-path ]] && REPO="$(cat repo-path)"
|
||||
repo="${REPO:-$HOME/projects/cachyos-gamescope-boot}"
|
||||
|
||||
cdrom=()
|
||||
direct=()
|
||||
[[ -n "${VM_KERNEL:-}" ]] && direct=(-kernel "$VM_KERNEL" -initrd "$VM_INITRD" -append "$VM_APPEND")
|
||||
# VM_CACHE=<host dir>: shared read-write as 9p tag `cache` (the ISO build's
|
||||
# package cache, scripts/vmisobuild.sh; one per VM, e.g. $VM_DIR/iso-cache),
|
||||
# so snapshot resets don't lose it.
|
||||
[[ -n "${VM_CACHE:-}" ]] && { mkdir -p "$VM_CACHE"; direct+=(-virtfs "local,path=$VM_CACHE,mount_tag=cache,security_model=mapped-xattr"); }
|
||||
# VM_SERIAL=<file>: the guest's serial console (console=ttyS0) logged into
|
||||
# that file, and reachable as a socket next to it (<file>.sock) to type into.
|
||||
[[ -n "${VM_SERIAL:-}" ]] && direct+=(-chardev "socket,id=ser0,path=$VM_SERIAL.sock,server=on,wait=off,logfile=$VM_SERIAL" -serial chardev:ser0)
|
||||
smbios=()
|
||||
gpu=virtio-vga-gl,xres=1920,yres=1080
|
||||
gpu=virtio-vga-gl,xres=1920,yres=1080 gl=on
|
||||
# VM_NOGL=1: no virgl (software rendering in the guest), so QMP screendump
|
||||
# works (scripts/qmpshot.py); for the live ISO and Calamares.
|
||||
[[ -n "${VM_NOGL:-}" ]] && { gpu=virtio-vga,xres=1920,yres=1080; gl=off; }
|
||||
[[ -n "${BIOS_VERSION:-}" ]] && smbios+=(-smbios "type=0,version=$BIOS_VERSION")
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
install) cdrom=(-cdrom cachyos.iso -boot d) ;;
|
||||
install) cdrom=(-cdrom "${VM_ISO:-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 ;;
|
||||
--amd) export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/radeon_icd.json ;;
|
||||
--headless) VM_HEADLESS=1 ;;
|
||||
--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
|
||||
|
||||
# VM_HEADLESS=1: no window (unattended tests, CI): plain virtio-vga and -display none;
|
||||
# QMP screendump works then too. The guest and ssh don't notice.
|
||||
display=(-display "gtk,gl=$gl,zoom-to-fit=off")
|
||||
[[ -n "${VM_HEADLESS:-}" ]] && { gpu=virtio-vga,xres=1920,yres=1080; display=(-display none); }
|
||||
|
||||
# UEFI firmware: Ubuntu's path, or Arch's (edk2-ovmf).
|
||||
ovmf=/usr/share/OVMF; ovmf_code=OVMF_CODE_4M.fd; ovmf_vars=OVMF_VARS_4M.fd
|
||||
[[ -f "$ovmf/$ovmf_code" ]] || { ovmf=/usr/share/edk2/x64; ovmf_code=OVMF_CODE.4m.fd; ovmf_vars=OVMF_VARS.4m.fd; }
|
||||
|
||||
# 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
|
||||
[[ -f vars.fd ]] || cp "$ovmf/$ovmf_vars" 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 \
|
||||
-enable-kvm -machine q35,memory-backend=mem -cpu host -smp "${VM_CPUS:-6}" -m "${VM_MEM:-8G}" \
|
||||
-object memory-backend-memfd,id=mem,size=${VM_MEM:-8G},share=on \
|
||||
-drive if=pflash,format=raw,readonly=on,file="$ovmf/$ovmf_code" \
|
||||
-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 "$gpu" "${display[@]}" \
|
||||
-device qemu-xhci -device usb-tablet \
|
||||
-nic user,model=virtio-net-pci,hostfwd=tcp::2222-:22 \
|
||||
-nic user,model=virtio-net-pci,hostfwd=tcp::${VM_PORT:-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[@]}"
|
||||
-qmp unix:"$PWD/qmp.sock",server=on,wait=off \
|
||||
"${smbios[@]}" "${cdrom[@]}" "${direct[@]}"
|
||||
+35
-2
@@ -1,5 +1,38 @@
|
||||
# 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" "$@"; }
|
||||
# Where disk.qcow2, vars*.fd and run.sh live: this repo if it has a disk,
|
||||
# else ~/vms/cachyos-test.
|
||||
_repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
if [[ -z "${VM_DIR:-}" ]]; then
|
||||
if [[ -f "$_repo/disk.qcow2" ]]; then VM_DIR="$_repo"; else VM_DIR="$HOME/vms/cachyos-test"; fi
|
||||
fi
|
||||
# The guest user: what scripts/vminstall.sh created ($VM_DIR/vm-user), else
|
||||
# theupriser (VMs installed by hand).
|
||||
if [[ -z "${VM_USER:-}" && -f "$VM_DIR/vm-user" ]]; then VM_USER="$(cat "$VM_DIR/vm-user")"; fi
|
||||
VM_USER="${VM_USER:-theupriser}"
|
||||
# scripts/vminstall.sh records the key it authorized in $VM_DIR/ssh-key.
|
||||
# The VM's host key lives next to its disk, never in ~/.ssh/known_hosts: a
|
||||
# reinstalled VM gets a new one on the same localhost port.
|
||||
VM_SSH_OPTS=(-o BatchMode=yes -o "UserKnownHostsFile=$VM_DIR/known_hosts" -o StrictHostKeyChecking=accept-new)
|
||||
if [[ -z "${VM_SSH_KEY:-}" && -f "$VM_DIR/ssh-key" ]]; then VM_SSH_KEY="$(cat "$VM_DIR/ssh-key")"; fi
|
||||
[[ -n "${VM_SSH_KEY:-}" ]] && VM_SSH_OPTS+=(-i "$VM_SSH_KEY" -o IdentitiesOnly=yes)
|
||||
# The VM's key, looked up at every call: vminstall.sh and vmbootloadertest.sh source this file before the key
|
||||
# exists or is chosen (a clean host has no other key the VM would accept).
|
||||
vm_key() {
|
||||
if [[ -n "${VM_SSH_KEY:-}" ]]; then printf '%s\n' "$VM_SSH_KEY"
|
||||
elif [[ -f "$VM_DIR/ssh-key" ]]; then cat "$VM_DIR/ssh-key"; fi
|
||||
}
|
||||
_vm_key_opts() { local k; k="$(vm_key)"; [[ -n "$k" ]] && printf '%s\n' -i "$k" -o IdentitiesOnly=yes; }
|
||||
vm_ssh() { local -a k; mapfile -t k < <(_vm_key_opts); ssh -p "$VM_PORT" "${VM_SSH_OPTS[@]}" "${k[@]}" "$VM_USER@$VM_HOST" "$@"; }
|
||||
vm_scp() { local -a k; mapfile -t k < <(_vm_key_opts); scp -q -P "$VM_PORT" "${VM_SSH_OPTS[@]}" "${k[@]}" "$@"; }
|
||||
|
||||
# One Konsole for every test run: it follows $TEST_LOG, which the test scripts append to.
|
||||
# Started only when there is a desktop, the run is headless and no such window exists.
|
||||
TEST_LOG="${TEST_LOG:-$HOME/vms/test.log}"
|
||||
view_start() {
|
||||
[[ -n "${VM_HEADLESS:-}" && -z "${CI:-}" && -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ]] && command -v konsole >/dev/null || return 0
|
||||
systemctl --user is-active --quiet vmtest-view 2>/dev/null && return 0
|
||||
mkdir -p "$(dirname "$TEST_LOG")"; touch "$TEST_LOG"
|
||||
systemd-run --user -q --collect --unit=vmtest-view konsole --hold -e tail -n 60 -F "$TEST_LOG" >/dev/null 2>&1 || true
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# qmpclick.py <qmp socket> <screen w> <screen h> <x,y>...: left-clicks at
|
||||
# those guest-screen pixels (the usb-tablet from run.sh, absolute 0..32767).
|
||||
import json, socket, sys, time
|
||||
sock, w, h = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
|
||||
s = socket.socket(socket.AF_UNIX); s.connect(sock); f = s.makefile("rw"); f.readline()
|
||||
def cmd(c):
|
||||
f.write(json.dumps(c) + "\n"); f.flush(); return f.readline()
|
||||
cmd({"execute": "qmp_capabilities"})
|
||||
def ev(*events): cmd({"execute": "input-send-event", "arguments": {"events": list(events)}})
|
||||
for pos in sys.argv[4:]:
|
||||
x, y = (int(v) for v in pos.split(","))
|
||||
ev({"type": "abs", "data": {"axis": "x", "value": x * 32767 // (w - 1)}},
|
||||
{"type": "abs", "data": {"axis": "y", "value": y * 32767 // (h - 1)}})
|
||||
time.sleep(0.2)
|
||||
ev({"type": "btn", "data": {"down": True, "button": "left"}}); time.sleep(0.08)
|
||||
ev({"type": "btn", "data": {"down": False, "button": "left"}}); time.sleep(0.8)
|
||||
@@ -0,0 +1,8 @@
|
||||
# qmpkey.py <qmp socket> <combo>...: presses keys in the VM (QEMU qcodes), e.g. down right ctrl-ret ret esc
|
||||
import socket, json, sys, time
|
||||
s = socket.socket(socket.AF_UNIX); s.connect(sys.argv[1]); f = s.makefile("rw"); f.readline()
|
||||
def cmd(c): f.write(json.dumps(c) + "\n"); f.flush(); return f.readline()
|
||||
cmd({"execute": "qmp_capabilities"})
|
||||
for combo in sys.argv[2:]:
|
||||
cmd({"execute": "send-key", "arguments": {"keys": [{"type": "qcode", "data": k} for k in combo.split("-")]}})
|
||||
time.sleep(0.4)
|
||||
@@ -0,0 +1,27 @@
|
||||
# qmpshot.py <qmp socket> <out.png>: screenshot of the VM's screen over QMP
|
||||
# (screendump). Needs a VM without virgl (run.sh with VM_NOGL=1): with
|
||||
# virgl QEMU answers "no surface". Before the guest's desktop is up it shows
|
||||
# whatever is on screen (boot menu, Calamares, ...).
|
||||
import json, os, socket, sys, tempfile
|
||||
|
||||
sock, out = sys.argv[1], sys.argv[2]
|
||||
s = socket.socket(socket.AF_UNIX); s.connect(sock); f = s.makefile("rw"); f.readline()
|
||||
def cmd(c):
|
||||
f.write(json.dumps(c) + "\n"); f.flush()
|
||||
while True:
|
||||
r = json.loads(f.readline())
|
||||
if "event" not in r:
|
||||
return r
|
||||
cmd({"execute": "qmp_capabilities"})
|
||||
ppm = tempfile.mktemp(suffix=".ppm")
|
||||
r = cmd({"execute": "screendump", "arguments": {"filename": ppm, "format": "ppm"}})
|
||||
if "error" in r:
|
||||
raise SystemExit("screendump: %s" % r["error"]["desc"])
|
||||
try:
|
||||
from PIL import Image
|
||||
Image.open(ppm).save(out)
|
||||
os.remove(ppm)
|
||||
except ImportError:
|
||||
os.replace(ppm, os.path.splitext(out)[0] + ".ppm")
|
||||
print("PIL missing: wrote a .ppm instead", file=sys.stderr)
|
||||
print(out)
|
||||
@@ -0,0 +1,23 @@
|
||||
# qmptype.py <qmp socket> <text>: types <text> (US layout) into the VM, then Enter.
|
||||
import socket, json, sys, time
|
||||
sock, text = sys.argv[1], sys.argv[2]
|
||||
s = socket.socket(socket.AF_UNIX); s.connect(sock); f = s.makefile("rw"); f.readline()
|
||||
def cmd(c):
|
||||
f.write(json.dumps(c) + "\n"); f.flush(); return f.readline()
|
||||
cmd({"execute": "qmp_capabilities"})
|
||||
plain = {" ": "spc", "/": "slash", ".": "dot", "-": "minus", "'": "apostrophe", "\n": "ret",
|
||||
"=": "equal", ",": "comma", ";": "semicolon", "\\": "backslash", "[": "bracket_left", "]": "bracket_right"}
|
||||
shifted = {"&": "7", "*": "8", "_": "minus", ":": "semicolon", "|": "backslash", "~": "grave_accent",
|
||||
"!": "1", "@": "2", "#": "3", "$": "4", "%": "5", "^": "6", "(": "9", ")": "0", "+": "equal", '"': "apostrophe",
|
||||
">": "dot", "<": "comma", "?": "slash", "{": "bracket_left", "}": "bracket_right"}
|
||||
for ch in text + "\n":
|
||||
keys = []
|
||||
if ch.isalpha():
|
||||
if ch.isupper(): keys.append("shift")
|
||||
keys.append(ch.lower())
|
||||
elif ch.isdigit(): keys.append(ch)
|
||||
elif ch in plain: keys.append(plain[ch])
|
||||
elif ch in shifted: keys += ["shift", shifted[ch]]
|
||||
else: raise SystemExit("no key for %r" % ch)
|
||||
cmd({"execute": "send-key", "arguments": {"keys": [{"type": "qcode", "data": k} for k in keys]}})
|
||||
time.sleep(0.03)
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Authorize the test VM's SSH key (made by vminstall.sh) on the Steam Machine
|
||||
# too, so the same key reaches both. Asks the Steam Machine user's password
|
||||
# once (ssh-copy-id); adds the key only if it isn't there yet.
|
||||
# scripts/steammachine-addkey.sh [user@host] default: steammachine (~/.ssh/config)
|
||||
# Env: VM_SSH_KEY (default ~/.ssh/steamify-vm_ed25519).
|
||||
set -euo pipefail
|
||||
key="${VM_SSH_KEY:-$HOME/.ssh/steamify-vm_ed25519}"
|
||||
target="${1:-steammachine}"
|
||||
[[ -f "$key.pub" ]] || { echo "No $key.pub: run scripts/vminstall.sh first (it makes the key)." >&2; exit 1; }
|
||||
ssh-copy-id -i "$key.pub" "$target"
|
||||
ssh -i "$key" -o IdentitiesOnly=yes -o BatchMode=yes "$target" true &&
|
||||
echo "OK: ssh -i $key $target works without a password."
|
||||
Executable
+149
@@ -0,0 +1,149 @@
|
||||
#!/bin/bash
|
||||
# Does a boot loader work with Steamify's kernel side (the Steam Machine's DKMS
|
||||
# modules)? On a VM that reports Fremont hardware: applies Steamify's options,
|
||||
# checks the modules for every kernel, Steamify's boot-loader code (removal of
|
||||
# the old drm.edid_firmware parameter), a kernel update, and a reboot into
|
||||
# each of the two kernels. Guest checks: share/bootloader-test/*.sh.
|
||||
# All loaders in one go: scripts/vmtest.sh.
|
||||
# scripts/vmbootloadertest.sh <limine|systemd-boot|grub> [--install] [--quick [--generic]] [--window]
|
||||
# --install first install the VM (scripts/vminstall.sh, from the Steamify ISO)
|
||||
# --quick only what the ISO's install must get right (B1-B5: boots through its own entry, OS name, loader,
|
||||
# Steamify's state and modules as the install left them): no steamify.sh run, kernel update or reboots
|
||||
# --generic with --quick: a plain PC, not a Steam Machine (no --fremont): its Steam Machine items must stay off
|
||||
# --window show the VM's window (default: headless, with a Konsole on the log when there is a desktop)
|
||||
# Env: VM_DIR (default ~/vms/bl-<loader>), VM_ISO (default: the newest ISO in
|
||||
# ../steamify-cachyos-live-iso/out/desktop), REPO (steamify-cachyos, 9p `repo`).
|
||||
# Exit status: the number of failed checks. Only one VM runs at a time (port 2222).
|
||||
set -uo pipefail
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo="$(cd "$here/.." && pwd)"
|
||||
loader="${1:?usage: $0 <limine|systemd-boot|grub> [--install]}"
|
||||
install=false; window=false; quick=false; generic=false
|
||||
for a in "${@:2}"; do
|
||||
case "$a" in --install) install=true ;; --quick) quick=true ;; --generic) generic=true ;; --window) window=true ;; *) echo "Unknown argument: $a" >&2; exit 2 ;; esac
|
||||
done
|
||||
# No human is needed: headless (no VM window) unless --window. On a desktop a Konsole follows the log.
|
||||
$window || export VM_HEADLESS=1
|
||||
case "$loader" in limine|systemd-boot|grub) ;; *) echo "Unknown boot loader: $loader" >&2; exit 2 ;; esac
|
||||
$generic && ! $quick && { echo "--generic needs --quick" >&2; exit 2; }
|
||||
hw=fremont; $generic && hw=generic
|
||||
suffix=""; $generic && suffix="-generic"
|
||||
export VM_DIR="${VM_DIR:-$HOME/vms/bl-$loader$suffix}"
|
||||
hwflags=(--fremont); $generic && hwflags=()
|
||||
# Own ssh port per loader, so the loaders can run side by side (vmtest.sh).
|
||||
off=0; $generic && off=10
|
||||
case "$loader" in limine) export VM_PORT="${VM_PORT:-$((2401 + off))}" ;; systemd-boot) export VM_PORT="${VM_PORT:-$((2402 + off))}" ;; grub) export VM_PORT="${VM_PORT:-$((2403 + off))}" ;; esac
|
||||
. "$here/common.sh"
|
||||
guest="$repo/share/bootloader-test"
|
||||
log="$VM_DIR.test.log"
|
||||
|
||||
step() { printf '\n##### %s %s\n' "$(date +%T)" "$*"; }
|
||||
gssh() { vm_ssh -o ConnectTimeout=6 -o LogLevel=ERROR "$@"; }
|
||||
gscript() { gssh env "LOADER=$loader" "HARDWARE=$hw" bash -s < "$guest/$1"; } # the guest's login shell is fish
|
||||
# Up to ~15 minutes for the first ssh: a first boot is slow when several VMs run at once.
|
||||
waitssh() { local _; for _ in $(seq 180); do gssh 'uptime -p' 2>/dev/null | grep -q '^up' && return 0; sleep 5; done; echo "FAIL no SSH"; return 1; }
|
||||
running() { pgrep -f "[h]ostfwd=tcp::$VM_PORT-" >/dev/null; }
|
||||
stop_vm() {
|
||||
running || return 0
|
||||
# Whichever VM runs: its key and host key are not this VM's.
|
||||
ssh -p "$VM_PORT" -i "$(vm_key)" -o BatchMode=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=ERROR \
|
||||
"$VM_USER@$VM_HOST" 'sudo systemctl poweroff' >/dev/null 2>&1
|
||||
local _; for _ in $(seq 30); do running || return 0; sleep 2; done
|
||||
pkill -f "[h]ostfwd=tcp::$VM_PORT-"; sleep 2
|
||||
}
|
||||
export VM_CACHE="${VM_CACHE-$HOME/vms/pkg-cache}" # 9p tag `cache`: Steamify's downloads once for every VM
|
||||
start_vm() { stop_vm; cp "$repo/run.sh" "$VM_DIR/run.sh"; (cd "$VM_DIR" && VM_MEM="${VM_MEM:-4G}" REPO="${REPO:-$repo/../steamify-cachyos}" setsid nohup ./run.sh "${hwflags[@]}" > vm.log 2>&1 &); }
|
||||
reboot_vm() { gssh 'sudo systemctl reboot' >/dev/null 2>&1; sleep 25; waitssh; }
|
||||
view_start # one shared Konsole (common.sh)
|
||||
# The guest checks print PASS/FAIL lines; the summary at the end counts them in the log.
|
||||
check() { cat; }
|
||||
|
||||
{
|
||||
printf '\n===== vmbootloadertest.sh %s (%s) =====\n' "$loader" "$(date +%T)"
|
||||
if $install; then
|
||||
step "install $loader VM in $VM_DIR"
|
||||
stop_vm
|
||||
iso="${VM_ISO:-$(ls -t "$repo"/../steamify-cachyos-live-iso/out/desktop/*.iso 2>/dev/null | head -n 1)}"
|
||||
[[ -f "$iso" ]] || { echo "No ISO: set VM_ISO"; exit 2; }
|
||||
if [[ -f "$VM_DIR/disk.qcow2" ]]; then
|
||||
[[ -t 0 ]] || { echo "$VM_DIR has a disk; run this from a terminal to replace it"; exit 2; }
|
||||
read -r -p "Replace $VM_DIR/disk.qcow2? Type YES: " a
|
||||
[[ "$a" == YES ]] || exit 1
|
||||
rm -f "$VM_DIR/disk.qcow2"
|
||||
fi
|
||||
# --quick checks what the ISO's install leaves on a Steam Machine: install on a VM that reports Fremont hardware
|
||||
# (a generic PC rightly gets no poweroff/CEC items and no DKMS modules).
|
||||
fremont=(); $quick && ! $generic && fremont=(--fremont)
|
||||
VM_BOOTLOADER="$loader" "$here/vminstall.sh" --iso "$iso" "${fremont[@]}" < /dev/null || exit 1
|
||||
fi
|
||||
|
||||
step "start $loader as a $($generic && echo plain PC || echo Steam Machine)"
|
||||
start_vm; waitssh || exit 1
|
||||
gssh 'sudo cat /sys/class/dmi/id/product_name' 2>/dev/null
|
||||
|
||||
if $quick; then
|
||||
step "the installed system: Steamify's state, OS name, boot loader"; gscript steamify-state.sh | check
|
||||
step "Steamify's modules"; gscript modules.sh | check
|
||||
step "boot and its boot entry"; gscript boot-check.sh | check
|
||||
else
|
||||
step "Steamify's Steam Machine options"
|
||||
gssh 'sudo mkdir -p /var/cache/steamify-pkg /var/cache/steamify; sudo mountpoint -q /var/cache/steamify-pkg || sudo mount -t 9p -o trans=virtio,version=9p2000.L cache /var/cache/steamify-pkg && { sudo mkdir -p /var/cache/steamify-pkg/steamify; sudo mount --bind /var/cache/steamify-pkg/steamify /var/cache/steamify; }' 2>/dev/null
|
||||
gssh 'sudo mountpoint -q /mnt || sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt; cd /mnt && nohup bash steamify.sh --defaults --options gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff --boot gamescope > /tmp/steamify.log 2>&1 < /dev/null &'
|
||||
sleep 20
|
||||
# [s]: the pattern must not match this ssh command itself.
|
||||
n=0
|
||||
for _ in $(seq 90); do
|
||||
gssh 'pgrep -f "[s]teamify.sh --defaults" >/dev/null && echo R || echo D' 2>/dev/null | grep -q D && break
|
||||
(( n++ % 6 == 0 )) && echo " .. steamify.sh running: $(gssh 'tail -n 1 /tmp/steamify.log' 2>/dev/null | sed 's/\x1b\[[0-9;]*m//g' | cut -c1-110)"
|
||||
sleep 10
|
||||
done
|
||||
gssh 'sed "s/\x1b\[[0-9;]*m//g" /tmp/steamify.log | grep -E "^\[(WARN|ERROR)\]" | cut -c1-140'
|
||||
gssh 'tail -n 1 /tmp/steamify.log | sed "s/\x1b\[[0-9;]*m//g"' | grep -q '^\[OK\] Done' && echo "PASS Steamify finished" || { echo "FAIL Steamify did not finish"; }
|
||||
|
||||
step "Steamify's state, OS name, boot loader"; gscript steamify-state.sh | check
|
||||
step "Steamify's modules"; gscript modules.sh | check
|
||||
step "boot loader code (legacy kernel parameter removal)"; gscript legacy-param.sh | check
|
||||
step "kernel update"; gscript kernel-update.sh | check
|
||||
|
||||
step "reboot into the default kernel"
|
||||
if [[ "$loader" == limine ]]; then
|
||||
# remember_last_entry: yes boots whatever ran last, not default_entry: pin the default for this test, restore it at the end.
|
||||
cur="$(gssh "sudo sed -n 's/^default_entry: *//p' /boot/limine.conf")"
|
||||
rem="$(gssh "sudo sed -n 's/^remember_last_entry: *//p' /boot/limine.conf")"
|
||||
gssh "sudo sed -i -e 's/^default_entry: .*/default_entry: $cur/' -e 's/^remember_last_entry: .*/remember_last_entry: no/' /boot/limine.conf"
|
||||
fi
|
||||
reboot_vm && gscript boot-check.sh | check
|
||||
a="$(gssh uname -r)"
|
||||
|
||||
step "reboot into the other kernel"
|
||||
case "$loader" in
|
||||
systemd-boot) # the default entry is linux-cachyos.conf (vminstall-post.sh)
|
||||
gssh 'sudo bootctl set-oneshot linux-cachyos-lts.conf'; gssh 'sudo systemctl reboot' >/dev/null 2>&1; sleep 25; waitssh ;;
|
||||
grub) # GRUB_DEFAULT = the other kernel in the "Advanced options" submenu (0 = lts, 1 = linux-cachyos), no key presses
|
||||
sub=$([[ "$a" == *lts* ]] && echo 1 || echo 0)
|
||||
gg="$(gssh "sudo sed -n 's/^GRUB_DEFAULT=//p' /etc/default/grub")"
|
||||
gssh "sudo sed -i 's/^GRUB_DEFAULT=.*/GRUB_DEFAULT=\"1>$sub\"/' /etc/default/grub && sudo grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1"
|
||||
reboot_vm ;;
|
||||
limine) # the next entry in limine.conf; remember_last_entry would override default_entry
|
||||
gssh "sudo sed -i -e 's/^default_entry: .*/default_entry: $((cur + 1))/' /boot/limine.conf"
|
||||
reboot_vm ;;
|
||||
esac
|
||||
gscript boot-check.sh | check
|
||||
b="$(gssh uname -r)"
|
||||
[[ -n "$a" && -n "$b" && "$a" != "$b" ]] && echo "PASS booted both kernels ($a, $b)" || echo "FAIL both boots ran the same kernel ($a, $b)"
|
||||
[[ "$loader" == grub ]] && gssh "sudo sed -i 's/^GRUB_DEFAULT=.*/GRUB_DEFAULT=$gg/' /etc/default/grub && sudo grub-mkconfig -o /boot/grub/grub.cfg >/dev/null 2>&1"
|
||||
[[ "$loader" == limine ]] && gssh "sudo sed -i -e 's/^default_entry: .*/default_entry: $cur/' -e 's/^remember_last_entry: .*/remember_last_entry: ${rem:-yes}/' /boot/limine.conf"
|
||||
|
||||
fi
|
||||
|
||||
step "DONE"
|
||||
stop_vm
|
||||
} 2>&1 | tee "$log" | tee >(sed -u "s/^/[$loader] /" >> "$TEST_LOG") | sed -u 's/\x1b\[[0-9;]*m//g'
|
||||
rc=${PIPESTATUS[0]} # the test itself: an abort (exit inside the braces) must not pass as 0 failed checks
|
||||
fails="$(grep -c '^FAIL' "$log")"
|
||||
if [[ $rc -ne 0 && $fails -eq 0 ]]; then
|
||||
echo "FAIL $loader aborted (exit $rc): $(grep -v '^$' "$log" | tail -n 1)" | tee -a "$log"; fails=1
|
||||
fi
|
||||
echo; echo "== $loader$suffix: $(grep -c '^PASS' "$log") passed, $fails failed (log: $log)"
|
||||
grep '^FAIL' "$log"
|
||||
exit "$fails"
|
||||
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/bin/bash
|
||||
# Test HDMI-CEC in the guest against a fake TV: the kernel's vivid driver
|
||||
# gives a virtual HDMI input (/dev/cec0, plays the TV, driven with cec-ctl)
|
||||
# and output (/dev/cec1, the PC, where cecd runs). Needs the wizard's HDMI-CEC
|
||||
# item on (cecd, cec-ctl from v4l-utils); steamos-manager (Steam Machine
|
||||
# support) for the Steam settings tests. Prints PASS/FAIL/SKIP per check.
|
||||
#
|
||||
# scripts/vmcec.sh everything, including a real suspend/resume
|
||||
# (needs the QMP socket from run.sh to wake up)
|
||||
# scripts/vmcec.sh --no-sleep everything except suspending the VM
|
||||
# scripts/vmcec.sh --sleep-only only the real sleep/wake check (after a full run)
|
||||
#
|
||||
# Afterwards the normal cecd service runs again; vivid stays loaded.
|
||||
# Env: VM_USER (theupriser), VM_PORT (2222), VM_HOST (localhost).
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/common.sh"
|
||||
SLEEP_TEST=true SLEEP_ONLY=false
|
||||
[[ "${1:-}" == --no-sleep ]] && SLEEP_TEST=false
|
||||
[[ "${1:-}" == --sleep-only ]] && SLEEP_ONLY=true
|
||||
|
||||
# --- in the guest: helpers, setup and all checks except the real sleep ---
|
||||
$SLEEP_ONLY || vm_ssh "SLEEP_TEST=$SLEEP_TEST bash -s" << 'REMOTE'
|
||||
export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
||||
W=~/.cache/vmcec; mkdir -p "$W"
|
||||
pass=0 fail=0 skip=0
|
||||
PASS() { echo " PASS $*"; pass=$((pass + 1)); }
|
||||
FAIL() { echo " FAIL $*"; fail=$((fail + 1)); }
|
||||
SKIP() { echo " SKIP $*"; skip=$((skip + 1)); }
|
||||
check() { local d="$1"; shift; if "$@" >/dev/null 2>&1; then PASS "$d"; else FAIL "$d"; fi; }
|
||||
TV() { cec-ctl -d /dev/cec0 "$@"; }
|
||||
BUS=com.steampowered.CecDaemon1
|
||||
has_smgr() { steamosctl get-hdmi-cec-state >/dev/null 2>&1; }
|
||||
|
||||
# Key reader for the cecd input device: prints key names while it runs.
|
||||
cat > "$W/keys.py" << 'PY'
|
||||
import re, select, struct, sys, time
|
||||
names = {}
|
||||
for line in open('/usr/include/linux/input-event-codes.h'):
|
||||
m = re.match(r'#define\s+(KEY_\w+|BTN_\w+)\s+(0x[0-9a-fA-F]+|\d+)\b', line)
|
||||
if m: names.setdefault(int(m.group(2), 0), m.group(1))
|
||||
f = open(sys.argv[1], 'rb'); end = time.time() + float(sys.argv[2])
|
||||
while time.time() < end:
|
||||
if select.select([f], [], [], 0.1)[0]:
|
||||
_, _, t, c, v = struct.unpack('llHHi', f.read(24))
|
||||
if t == 1 and v == 1: print(names.get(c, c), flush=True)
|
||||
PY
|
||||
|
||||
# Monitor the TV side's CEC bus into $W/mon.txt (needs root).
|
||||
mon_start() { sudo pkill -f 'cec-ctl -d /dev/cec0 -M' 2>/dev/null; : > "$W/mon.txt"
|
||||
sudo sh -c "exec cec-ctl -d /dev/cec0 -M > $W/mon.txt 2>&1" & sleep 1; }
|
||||
mon_stop() { sleep 1; sudo pkill -f 'cec-ctl -d /dev/cec0 -M' 2>/dev/null; wait 2>/dev/null; }
|
||||
seen() { grep -q "Received from.*): $1" "$W/mon.txt"; }
|
||||
|
||||
echo "== Setup: fake TV (vivid)"
|
||||
lsmod | grep -q '^vivid' || sudo modprobe vivid n_devs=1 num_inputs=1 input_types=0x3 num_outputs=1 output_types=0x1
|
||||
sleep 2
|
||||
TV --tv --osd-name "Fake TV" >/dev/null 2>&1
|
||||
v4l2-ctl -d /dev/video0 --set-ctrl hdmi_000_0_is_connected_to=2
|
||||
# The cecd service (-e) takes every CEC device, the TV too: a temporary
|
||||
# drop-in limits it to the PC side, so steamos-manager still controls it.
|
||||
mkdir -p ~/.config/systemd/user/cecd.service.d
|
||||
printf '[Service]\nExecStart=\nExecStart=/usr/bin/cecd -d /dev/cec1\n' > ~/.config/systemd/user/cecd.service.d/vmcec.conf
|
||||
systemctl --user daemon-reload; systemctl --user restart cecd.service
|
||||
sleep 4
|
||||
systemctl --user is-active -q steamos-manager.service && { systemctl --user restart steamos-manager.service; sleep 3; }
|
||||
LA=$(cec-ctl -d /dev/cec1 | awk '/Logical Address /{print $4; exit}')
|
||||
PA=$(cec-ctl -d /dev/cec1 | awk '/Physical Address/{print $4; exit}')
|
||||
EV=$(grep -A4 'N: Name="cecd' /proc/bus/input/devices | grep -oE 'event[0-9]+' | head -1)
|
||||
echo " PC side: physical $PA, logical $LA, input /dev/input/${EV:-none}"
|
||||
|
||||
echo "== 1. Identity (TV asks the PC)"
|
||||
[[ "$PA" == 1.0.0.0 ]] && PASS "physical address 1.0.0.0" || FAIL "physical address is $PA"
|
||||
[[ "$LA" =~ ^(4|8|9|11)$ ]] && PASS "logical address $LA (playback device)" || FAIL "logical address is '$LA'"
|
||||
name=$(TV -t "$LA" --give-osd-name 2>&1 | awk -F': ' '/name:/{print $2; exit}')
|
||||
[[ "$name" == "Steam Machine" ]] && PASS "OSD name 'Steam Machine'" || FAIL "OSD name is '$name' (steamos-manager's config not applied?)"
|
||||
vendor=$(TV -t "$LA" --give-device-vendor-id 2>&1 | awk '/vendor-id:/{print $2; exit}')
|
||||
[[ "$vendor" == 0xe0319e* ]] && PASS "vendor ID Valve (0xe0319e)" || FAIL "vendor ID is '$vendor'"
|
||||
check "answers a poll" TV -t "$LA" --poll
|
||||
|
||||
echo "== 2. TV remote -> PC (key presses)"
|
||||
press() { TV -t "$LA" --user-control-pressed ui-cmd="$1" >/dev/null; sleep 0.15; TV -t "$LA" --user-control-released >/dev/null; sleep 0.25; }
|
||||
keys_for() { # keys_for <seconds> <ui-cmd>...: keys arriving while pressing
|
||||
EV=$(grep -A4 'N: Name="cecd' /proc/bus/input/devices | grep -oE 'event[0-9]+' | head -1)
|
||||
: > "$W/keys.txt"; local r=
|
||||
[[ -n "$EV" ]] && { sudo timeout "$1" python3 "$W/keys.py" "/dev/input/$EV" "$1" > "$W/keys.txt" & r=$!; }
|
||||
sleep 1; shift
|
||||
local k; for k in "$@"; do press "$k"; done; [[ -n "$r" ]] && wait "$r" 2>/dev/null; cat "$W/keys.txt"; }
|
||||
if [[ -z "$EV" ]]; then FAIL "no cecd input device"
|
||||
else
|
||||
for k in up down left right select exit back root-menu setup-menu contents-menu \
|
||||
play pause stop rewind fast-forward forward backward \
|
||||
volume-up volume-down mute number-entry-mode number-0-or-number-10 number-5 \
|
||||
channel-up channel-down display-information electronic-program-guide \
|
||||
f1-blue f2-red f3-green f4-yellow; do
|
||||
got=$(keys_for 2 "$k" | tr '\n' ' ')
|
||||
if [[ -n "$got" ]]; then PASS "$(printf '%-28s -> %s' "$k" "$got")"
|
||||
else SKIP "$(printf '%-28s -> nothing (not mapped by cecd)' "$k")"; fi
|
||||
done
|
||||
|
||||
# The TV's power button arrives as KEY_POWER, and Steam Machine support
|
||||
# makes the power button sleep: with sleep blocked, check the key only.
|
||||
sudo systemd-inhibit --what=sleep:handle-power-key --mode=block --who=vmcec --why=test sleep 6 & inh=$!; sleep 1
|
||||
got=$(keys_for 2 power-toggle-function | tr '\n' ' ')
|
||||
wait $inh 2>/dev/null
|
||||
[[ "$got" == *KEY_POWER* ]] && PASS "$(printf '%-28s -> %s' power-toggle-function "$got") (sleeps the PC)" ||
|
||||
FAIL "power-toggle-function -> '$got'"
|
||||
fi
|
||||
|
||||
echo "== 3. Steam settings (steamos-manager, over D-Bus like Steam)"
|
||||
# steamosctl's suspend-tv/suspend-device getters and setters don't work
|
||||
# (steamos-manager 26.4.1); Steam uses these D-Bus properties.
|
||||
SM=com.steampowered.SteamOSManager1; SMP=/com/steampowered/SteamOSManager1; CEC2=$SM.HdmiCec2
|
||||
prop() { busctl --user get-property $SM $SMP $CEC2 "$1" 2>/dev/null | awk '{print $2}'; }
|
||||
setp() { busctl --user set-property $SM $SMP $CEC2 "$1" b "$2" 2>/dev/null; sleep 2; }
|
||||
cfg() { grep -h "^$1 *=" ~/.config/cecd/config.d/*.toml 2>/dev/null | tail -1 | awk -F'= *' '{print $2}'; }
|
||||
if ! has_smgr; then SKIP "steamos-manager has no HDMI-CEC interface (no Steam Machine support?)"
|
||||
else
|
||||
echo " now: $(steamosctl get-hdmi-cec-state 2>&1); EnableControl=$(prop EnableControl) WakeTv=$(prop WakeTv) SuspendTv=$(prop SuspendTv) SuspendDevice=$(prop SuspendDevice) WakeDeviceSupported=$(prop WakeDeviceSupported)"
|
||||
setp EnableControl false
|
||||
got=$(keys_for 2 down | tr '\n' ' ')
|
||||
[[ -z "$got" && "$(cfg uinput)" == false ]] && PASS "remote control off: keys are ignored (uinput = false)" || FAIL "remote control off: keys '$got', uinput = $(cfg uinput)"
|
||||
setp EnableControl true
|
||||
got=$(keys_for 2 down | tr '\n' ' ')
|
||||
[[ -n "$got" ]] && PASS "remote control on again: $got" || FAIL "remote control on, but no keys"
|
||||
for pr in WakeTv:wake_tv SuspendTv:suspend_tv SuspendDevice:allow_standby; do
|
||||
for v in false true; do
|
||||
setp "${pr%%:*}" $v
|
||||
[[ "$(prop "${pr%%:*}")" == "$v" && "$(cfg "${pr#*:}")" == "$v" ]] &&
|
||||
PASS "${pr%%:*} $v (cecd config ${pr#*:} = $v)" || FAIL "${pr%%:*} $v: property $(prop "${pr%%:*}"), config ${pr#*:} = $(cfg "${pr#*:}")"
|
||||
done
|
||||
done
|
||||
[[ "$(prop WakeDeviceSupported)" == true ]] && PASS "waking the PC with the TV is supported" ||
|
||||
SKIP "waking the PC with the TV: not supported here (WakeDeviceSupported = false)"
|
||||
steamosctl set-hdmi-cec-state control-and-wake >/dev/null 2>&1; sleep 3
|
||||
fi
|
||||
systemctl --user is-active -q cecd.service && PASS "cecd still running" || FAIL "cecd stopped"
|
||||
|
||||
echo "== 4. PC -> TV"
|
||||
# A plain TV (no soundbar): volume goes to the TV itself (address 0).
|
||||
TV --tv --osd-name "Fake TV" >/dev/null 2>&1; sleep 2
|
||||
echo " fake TV: $(TV | awk '/Logical Address /{printf "%s%s ", $4, $5}')"
|
||||
DEV=$(busctl --user tree $BUS 2>/dev/null | grep -oE '/com/steampowered/CecDaemon1/Devices/Cec[0-9]+' | head -1)
|
||||
mon_start
|
||||
if has_smgr; then steamosctl hdmi-cec-make-active >/dev/null 2>&1; sleep 1; else busctl --user call $BUS "$DEV" $BUS.CecDevice1 SetActiveSource >/dev/null 2>&1; sleep 1; fi
|
||||
busctl --user call $BUS /com/steampowered/CecDaemon1/Daemon $BUS.Daemon1 Wake >/dev/null 2>&1; sleep 1
|
||||
mon_stop
|
||||
{ seen ACTIVE_SOURCE || seen IMAGE_VIEW_ON || seen TEXT_VIEW_ON; } && PASS "make active: TV switches to the PC ($(grep -oE '\): [A-Z_]+' "$W/mon.txt" | sort -u | tr -d '):' | tr '\n' ' '))" || FAIL "make active: TV got nothing"
|
||||
seen IMAGE_VIEW_ON || seen TEXT_VIEW_ON && PASS "wake: TV is turned on (IMAGE_VIEW_ON)" || FAIL "wake: no IMAGE_VIEW_ON"
|
||||
mon_start
|
||||
for m in VolumeUp VolumeDown Mute; do busctl --user call $BUS "$DEV" $BUS.CecDevice1 $m y 0 >/dev/null 2>&1; sleep 0.5; done
|
||||
echo " audio status: $(busctl --user --timeout=3 call $BUS "$DEV" $BUS.CecDevice1 GetAudioStatus y 0 2>&1)"; sleep 1
|
||||
mon_stop
|
||||
n=$(grep -cE 'USER_CONTROL_PRESSED' "$W/mon.txt"); ops=$(grep -A1 USER_CONTROL_PRESSED "$W/mon.txt" | grep -oiE 'volume-up|volume-down|mute' | sort -u | tr '\n' ' ')
|
||||
[[ $n -ge 3 ]] && PASS "volume up/down/mute sent to the TV ($ops)" || FAIL "volume: $n key presses reached the TV ($ops)"
|
||||
seen GIVE_AUDIO_STATUS && PASS "asks the TV's audio status" || FAIL "no GIVE_AUDIO_STATUS"
|
||||
mon_start
|
||||
busctl --user call $BUS /com/steampowered/CecDaemon1/Daemon $BUS.Daemon1 Standby b true >/dev/null 2>&1; sleep 1
|
||||
mon_stop
|
||||
seen STANDBY && PASS "standby: TV is turned off (STANDBY)" || FAIL "standby: no STANDBY"
|
||||
systemctl --user is-active -q cec-audio-control.socket && PASS "cec-audio-control socket listening" || FAIL "cec-audio-control socket not active"
|
||||
|
||||
echo "== 5. TV off -> PC to sleep (sleep blocked, so the VM stays up)"
|
||||
if ! has_smgr; then SKIP "needs steamos-manager"
|
||||
else
|
||||
TV --tv >/dev/null 2>&1; sleep 2
|
||||
for want in true false; do
|
||||
setp SuspendDevice $want; sleep 1
|
||||
since=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
sudo systemd-inhibit --what=sleep --mode=block --who=vmcec --why=test sleep 7 & inh=$!; sleep 1
|
||||
TV -t 15 --standby >/dev/null 2>&1; sleep 4
|
||||
wait $inh 2>/dev/null
|
||||
tried=$(journalctl --user -u cecd --since "$since" --no-pager | grep -c 'Failed to standby')
|
||||
if [[ $want == true ]]; then
|
||||
[[ $tried -gt 0 ]] && PASS "SuspendDevice on: TV standby makes the PC go to sleep (blocked here)" || FAIL "SuspendDevice on, but no sleep attempt"
|
||||
else
|
||||
[[ $tried -eq 0 ]] && PASS "SuspendDevice off: TV standby doesn't touch the PC" || FAIL "SuspendDevice off, but the PC tried to sleep"
|
||||
fi
|
||||
done
|
||||
setp SuspendDevice true
|
||||
fi
|
||||
|
||||
if ! $SLEEP_TEST; then rm -f ~/.config/systemd/user/cecd.service.d/vmcec.conf; systemctl --user daemon-reload; systemctl --user restart cecd.service; fi
|
||||
echo "== Result: $pass passed, $fail failed, $skip skipped (before the sleep test)"
|
||||
echo "$LA" > "$W/la"
|
||||
REMOTE
|
||||
|
||||
$SLEEP_TEST || { echo "(sleep test skipped)"; exit 0; }
|
||||
|
||||
# --- real suspend/resume: the TV should go off, and on again at wake ---
|
||||
# Through logind (systemctl suspend), like Steam and the power button: cecd
|
||||
# acts on logind's PrepareForSleep, which rtcwake skips. cec-follower plays a
|
||||
# TV that answers power status; QMP (vmwake.sh) wakes the VM.
|
||||
echo "== 6. Real sleep and wake (systemctl suspend, woken over QMP)"
|
||||
vm_ssh bash -s << 'REMOTE'
|
||||
export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
||||
W=~/.cache/vmcec
|
||||
# cecd on the PC side only, with debug logging (it logs what it does at sleep).
|
||||
mkdir -p ~/.config/systemd/user/cecd.service.d
|
||||
printf '[Service]\nExecStart=\nExecStart=/usr/bin/cecd -d /dev/cec1\nEnvironment=RUST_LOG=debug\n' > ~/.config/systemd/user/cecd.service.d/vmcec.conf
|
||||
systemctl --user daemon-reload; systemctl --user restart cecd.service; sleep 4
|
||||
SM=com.steampowered.SteamOSManager1; SMP=/com/steampowered/SteamOSManager1
|
||||
busctl --user set-property $SM $SMP $SM.HdmiCec2 SuspendTv b true 2>/dev/null
|
||||
busctl --user set-property $SM $SMP $SM.HdmiCec2 WakeTv b true 2>/dev/null
|
||||
sudo systemctl stop vmcec-tv vmcec-mon 2>/dev/null; sudo systemctl reset-failed vmcec-tv vmcec-mon vmcec-sleep 2>/dev/null
|
||||
cec-ctl -d /dev/cec0 --tv >/dev/null; sleep 1
|
||||
sudo systemd-run -q --unit=vmcec-tv sh -c 'exec cec-follower -d /dev/cec0 > /dev/null 2>&1'; sleep 2
|
||||
steamosctl hdmi-cec-make-active >/dev/null 2>&1; sleep 3
|
||||
sudo systemd-run -q --unit=vmcec-mon sh -c "exec cec-ctl -d /dev/cec0 -M > $W/sleep-mon.txt 2>&1"; sleep 1
|
||||
sudo systemd-run -q --unit=vmcec-sleep sh -c 'sleep 2; systemctl suspend'
|
||||
echo " suspending..."
|
||||
REMOTE
|
||||
sleep 15
|
||||
"$(dirname "$0")/vmwake.sh" || true
|
||||
until vm_ssh -o ConnectTimeout=4 true 2>/dev/null; do sleep 3; done
|
||||
sleep 8
|
||||
vm_ssh bash -s << 'REMOTE'
|
||||
export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
||||
W=~/.cache/vmcec
|
||||
sudo systemctl stop vmcec-mon vmcec-tv 2>/dev/null
|
||||
t=$(journalctl -b --no-pager -o short-iso | grep "PM: suspend entry" | tail -1 | awk '{print $1}')
|
||||
[[ -n "$t" ]] && echo " PASS the VM really slept and woke (suspend at $t)" || echo " FAIL no suspend in the kernel log"
|
||||
log=$(journalctl --user -u cecd --since "$(date -d "${t:-now} -10 sec" '+%F %T')" --no-pager)
|
||||
grep -q 'Putting TV in standby' <<< "$log" && echo " PASS cecd: 'Putting TV in standby'" || echo " FAIL cecd didn't act on going to sleep"
|
||||
grep -q 'Waking TV' <<< "$log" && echo " PASS cecd: 'Woke from standby. Waking TV.'" || echo " FAIL cecd didn't act on waking up"
|
||||
grep -q 'Received from.*: STANDBY' "$W/sleep-mon.txt" && echo " PASS going to sleep: TV turned off (STANDBY)" || echo " FAIL going to sleep: no STANDBY to the TV"
|
||||
grep -qE 'Received from.*: (IMAGE_VIEW_ON|TEXT_VIEW_ON)' "$W/sleep-mon.txt" && echo " PASS waking up: TV turned on (IMAGE_VIEW_ON)" || echo " FAIL waking up: no IMAGE_VIEW_ON"
|
||||
grep -oE '^(Received|Transmitted)[^:]*: [A-Z_]+' "$W/sleep-mon.txt" | sed 's/^/ /'
|
||||
# Back to the normal service.
|
||||
rm -f ~/.config/systemd/user/cecd.service.d/vmcec.conf; systemctl --user daemon-reload; systemctl --user restart cecd.service
|
||||
REMOTE
|
||||
@@ -0,0 +1,28 @@
|
||||
# vminstall-server.py <dir> <port>: serves <dir> to the installing VM (the
|
||||
# guest reaches the host as 10.0.2.2) and stores what it PUTs there (the
|
||||
# install log, the final status). Only on 127.0.0.1. Used by vminstall.sh.
|
||||
import http.server, os, sys
|
||||
|
||||
root, port = sys.argv[1], int(sys.argv[2])
|
||||
os.chdir(root)
|
||||
|
||||
|
||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
def do_PUT(self):
|
||||
name = os.path.basename(self.path)
|
||||
if not name:
|
||||
self.send_error(400)
|
||||
return
|
||||
data = self.rfile.read(int(self.headers.get("Content-Length", 0)))
|
||||
with open(name, "wb") as f:
|
||||
f.write(data)
|
||||
self.send_response(204)
|
||||
self.end_headers()
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
# requests.log: shows the live system reached the host, and when.
|
||||
with open("requests.log", "a") as f:
|
||||
f.write("%s %s\n" % (self.log_date_time_string(), fmt % args))
|
||||
|
||||
|
||||
http.server.ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
|
||||
Executable
+226
@@ -0,0 +1,226 @@
|
||||
#!/bin/bash
|
||||
# Install a fresh, ssh-ready test VM unattended: CachyOS (KDE Plasma,
|
||||
# plasma-login-manager, btrfs, Limine, linux-cachyos + -lts) from the ISO's
|
||||
# headless installer, then sshd with your key, passwordless sudo and the test
|
||||
# autologin into Plasma. Ends with the snapshots `clean` and `ssh-ready`.
|
||||
# scripts/vminstall.sh [--force] [--iso <file>] [run.sh flags...] e.g. --fremont
|
||||
# --force replace an existing disk.qcow2 in $VM_DIR (asks first)
|
||||
# --iso <file> the live ISO (default $VM_DIR/cachyos.iso, e.g. a
|
||||
# Steamify CachyOS build later; it must be archiso-based and
|
||||
# have cachyos-installer)
|
||||
# Env: VM_DIR (see common.sh), VM_USER (default: your host username; theupriser when that is root),
|
||||
# VM_PASSWORD (steamify), VM_SSH_KEY (private key to authorize; default
|
||||
# ~/.ssh/steamify-vm_ed25519, asked once when that doesn't exist), REPO (shared as 9p `repo`, default ../steamify-cachyos),
|
||||
# VM_INSTALL_MEM (RAM of the live installer VM only; default VM_MEM, else run.sh's 8G),
|
||||
# VM_CACHE (host dir for the packages, default ~/vms/pkg-cache; empty = none),
|
||||
# VM_STEAMIFY (a Steamify ISO also runs steamify-install: the Steamify page's ids,
|
||||
# empty = defaults, skip = plain CachyOS), VM_BOOTLOADER (limine; or systemd-boot, grub), VM_TIMEZONE (the host's), VM_HOST_IP (the host as the guest sees it,
|
||||
# 10.0.2.2 with QEMU's user networking).
|
||||
# The VM's user and key are recorded in $VM_DIR (vm-user, ssh-key) for the
|
||||
# other scripts. Never touches your existing SSH keys or ~/.ssh/known_hosts.
|
||||
set -euo pipefail
|
||||
[[ -n "${CI:-}" ]] && export VM_HEADLESS=1 # no display in CI (QEMU fails with "OpenGL is not supported by the display")
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo="$(cd "$here/.." && pwd)"
|
||||
[[ -n "${VM_USER:-}" ]] || VM_USER="$(id -un)"
|
||||
[[ "$VM_USER" != root ]] || VM_USER=theupriser # root can't be the guest user (WSL runs as root)
|
||||
export VM_USER
|
||||
. "$here/common.sh"
|
||||
# The pacman package cache, on the host and shared by every VM you install
|
||||
# (9p tag `cache`, see run.sh): a second install downloads nothing. VM_CACHE= (empty) turns it off.
|
||||
export VM_CACHE="${VM_CACHE-$HOME/vms/pkg-cache}"
|
||||
|
||||
force=false iso="" run_flags=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--force) force=true; shift ;;
|
||||
--iso) iso="${2:?--iso needs a file}"; shift 2 ;;
|
||||
*) run_flags+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
password="${VM_PASSWORD:-steamify}"
|
||||
timezone="${VM_TIMEZONE:-$(timedatectl show -p Timezone --value 2>/dev/null || cat /etc/timezone 2>/dev/null || echo UTC)}"
|
||||
hostname=steamify-vm
|
||||
REPO="${REPO:-$(dirname "$repo")/steamify-cachyos}"
|
||||
[[ -d "$REPO" ]] || { echo "REPO $REPO doesn't exist (the Steamify checkout shared into the VM); set REPO=." >&2; exit 1; }
|
||||
export REPO
|
||||
|
||||
mkdir -p "$VM_DIR"
|
||||
iso="${iso:-$VM_DIR/cachyos.iso}"
|
||||
[[ -f "$iso" ]] || { echo "No ISO at $iso: run get-iso.sh (in $VM_DIR) or pass --iso <file>." >&2; exit 1; }
|
||||
iso="$(cd "$(dirname "$iso")" && pwd)/$(basename "$iso")"
|
||||
pgrep -f "[h]ostfwd=tcp::$VM_PORT-" >/dev/null && { echo "A VM is running on port $VM_PORT; power it off first." >&2; exit 1; }
|
||||
if [[ -f "$VM_DIR/disk.qcow2" ]]; then
|
||||
[[ "$force" == true ]] || { echo "$VM_DIR already has a disk.qcow2; use --force to replace it, or another VM_DIR." >&2; exit 1; }
|
||||
read -r -p "Replace $VM_DIR/disk.qcow2 and all its snapshots? Type YES: " answer < /dev/tty
|
||||
[[ "$answer" == YES ]] || { echo "Nothing changed."; exit 1; }
|
||||
fi
|
||||
|
||||
# --- The SSH key to authorize: a new one just for the VM, or one of yours.
|
||||
# The VM's own key is made once and then always used: asked only when it
|
||||
# doesn't exist yet.
|
||||
dedicated="$HOME/.ssh/steamify-vm_ed25519"
|
||||
[[ -z "${VM_SSH_KEY:-}" && -f "$dedicated" ]] && VM_SSH_KEY="$dedicated"
|
||||
if [[ -z "${VM_SSH_KEY:-}" ]]; then
|
||||
mapfile -t pubs < <(ls "$HOME"/.ssh/*.pub 2>/dev/null | grep -vxF "$dedicated.pub")
|
||||
echo "Which SSH key should log in to the VM?"
|
||||
echo " 1) a new key just for the VM, used from now on: $dedicated (no passphrase)"
|
||||
for i in "${!pubs[@]}"; do echo " $((i + 2))) your key ${pubs[$i]%.pub}"; done
|
||||
read -r -p "Choice [1]: " choice < /dev/tty
|
||||
choice="${choice:-1}"
|
||||
if [[ "$choice" == 1 ]]; then
|
||||
VM_SSH_KEY="$dedicated"
|
||||
# Never overwrite a key: it doesn't exist here.
|
||||
ssh-keygen -q -t ed25519 -N "" -C "steamify-vm" -f "$dedicated"
|
||||
elif [[ "$choice" =~ ^[0-9]+$ && -n "${pubs[$((choice - 2))]:-}" ]]; then
|
||||
VM_SSH_KEY="${pubs[$((choice - 2))]%.pub}"
|
||||
else
|
||||
echo "Unknown choice: $choice" >&2; exit 1
|
||||
fi
|
||||
fi
|
||||
[[ -f "$VM_SSH_KEY.pub" ]] || { echo "No public key $VM_SSH_KEY.pub." >&2; exit 1; }
|
||||
pubkey="$(cat "$VM_SSH_KEY.pub")"
|
||||
|
||||
echo "VM: $VM_DIR user: $VM_USER password: $password hostname: $hostname timezone: $timezone"
|
||||
echo "ISO: $iso key: $VM_SSH_KEY"
|
||||
|
||||
# --- The ISO's kernel and initramfs, to boot it with our parameters.
|
||||
boot="$VM_DIR/iso-boot"
|
||||
mkdir -p "$boot"
|
||||
# One loop setup at a time: parallel installs (vmtest.sh --install) otherwise get "Device or resource busy".
|
||||
exec 9> /tmp/vminstall-iso.lock; flock 9
|
||||
if command -v bsdtar >/dev/null; then
|
||||
# No loop device, no udisks, no root: libarchive reads the ISO9660 image directly (a CI container has no
|
||||
# loop partition nodes, so the mounts below can't work there).
|
||||
mnt="$(mktemp -d)"
|
||||
cleanup_loop() { chmod -R u+rwX "$mnt" 2>/dev/null || true; rm -rf "$mnt" 2>/dev/null || true; } # the ISO's files come out read-only
|
||||
trap cleanup_loop EXIT
|
||||
bsdtar -xf "$iso" -C "$mnt" arch/boot/x86_64/vmlinuz-linux-cachyos arch/boot/x86_64/initramfs-linux-cachyos.img boot EFI 2>/dev/null || true
|
||||
[[ -f "$mnt/arch/boot/x86_64/vmlinuz-linux-cachyos" ]] || { echo "bsdtar found no kernel in the ISO." >&2; exit 1; }
|
||||
elif ! command -v udisksctl >/dev/null && [[ $EUID -eq 0 ]]; then
|
||||
# No udisks (the WSL box, as root): a plain loop device and mount.
|
||||
loop="$(losetup -f -r -P --show "$iso")"
|
||||
mnt="$(mktemp -d)"
|
||||
cleanup_loop() { umount "$mnt" 2>/dev/null || true; rmdir "$mnt" 2>/dev/null || true; losetup -d "$loop" 2>/dev/null || true; }
|
||||
trap cleanup_loop EXIT
|
||||
mount -r "${loop}p1" "$mnt" 2>/dev/null || mnt=""
|
||||
else
|
||||
loop="$(udisksctl loop-setup --no-user-interaction -r -f "$iso" | grep -o '/dev/loop[0-9]*')"
|
||||
cleanup_loop() { udisksctl unmount --no-user-interaction -b "${loop}p1" >/dev/null 2>&1 || true; udisksctl loop-delete --no-user-interaction -b "$loop" >/dev/null 2>&1 || true; }
|
||||
trap cleanup_loop EXIT
|
||||
mnt=""
|
||||
for _ in $(seq 10); do
|
||||
mnt="$(findmnt -nro TARGET "${loop}p1" 2>/dev/null || true)"
|
||||
[[ -n "$mnt" ]] && break
|
||||
udisksctl mount --no-user-interaction -b "${loop}p1" >/dev/null 2>&1 || true; sleep 1
|
||||
done
|
||||
fi
|
||||
[[ -n "$mnt" ]] || { echo "Couldn't mount the ISO." >&2; exit 1; }
|
||||
rm -f "$boot"/* # copied read-only from the ISO
|
||||
install -m 644 "$mnt/arch/boot/x86_64/vmlinuz-linux-cachyos" "$mnt/arch/boot/x86_64/initramfs-linux-cachyos.img" "$boot/"
|
||||
search="$(grep -rhoE 'archisosearchuuid=[^ ]+' "$mnt/boot" "$mnt/EFI" 2>/dev/null | head -1)"
|
||||
[[ -n "$search" ]] || { echo "No archisosearchuuid in the ISO's boot menu." >&2; exit 1; }
|
||||
cleanup_loop; trap - EXIT
|
||||
flock -u 9; exec 9>&-
|
||||
|
||||
# --- What the live system fetches from the host.
|
||||
www="$VM_DIR/install-www"
|
||||
rm -rf "$www"; mkdir -p "$www"
|
||||
# QEMU's user networking (-nic user in run.sh) always shows the host to the
|
||||
# guest at 10.0.2.2, whatever network the host is on; only a bridged/tap
|
||||
# setup would need another address.
|
||||
host_ip="${VM_HOST_IP:-10.0.2.2}"
|
||||
port="$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1])')"
|
||||
sed -e "s/@PORT@/$port/" -e "s/@HOST_IP@/$host_ip/" "$repo/share/vminstall-live.sh" > "$www/vminstall-live.sh"
|
||||
cp "$repo/share/vminstall-post.sh" "$www/"
|
||||
{
|
||||
printf 'VM_USER=%q\nVM_HOSTNAME=%q\nVM_PUBKEY=%q\nDEVICE=/dev/vda\nVM_STEAMIFY=%q\n' "$VM_USER" "$hostname" "$pubkey" "${VM_STEAMIFY:-}"
|
||||
} > "$www/vminstall.env"
|
||||
python3 - "$www/settings.json" "$VM_USER" "$password" "$timezone" "$hostname" "${VM_BOOTLOADER:-limine}" << 'PY'
|
||||
import json, sys
|
||||
out, user, pw, tz, host, bootloader = sys.argv[1:]
|
||||
json.dump({
|
||||
"install_type": "simple", "headless_mode": True,
|
||||
"device": "/dev/vda", "fs_name": "btrfs", "subvolumes": "default",
|
||||
"partitions": [
|
||||
{"name": "/dev/vda1", "mountpoint": "/boot", "size": "2G", "fs_name": "vfat", "type": "boot"},
|
||||
{"name": "/dev/vda2", "mountpoint": "/", "size": "100%", "type": "root"},
|
||||
],
|
||||
"hostname": host, "locale": "en_US.UTF-8", "xkbmap": "us", "timezone": tz,
|
||||
"user_name": user, "user_pass": pw, "user_shell": "/bin/fish", "root_pass": pw,
|
||||
"kernel": "linux-cachyos linux-cachyos-lts", "desktop": "kde", "bootloader": bootloader,
|
||||
}, open(out, "w"), indent=2)
|
||||
PY
|
||||
python3 "$here/vminstall-server.py" "$www" "$port" &
|
||||
server=$!
|
||||
trap 'kill $server 2>/dev/null || true' EXIT
|
||||
|
||||
# --- Install.
|
||||
cd "$VM_DIR"
|
||||
cp "$repo/run.sh" run.sh # always this repo's: the install needs its kernel/serial options
|
||||
[[ -e share ]] || ln -s "$repo/share" share
|
||||
rm -f disk.qcow2 vars.fd vars.clean.fd vars.ssh-ready.fd known_hosts
|
||||
qemu-img create -q -f qcow2 disk.qcow2 60G
|
||||
echo "$VM_USER" > vm-user
|
||||
echo "$REPO" > repo-path
|
||||
echo "$VM_SSH_KEY" > ssh-key
|
||||
# systemd.run= only generates kernel-command-line.service; systemd.wants=
|
||||
# starts it next to the normal boot (desktop, network). It fetches and runs
|
||||
# the live script. (Root logs in on the serial console without a password:
|
||||
# VM_SERIAL.sock, for debugging.)
|
||||
append="$search archisobasedir=arch cow_spacesize=10G module_blacklist=pcspkr console=ttyS0,115200 console=tty0"
|
||||
append+=" systemd.run=\"/usr/bin/bash -c 'curl -fsS --retry 150 --retry-all-errors --retry-delay 2 http://$host_ip:$port/vminstall-live.sh | bash'\""
|
||||
append+=" systemd.run_success_action=none systemd.run_failure_action=none systemd.wants=kernel-command-line.service"
|
||||
echo "Installing: watch the VM's window, or follow the console with tail -f $VM_DIR/serial.log (install log: $www/install.log)..."
|
||||
VM_MEM="${VM_INSTALL_MEM:-${VM_MEM:-}}" VM_SERIAL="$VM_DIR/serial.log" VM_ISO="$iso" VM_KERNEL="$boot/vmlinuz-linux-cachyos" VM_INITRD="$boot/initramfs-linux-cachyos.img" VM_APPEND="$append" \
|
||||
./run.sh install "${run_flags[@]}" > vm-install.log 2>&1 &
|
||||
qemu=$!
|
||||
# 90 minutes; a failure reported by the live system ends it at once (it stays up after one)
|
||||
waited=0
|
||||
for _ in $(seq 1080); do
|
||||
kill -0 $qemu 2>/dev/null || break
|
||||
# A sign of life every minute (CI shows nothing else for ~10 minutes): where the live installer is.
|
||||
(( waited++ % 12 == 0 )) && echo " .. installing, $((waited * 5 / 60)) min: $(tail -n 1 "$www/install.log" 2>/dev/null | cut -c1-110)"
|
||||
[[ -s "$www/status" && "$(cat "$www/status")" != ok ]] && { sleep 5; kill $qemu 2>/dev/null; wait $qemu 2>/dev/null; break; }
|
||||
sleep 5
|
||||
done
|
||||
kill -0 $qemu 2>/dev/null && { echo "The install didn't finish in 90 minutes; see $www/install.log." >&2; kill $qemu; exit 1; }
|
||||
status="$(cat "$www/status" 2>/dev/null || echo "no status (the live system never reported back)")"
|
||||
cp "$www/install.log" install.log 2>/dev/null || true
|
||||
if [[ "$status" != ok ]]; then
|
||||
echo "Install failed: $status. Log: $VM_DIR/install.log" >&2
|
||||
# In CI only the job log survives: show why.
|
||||
for f in vm-install.log serial.log install.log; do [[ -s "$f" ]] && { echo "--- $f (last 40 lines)" >&2; tail -n 40 "$f" | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' >&2; }; done
|
||||
exit 1
|
||||
fi
|
||||
qemu-img snapshot -c clean disk.qcow2 && cp vars.fd vars.clean.fd
|
||||
echo "Installed; snapshot 'clean' taken."
|
||||
|
||||
# --- First boot: check it's ssh-ready, then snapshot.
|
||||
nohup ./run.sh "${run_flags[@]}" > vm.log 2>&1 &
|
||||
vm=$!
|
||||
for _ in $(seq 100); do vm_ssh -o ConnectTimeout=3 true 2>/dev/null && break; sleep 3; done
|
||||
vm_ssh true || { echo "No SSH after the first boot; see $VM_DIR/vm.log." >&2; exit 1; }
|
||||
# The poweroff at the end may drop the connection (ssh exits 255): judge by the ssh-ready line instead.
|
||||
out="$(vm_ssh bash -s 2>&1 << 'REMOTE'
|
||||
set -e
|
||||
if [[ -f /etc/sddm.conf.d/10-gamescope-autologin.conf ]]; then
|
||||
# Steamify's gaming mode (gamescope, no Plasma, and it doesn't render in the VM): the login manager has to be up.
|
||||
for _ in $(seq 60); do systemctl is-active --quiet display-manager && break; sleep 3; done
|
||||
systemctl is-active --quiet display-manager || { echo "The login manager didn't start." >&2; exit 1; }
|
||||
else
|
||||
for _ in $(seq 60); do pgrep -u "$USER" -x plasmashell >/dev/null && break; sleep 3; done
|
||||
pgrep -u "$USER" -x plasmashell >/dev/null || { echo "Plasma didn't start." >&2; exit 1; }
|
||||
fi
|
||||
sudo -n true
|
||||
echo "ssh-ready: $(id -un)@$(hostname), $(uname -r), $(localectl status | sed -n 's/.*LANG=//p'), $(timedatectl show -p Timezone --value)"
|
||||
sudo systemctl poweroff
|
||||
REMOTE
|
||||
)" || true
|
||||
printf '%s\n' "$out" | grep -v '^Connection to .* closed'
|
||||
grep -q '^ssh-ready: ' <<< "$out" || { echo "The first boot check failed; see above and $VM_DIR/vm.log." >&2; kill $vm 2>/dev/null; exit 1; }
|
||||
while kill -0 $vm 2>/dev/null; do sleep 2; done # this VM only: others may run in parallel
|
||||
qemu-img snapshot -c ssh-ready disk.qcow2 && cp vars.fd vars.ssh-ready.fd
|
||||
rm -rf "$www"
|
||||
echo "Done: snapshots 'clean' and 'ssh-ready' in $VM_DIR."
|
||||
echo "Log in by hand as $VM_USER / $password (root: $password)."
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
# Simulate the Steam Machine ISO's installer step in the test VM: a new user
|
||||
# who never logged in (no session bus, user systemd or plasmashell) gets
|
||||
# `steamify.sh --defaults` from /mnt, visibly in a Konsole on the VM's
|
||||
# desktop. Prints the per-component result lines.
|
||||
# scripts/vminstallsim.sh [--fresh] [--defaults options...]
|
||||
# --fresh: delete and recreate the user first; the rest goes to
|
||||
# --defaults (e.g. --options gaming,theme --boot desktop)
|
||||
# Env: VM_USER / VM_PORT / VM_HOST, SIM_USER (isotest), GUEST_UID (1000).
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/common.sh"
|
||||
fresh=""; [[ "${1:-}" == --fresh ]] && { fresh=1; shift; }
|
||||
{ printf 'u=%q fresh=%q uid=%q opts=%q\n' "${SIM_USER:-isotest}" "$fresh" "${GUEST_UID:-1000}" "$*"; cat << 'REMOTE'
|
||||
mountpoint -q /mnt || sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
[ -n "$fresh" ] && sudo userdel -r "$u" 2>/dev/null
|
||||
cat > /tmp/install-sim.sh << SIM
|
||||
#!/bin/bash
|
||||
id $u >/dev/null 2>&1 || { sudo useradd -m -G wheel -s /bin/bash $u; echo "$u:$u" | sudo chpasswd; }
|
||||
echo "$u ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/10-steamify-install-sim >/dev/null
|
||||
sudo -u $u env -i HOME=/home/$u USER=$u LOGNAME=$u PATH=/usr/local/bin:/usr/bin TERM=xterm LANG=C.UTF-8 \
|
||||
bash -c 'cd ~ && /mnt/steamify.sh --defaults $opts < /dev/null' 2>&1 | tee /tmp/install-sim.log
|
||||
echo "exit: \${PIPESTATUS[0]}" | tee -a /tmp/install-sim.log
|
||||
sudo rm -f /etc/sudoers.d/10-steamify-install-sim
|
||||
touch /tmp/install-sim.done
|
||||
SIM
|
||||
chmod +x /tmp/install-sim.sh; rm -f /tmp/install-sim.done
|
||||
XDG_RUNTIME_DIR=/run/user/$uid DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus \
|
||||
systemd-run --user -q konsole --hold -e /tmp/install-sim.sh
|
||||
while [ ! -f /tmp/install-sim.done ]; do sleep 3; done
|
||||
sed 's/\x1b\[[0-9;]*m//g' /tmp/install-sim.log | grep -E '^Turning|^\[(WARN|ERROR|OK)\]|^exit'
|
||||
REMOTE
|
||||
} | vm_ssh bash -s 2>&1
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
# Boot a live ISO (the Steam Machine ISO) in its own VM, the normal way, to go
|
||||
# through Calamares by hand or driven with scripts/qmpkey.py / qmptype.py,
|
||||
# with screenshots of every page (scripts/qmpshot.py; no virgl, so QMP
|
||||
# screendump works). Unlike vminstall.sh nothing is automated: the wizard,
|
||||
# its Steamify pages and the installer's Steamify step run as on real
|
||||
# hardware. Starts in the background; the VM's window is where you watch.
|
||||
# scripts/vmisoboot.sh [--fresh] [--iso <file>] [run.sh flags...] e.g. --fremont
|
||||
# --fresh a new, empty disk (else the existing one: e.g. boot the
|
||||
# installed system with `./run.sh` in $VM_DIR afterwards)
|
||||
# --iso <file> default: the newest out/desktop/*.iso of the host's
|
||||
# steamify-cachyos-live-iso checkout
|
||||
# Env: ISO_VM_DIR (~/vms/iso-vm), ISO_VM_PORT (2223: runs next to the test
|
||||
# VM on 2222).
|
||||
set -euo pipefail
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo="$(cd "$here/.." && pwd)"
|
||||
dir="${ISO_VM_DIR:-$HOME/vms/iso-vm}"
|
||||
port="${ISO_VM_PORT:-2223}"
|
||||
fresh=false iso="" flags=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--fresh) fresh=true; shift ;;
|
||||
--iso) iso="${2:?--iso needs a file}"; shift 2 ;;
|
||||
*) flags+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
if [[ -z "$iso" ]]; then
|
||||
iso="$(ls -t "$(dirname "$repo")"/steamify-cachyos-live-iso/out/desktop/*.iso 2>/dev/null | head -1)"
|
||||
[[ -n "$iso" ]] || { echo "No ISO in steamify-cachyos-live-iso/out/desktop: build one (vmisobuild.sh) or pass --iso." >&2; exit 1; }
|
||||
fi
|
||||
iso="$(cd "$(dirname "$iso")" && pwd)/$(basename "$iso")"
|
||||
mkdir -p "$dir"
|
||||
cd "$dir"
|
||||
[[ -S qmp.sock ]] && python3 -c "import socket,sys; s=socket.socket(socket.AF_UNIX); s.connect(sys.argv[1])" qmp.sock 2>/dev/null &&
|
||||
{ echo "The ISO VM in $dir is running already." >&2; exit 1; }
|
||||
cp "$repo/run.sh" run.sh
|
||||
[[ -e share ]] || ln -s "$repo/share" share
|
||||
if [[ "$fresh" == true ]]; then rm -f disk.qcow2 vars.fd; fi
|
||||
REPO="${REPO:-$(dirname "$repo")/steamify-cachyos}" VM_NOGL=1 VM_PORT="$port" VM_ISO="$iso" \
|
||||
nohup ./run.sh install "${flags[@]}" > vm.log 2>&1 &
|
||||
echo "ISO VM: $iso in $dir (SSH port $port once installed)."
|
||||
echo "Screenshot: python3 $here/qmpshot.py $dir/qmp.sock shot.png"
|
||||
echo "Keys: python3 $here/qmpkey.py $dir/qmp.sock down ret / qmptype.py $dir/qmp.sock \"text\""
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
# Build the Steam Machine ISO (steamify-cachyos-live-iso) in the test VM
|
||||
# (directly, no container: build-live-modules.sh and build-calamares-modules.sh with sudo, buildiso.sh as
|
||||
# the user, it sudos itself),
|
||||
# visibly (a Konsole on the VM's desktop follows the log), with the package
|
||||
# downloads cached on the host, and copy the ISO back.
|
||||
# scripts/vmisobuild.sh [--steamify <guest path>]
|
||||
# --steamify a Steamify checkout in the guest (e.g. /mnt, the mounted
|
||||
# REPO) instead of the newest release
|
||||
# The VM must run with its cache shared (one per VM, next to its disk), and
|
||||
# gets all host cores but 2 for the build: start it with
|
||||
# VM_CACHE=$VM_DIR/iso-cache VM_CPUS=$(( $(nproc) * 3 / 4 )) scripts/vmreset.sh --fremont
|
||||
# Env: VM_DIR (see common.sh), ISO_BRANCH (master), ISO_OUT (the host's
|
||||
# steamify-cachyos-live-iso/out/desktop).
|
||||
set -euo pipefail
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$here/common.sh"
|
||||
steamify=""
|
||||
[[ "${1:-}" == --steamify ]] && steamify="${2:?--steamify needs a guest path}"
|
||||
branch="${ISO_BRANCH:-master}"
|
||||
# Where the ISO lands: out/desktop of the host's live-ISO checkout (next to
|
||||
# this repo), where a local build would put it.
|
||||
out="${ISO_OUT:-$(dirname "$(cd "$here/.." && pwd)")/steamify-cachyos-live-iso/out/desktop}"
|
||||
mkdir -p "$out"
|
||||
|
||||
{ printf 'branch=%q steamify=%q\n' "$branch" "$steamify"; cat << 'REMOTE'
|
||||
set -e
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u) DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus
|
||||
# The package cache on the host (9p tag `cache`), kept across snapshot resets,
|
||||
# is the VM's pacman cache: the build tools and mkarchiso (which installs the
|
||||
# ISO's packages with the build system's cache) both use it. No container:
|
||||
# the VM is CachyOS already, and a snapshot reset removes the build tools.
|
||||
sudo mkdir -p /var/cache/steamify-iso
|
||||
mountpoint -q /var/cache/steamify-iso || sudo mount -t 9p -o trans=virtio,version=9p2000.L cache /var/cache/steamify-iso ||
|
||||
{ echo "No 9p share 'cache': start the VM with VM_CACHE=<host dir> (run.sh)." >&2; exit 1; }
|
||||
sudo mkdir -p /var/cache/steamify-iso/pkg
|
||||
mountpoint -q /var/cache/pacman/pkg || sudo mount --bind /var/cache/steamify-iso/pkg /var/cache/pacman/pkg
|
||||
[ -z "$steamify" ] || mountpoint -q /mnt || sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
# archlinux.cachyos.org serves packages without their .sig (404), and pacman
|
||||
# doesn't try the next mirror for the signature of a cached package: the
|
||||
# build fails. mkarchiso uses this mirrorlist, so skip it here.
|
||||
sudo sed -i '/archlinux\.cachyos\.org/s/^Server/#Server/' /etc/pacman.d/mirrorlist
|
||||
sudo pacman -Sy --needed --noconfirm archiso mkinitcpio-archiso git squashfs-tools grub >/dev/null
|
||||
iso=~/projects/steamify-cachyos-live-iso
|
||||
if [ -d "$iso/.git" ]; then git -C "$iso" fetch -q && git -C "$iso" checkout -q "$branch" && git -C "$iso" reset -q --hard "origin/$branch"
|
||||
else mkdir -p ~/projects && git clone -q -b "$branch" https://github.com/theupriser/steamify-cachyos-live-iso "$iso"; fi
|
||||
cd "$iso" && echo "ISO repo: $(git log --oneline -1)"
|
||||
./steamify-prepare.sh $steamify | tail -1
|
||||
sudo rm -rf build out
|
||||
cat > ~/projects/iso-build.sh << S
|
||||
#!/bin/bash
|
||||
cd $iso
|
||||
sudo ./build-live-modules.sh && sudo ./build-calamares-modules.sh && { ./buildiso.sh -p desktop -w || ./buildiso.sh -p desktop -c -w; }
|
||||
echo "== BUILD EXIT \$?"
|
||||
ls -la out/desktop/*.iso 2>/dev/null
|
||||
S
|
||||
chmod +x ~/projects/iso-build.sh; : > ~/projects/iso-build.log
|
||||
systemd-run --user --collect -q -u "isobuild-$(date +%s)" bash -c "~/projects/iso-build.sh > ~/projects/iso-build.log 2>&1"
|
||||
systemd-run --user -q --setenv=XDG_CURRENT_DESKTOP=KDE konsole --separate --hold -e bash -c "tail -n 50 -F ~/projects/iso-build.log"
|
||||
REMOTE
|
||||
} | vm_ssh bash -s
|
||||
echo "Building in the VM (its Konsole shows the log)..."
|
||||
until vm_ssh 'grep -q "^== BUILD EXIT" ~/projects/iso-build.log'; do sleep 30; done
|
||||
iso="$(vm_ssh "bash -c 'ls -t ~/projects/steamify-cachyos-live-iso/out/desktop/*.iso 2>/dev/null | head -1'")"
|
||||
[[ -n "$iso" ]] || { echo "No ISO was built; see ~/projects/iso-build.log in the VM." >&2; exit 1; }
|
||||
vm_scp "$VM_USER@$VM_HOST:$iso" "$out/"
|
||||
echo "ISO: $out/$(basename "$iso") (cache: $VM_DIR/iso-cache)"
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# Progress of test jobs, one line per change (for a monitor; see the progress-report skill).
|
||||
# scripts/vmprogress.sh [--once] [job...] jobs: limine systemd-boot grub limine-generic systemd-boot-generic grub-generic cli menu hw installer toggles nvidia (default all)
|
||||
# Per job (only a new step, a finished job or a FAIL counts as a change): state (install NN% <stage> / <test step> / done / -), checks passed/expected, failed.
|
||||
# Line: "HH:MM total P/E F failed ;; job | state | P/E | F failed ;; ...". Prints nothing while nothing
|
||||
# changes (checked every 30 s), "ALLDONE" and exits once every started job has its summary line.
|
||||
# Expected counts: the last complete run of each (AGENTS.md "State"); update them when checks are added.
|
||||
set -uo pipefail
|
||||
declare -A expect=([limine]=43 [systemd-boot]=42 [grub]=42 [cli]=65 [menu]=37 [hw]=78 [installer]=4 [toggles]=42 [nvidia]=43 [limine-generic]=25 [systemd-boot-generic]=25 [grub-generic]=25)
|
||||
once=false; jobs=()
|
||||
for a in "$@"; do [[ "$a" == --once ]] && once=true || jobs+=("$a"); done
|
||||
[[ ${#jobs[@]} -gt 0 ]] || jobs=(limine systemd-boot grub limine-generic systemd-boot-generic grub-generic cli menu hw installer toggles nvidia)
|
||||
vms="$HOME/vms"
|
||||
|
||||
status() { # status <job>: "state|pass|fail|finished(0/1)|started(0/1)"
|
||||
local j="$1" log dir
|
||||
case "$j" in limine|systemd-boot|grub|limine-generic|systemd-boot-generic|grub-generic) log="$vms/bl-$j.test.log" dir="$vms/bl-$j" ;; *) log="$vms/run-$j.log" dir="$vms/run-$j" ;; esac
|
||||
[[ -f "$log" ]] || { echo "-|0|0|0|0"; return; }
|
||||
local p f st run
|
||||
# The logs are appended to: only the latest run counts (from its "===== vm..." header on).
|
||||
run="$(tac "$log" | sed '/^===== vm/q' | tac)"
|
||||
p=$(grep -ac '^PASS' <<< "$run"); f=$(grep -ac '^FAIL' <<< "$run")
|
||||
if grep -aq "^== $j: [0-9]* passed" <<< "$run"; then echo "done|$p|$f|1|1"; return; fi
|
||||
st=$(grep -aE '^##### ' <<< "$run" | tail -1 | sed 's/^##### [0-9:]* //' | cut -c1-40)
|
||||
if [[ "$st" == install* && -f "$dir/install-www/install.log" ]]; then
|
||||
st="install $(grep -aoE '^\[ *[0-9.]+%\] [^.(]*' "$dir/install-www/install.log" | tail -1 | sed 's/^\[ *\([0-9]*\)[0-9.]*%\] /\1% /; s/ *$//')"
|
||||
# The live installer's own failure (vminstall-live.sh: "== failed: ..."), or the host side giving up.
|
||||
local why; why=$(grep -ahE '^== failed: ' "$dir/install-www/install.log" "$log" 2>/dev/null | tail -1 | cut -c12-70)
|
||||
[[ -n "$why" ]] && { st="INSTALL FAILED: $why"; f=$((f + 1)); }
|
||||
fi
|
||||
echo "${st:-starting}|$p|$f|0|1"
|
||||
}
|
||||
|
||||
prev=""
|
||||
while true; do
|
||||
line="" tp=0 te=0 tf=0 running=0
|
||||
for j in "${jobs[@]}"; do
|
||||
IFS='|' read -r st p f fin started <<< "$(status "$j")"
|
||||
[[ "$started" == 1 && "$fin" == 0 ]] && running=$((running + 1))
|
||||
tp=$((tp + p)) tf=$((tf + f)) te=$((te + ${expect[$j]:-0}))
|
||||
line+=" ;; $j | $st | $p/${expect[$j]:-?} | $f failed"
|
||||
done
|
||||
cur="total $tp/$te $tf failed$line"
|
||||
# A change is a new stage/step, a job finishing or a FAIL: not a new install percentage or PASS count.
|
||||
key="$(sed -E 's/install [0-9]+% /install /g; s/\| [0-9]+\/[0-9?]+ \|/|/g; s/^total [0-9]+\/[0-9]+ //' <<< "$cur")"
|
||||
[[ "$key" != "$prev" ]] && { echo "$(date +%H:%M) $cur"; prev="$key"; }
|
||||
$once && exit 0
|
||||
[[ $running -eq 0 ]] && { echo ALLDONE; exit 0; }
|
||||
sleep 30
|
||||
done
|
||||
+7
-5
@@ -9,10 +9,7 @@
|
||||
# VM_LOG (QEMU output, default $VM_DIR/vm.log).
|
||||
set -euo pipefail
|
||||
. "$(dirname "$0")/common.sh"
|
||||
repo="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
if [[ -z "${VM_DIR:-}" ]]; then
|
||||
if [[ -f "$repo/disk.qcow2" ]]; then VM_DIR="$repo"; else VM_DIR="$HOME/vms/cachyos-test"; fi
|
||||
fi
|
||||
repo_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SNAPSHOT="${SNAPSHOT:-ssh-ready}"
|
||||
VM_LOG="${VM_LOG:-$VM_DIR/vm.log}"
|
||||
|
||||
@@ -25,6 +22,8 @@ fi
|
||||
|
||||
echo "Restoring snapshot '$SNAPSHOT' in $VM_DIR..."
|
||||
cd "$VM_DIR"
|
||||
# Always this repo's run.sh: a copy in $VM_DIR falls behind (VM_CACHE, ...).
|
||||
[[ "$repo_dir/run.sh" -ef run.sh ]] || cp "$repo_dir/run.sh" run.sh
|
||||
qemu-img snapshot -a "$SNAPSHOT" disk.qcow2
|
||||
cp "vars.$SNAPSHOT.fd" vars.fd
|
||||
|
||||
@@ -37,7 +36,10 @@ vm_ssh bash -s << 'REMOTE'
|
||||
sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
sudo mkdir -p /etc/plasmalogin.conf.d
|
||||
printf '[Autologin]\nUser=%s\nSession=plasma.desktop\n' "$USER" | sudo tee /etc/plasmalogin.conf.d/00-test-autologin.conf >/dev/null
|
||||
sudo systemctl restart plasmalogin
|
||||
# Snapshots with the autologin file log in at boot; restarting plasmalogin
|
||||
# then fails (HELPER_TTY_ERROR on tty1) and leaves the greeter.
|
||||
for _ in $(seq 10); do pgrep -u "$USER" -x plasmashell >/dev/null && break; sleep 3; done
|
||||
pgrep -u "$USER" -x plasmashell >/dev/null || { sudo systemctl reset-failed plasmalogin; sudo systemctl restart plasmalogin; }
|
||||
for _ in $(seq 40); do pgrep -u "$USER" -x plasmashell >/dev/null && break; sleep 3; done
|
||||
sleep 10
|
||||
# Windows the snapshot opens at login would cover the screenshots.
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ export XDG_RUNTIME_DIR=/run/user/$uid DBUS_SESSION_BUS_ADDRESS=unix:path=/run/us
|
||||
rm -f /tmp/vmshot.png
|
||||
if [ -n "$clean" ]; then
|
||||
# Steam as a whole: closing only its web helper leaves a black window.
|
||||
# It comes back at the next login (steam-desktop-autostart).
|
||||
# It comes back at the next login (Steam autostart entry).
|
||||
steam -shutdown >/dev/null 2>&1 & sleep 5; pkill -u "$USER" -x steam; pkill -u "$USER" -x steamwebhelper
|
||||
pkill -u "$USER" cachyos-hello; pkill -u "$USER" -x konsole
|
||||
sleep 2
|
||||
@@ -25,5 +25,5 @@ sleep 2
|
||||
systemd-run --user --wait -q spectacle -b -n -f -o /tmp/vmshot.png
|
||||
REMOTE
|
||||
} | vm_ssh bash -s
|
||||
scp -q -P "$VM_PORT" "$VM_USER@$VM_HOST:/tmp/vmshot.png" "$out"
|
||||
vm_scp "$VM_USER@$VM_HOST:/tmp/vmshot.png" "$out"
|
||||
echo "$out"
|
||||
+3
-3
@@ -8,8 +8,8 @@ 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) dark-lnf=$(kreadconfig6 --file kdeglobals --group KDE --key DefaultDarkLookAndFeel) scheme=$(kreadconfig6 --file kdeglobals --group General --key ColorScheme) cursor=$(kreadconfig6 --file kcminputrc --group Mouse --key cursorTheme) vapor-pkg=$(pacman -Q cachyos-vapor 2>/dev/null || echo none) layout-backup=$(yn ~/.local/state/cachyos-gamescope-boot/theme-layout)"
|
||||
echo "steam-autostart=$(grep -qs "^Exec=.*-silent" ~/.config/autostart/steam.desktop && echo silent || ([ -f ~/.config/autostart/steam.desktop ] && echo plain || echo none)) glyphs=$(yn ~/.config/environment.d/99-gamescope-steam-glyphs.conf)"
|
||||
echo "lnf=$(kreadconfig6 --file kdeglobals --group KDE --key LookAndFeelPackage) dark-lnf=$(kreadconfig6 --file kdeglobals --group KDE --key DefaultDarkLookAndFeel) scheme=$(kreadconfig6 --file kdeglobals --group General --key ColorScheme) cursor=$(kreadconfig6 --file kcminputrc --group Mouse --key cursorTheme) vapor-pkg=$(pacman -Q cachyos-vapor 2>/dev/null || echo none) layout-backup=$(yn ~/.local/state/steamify/theme-layout)"
|
||||
echo "wallpaper: $(grep -h '^Image=' ~/.config/plasma-org.kde.plasma.desktop-appletsrc | sort -u | tr '\n' ' ')"
|
||||
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' ' ')"
|
||||
@@ -18,7 +18,7 @@ echo "leds-pkg=$(pacman -Q leds-valve-dkms-git 2>/dev/null || echo none) steamos
|
||||
echo "dkms: $(dkms status leds-valve-dkms 2>/dev/null | cut -d, -f2- | sed 's/, x86_64//' | tr '\n' ';') kernels: $(ls /usr/lib/modules | tr '\n' ' ')"
|
||||
echo "dkms-override=$(yn /etc/dkms/leds-valve-dkms.conf) headers-check=$(systemctl is-enabled ensure-kernel-headers.service 2>/dev/null || echo none)"
|
||||
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
|
||||
j=~/.local/state/steamify
|
||||
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
|
||||
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# One suite of the automated test (TESTPLAN.md rows that need no person) on the plain CachyOS VM
|
||||
# (Plasma, no Steamify: ~/vms/steamify-vm, from vminstall.sh --iso <CachyOS ISO> with VM_STEAMIFY=skip).
|
||||
# scripts/vmsuite.sh <suite> [--window] suites: the folders in share/vmtest/ (cli, ...)
|
||||
# scripts/vmsuite.sh --prepare only make the base image (below)
|
||||
# Every block share/vmtest/<suite>/NN-name.sh starts from a fresh copy of the VM's `ssh-ready` state,
|
||||
# runs in the guest as the VM user (repo on /mnt; NN-name.host.sh blocks run on the host instead: reboots,
|
||||
# host scripts), and prints PASS/FAIL/SKIP lines; the first comment lines say which TESTPLAN rows it
|
||||
# covers; `# env: VAR=value` there is set for the VM start (e.g. BIOS_VERSION=F7F0107).
|
||||
# Fast and parallel: the `ssh-ready` snapshot is converted once into $SRC/base-ssh-ready.qcow2; each suite
|
||||
# has its own directory ~/vms/run-<suite> and ssh port, where every block gets a new qcow2 overlay of the
|
||||
# base (instant, no snapshot restore), and the host's package cache (VM_CACHE, ~/vms/pkg-cache) is bound
|
||||
# over pacman's cache, so packages are downloaded once for all. Log: $VM_DIR.log. Exit status = FAILs.
|
||||
# Env: ONLY=<block name prefix> runs just that block; SRC (default ~/vms/steamify-vm); REPO (default ../steamify-cachyos).
|
||||
set -uo pipefail
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo="$(cd "$here/.." && pwd)"
|
||||
src="${SRC:-$HOME/vms/steamify-vm}"
|
||||
base="$src/base-ssh-ready.qcow2"
|
||||
|
||||
prepare_base() { # once, under a lock (parallel suites): the ssh-ready snapshot as a stand-alone image
|
||||
( flock 9
|
||||
if [[ ! -f "$base" || "$src/vars.ssh-ready.fd" -nt "$base" ]]; then
|
||||
echo "Making $base from the ssh-ready snapshot (once, a few minutes)..." >&2
|
||||
qemu-img convert -f qcow2 -O qcow2 -l snapshot.name=ssh-ready "$src/disk.qcow2" "$base.tmp" && mv "$base.tmp" "$base"
|
||||
fi ) 9>"$src/.base.lock"
|
||||
[[ -f "$base" ]]
|
||||
}
|
||||
if [[ "${1:-}" == --prepare ]]; then prepare_base; exit $?; fi
|
||||
|
||||
suite="${1:?usage: $0 <suite> [--window] | --prepare}"
|
||||
[[ "${2:-}" == --window ]] || export VM_HEADLESS=1
|
||||
dir="$repo/share/vmtest/$suite"
|
||||
[[ -d "$dir" ]] || { echo "No suite $suite (folders in share/vmtest/)" >&2; exit 2; }
|
||||
case "$suite" in cli) port=2301 ;; menu) port=2302 ;; hw) port=2303 ;; installer) port=2304 ;; toggles) port=2305 ;; nvidia) port=2306 ;; *) port=2390 ;; esac
|
||||
export VM_PORT="${VM_PORT:-$port}"
|
||||
export VM_DIR="$HOME/vms/run-$suite"
|
||||
export REPO="${REPO:-$repo/../steamify-cachyos}"
|
||||
export VM_CACHE="${VM_CACHE-$HOME/vms/pkg-cache}"
|
||||
export VM_MEM="${VM_MEM:-4G}" # three VMs at once next to a desktop: 8G each pushed the host into swap
|
||||
prepare_base || { echo "FAIL no base image" >&2; exit 1; }
|
||||
mkdir -p "$VM_DIR"
|
||||
for f in vm-user ssh-key; do cp "$src/$f" "$VM_DIR/$f"; done
|
||||
echo "$REPO" > "$VM_DIR/repo-path"; [[ -e "$VM_DIR/share" ]] || ln -s "$repo/share" "$VM_DIR/share"
|
||||
. "$here/common.sh"
|
||||
log="$VM_DIR.log"
|
||||
view_start # one shared Konsole (common.sh), headless on a desktop
|
||||
|
||||
vm_running() { pgrep -f "[h]ostfwd=tcp::$VM_PORT-" >/dev/null; }
|
||||
vm_stop() {
|
||||
vm_running || return 0
|
||||
vm_ssh -o ConnectTimeout=4 'sudo systemctl poweroff' >/dev/null 2>&1
|
||||
local _; for _ in $(seq 20); do vm_running || return 0; sleep 1; done
|
||||
pkill -f "[h]ostfwd=tcp::$VM_PORT-"; sleep 2
|
||||
}
|
||||
# A fresh VM for a block: new overlay of the base, the snapshot's UEFI vars, Plasma up, repo mounted, package cache bound.
|
||||
vm_fresh() { # vm_fresh [VAR=value...]: extra environment for the VM start (BIOS_VERSION=...)
|
||||
vm_stop
|
||||
cp "$repo/run.sh" "$VM_DIR/run.sh"; cp "$src/vars.ssh-ready.fd" "$VM_DIR/vars.fd"
|
||||
rm -f "$VM_DIR/disk.qcow2" "$VM_DIR/known_hosts"
|
||||
qemu-img create -q -f qcow2 -b "$base" -F qcow2 "$VM_DIR/disk.qcow2" || return 1
|
||||
(cd "$VM_DIR" && env "$@" nohup ./run.sh --fremont > vm.log 2>&1 &)
|
||||
local _; for _ in $(seq 100); do vm_ssh -o ConnectTimeout=3 true 2>/dev/null && break; sleep 3; done
|
||||
vm_ssh -o LogLevel=ERROR bash -s <<'REMOTE'
|
||||
sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
sudo mkdir -p /etc/plasmalogin.conf.d
|
||||
printf '[Autologin]\nUser=%s\nSession=plasma.desktop\n' "$USER" | sudo tee /etc/plasmalogin.conf.d/00-test-autologin.conf >/dev/null
|
||||
for _ in $(seq 10); do pgrep -u "$USER" -x plasmashell >/dev/null && break; sleep 3; done
|
||||
pgrep -u "$USER" -x plasmashell >/dev/null || { sudo systemctl reset-failed plasmalogin; sudo systemctl restart plasmalogin; }
|
||||
for _ in $(seq 40); do pgrep -u "$USER" -x plasmashell >/dev/null && break; sleep 3; done
|
||||
pkill -u "$USER" systemsettings; pkill -u "$USER" cachyos-hello
|
||||
for f in /etc/pacman.d/*mirrorlist*; do sudo sed -i '/krfoss/s/^Server/#Server/' "$f"; done
|
||||
# the host's package cache over pacman's
|
||||
sudo mkdir -p /var/cache/steamify-pkg
|
||||
if sudo mount -t 9p -o trans=virtio,version=9p2000.L cache /var/cache/steamify-pkg 2>/dev/null; then
|
||||
sudo mkdir -p /var/cache/steamify-pkg/pkg; sudo mount --bind /var/cache/steamify-pkg/pkg /var/cache/pacman/pkg
|
||||
# Steamify's own downloads (the CEC driver: GitHub rate-limits it) once for every VM
|
||||
sudo mkdir -p /var/cache/steamify-pkg/steamify /var/cache/steamify; sudo mount --bind /var/cache/steamify-pkg/steamify /var/cache/steamify
|
||||
fi
|
||||
pgrep -u "$USER" -x plasmashell >/dev/null && echo "Logged in to Plasma." || { echo "Plasma did not start." >&2; exit 1; }
|
||||
REMOTE
|
||||
}
|
||||
|
||||
{
|
||||
printf '\n===== vmsuite.sh %s (%s) =====\n' "$suite" "$(date +%T)"
|
||||
for block in "$dir"/*.sh; do
|
||||
[[ -z "${ONLY:-}" || "$(basename "$block")" == "$ONLY"* ]] || continue # ONLY=50: just that block
|
||||
printf '\n##### %s %s\n' "$(date +%T)" "$(basename "$block" .sh)"
|
||||
vm_fresh $(sed -n 's/^# env: //p' "$block" | head -n 1) > /tmp/vmsuite-$suite-reset.out 2>&1 \
|
||||
|| { echo "FAIL the VM did not come up: $(tail -n 1 /tmp/vmsuite-$suite-reset.out)"; continue; }
|
||||
if [[ "$block" == *.host.sh ]]; then # runs on the host (reboots, host scripts); prelude-host.sh has the helpers
|
||||
( . "$repo/share/vmtest/prelude-host.sh"; . "$block" ) 2>&1 | sed -u 's/\x1b\[[0-9;]*m//g'
|
||||
continue
|
||||
fi
|
||||
cat "$repo/share/vmtest/prelude.sh" "$block" | vm_ssh -o ConnectTimeout=6 -o LogLevel=ERROR bash -s 2>&1 | sed -u 's/\x1b\[[0-9;]*m//g'
|
||||
done
|
||||
vm_stop
|
||||
} 2>&1 | tee "$log" | sed -u "s/^/[$suite] /" >> "$TEST_LOG"
|
||||
rc=${PIPESTATUS[0]} # an abort inside the braces must not pass as 0 failed checks
|
||||
fails="$(grep -c '^FAIL' "$log")"
|
||||
if [[ $rc -ne 0 && $fails -eq 0 ]]; then
|
||||
echo "FAIL $suite aborted (exit $rc): $(grep -v '^$' "$log" | tail -n 1)" | tee -a "$log"; fails=1
|
||||
fi
|
||||
echo "== $suite: $(grep -c '^PASS' "$log") passed, $fails failed, $(grep -c '^SKIP' "$log") skipped (log: $log)" | tee -a "$log"
|
||||
grep '^FAIL' "$log"
|
||||
exit "$fails"
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Run the whole automated test on another machine (the PC with the VMs) from this one (e.g. a laptop):
|
||||
# update the repo there, start scripts/vmtest.sh in a detached `screen`, wait for the summary, print it.
|
||||
# scripts/vmtest-remote.sh <ssh-host> [--no-wait] [vmtest.sh args...] e.g. scripts/vmtest-remote.sh pc cli hw
|
||||
# The branch you are on must be pushed (the remote pulls it). The remote needs this repo, ../steamify-cachyos,
|
||||
# ../steamify-cachyos-live-iso (for the loader list and the ISO), qemu + OVMF + screen, KVM, and the VMs in
|
||||
# ~/vms (see the vm-install skill). Env: REMOTE_REPO (default ~/projects/steamify/steamify-cachyos-dev).
|
||||
set -uo pipefail
|
||||
host="${1:?usage: $0 <ssh-host> [--no-wait] [vmtest.sh args...]}"; shift
|
||||
wait=true; args=()
|
||||
for a in "$@"; do [[ "$a" == --no-wait ]] && wait=false || args+=("$a"); done
|
||||
repo="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
branch="$(git -C "$repo" branch --show-current)"
|
||||
rr="${REMOTE_REPO:-~/projects/steamify/steamify-cachyos-dev}"
|
||||
git -C "$repo" ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1 || { echo "Push $branch first: the remote pulls it." >&2; exit 1; }
|
||||
[[ -z "$(git -C "$repo" status --porcelain)" ]] || echo "Note: uncommitted changes here are NOT tested; the remote runs what is pushed." >&2
|
||||
ssh "$host" "rm -f ~/vms/last-test.txt; cd $rr && git fetch -q origin && git checkout -q $branch && git pull -q --ff-only && scripts/vmtest.sh --screen ${args[*]:-}" || exit 1
|
||||
$wait || { echo "Started on $host; the summary lands in ~/vms/last-test.txt there (screen -r vmtest to watch)."; exit 0; }
|
||||
until ssh "$host" 'grep -q "^== total" ~/vms/last-test.txt 2>/dev/null'; do sleep 30; done
|
||||
ssh "$host" 'cat ~/vms/last-test.txt'
|
||||
ssh "$host" 'test "$(grep -c "^FAIL" ~/vms/last-test.txt)" -eq 0'
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# The whole automated test, one command: the boot loader test for every loader the Steamify ISO
|
||||
# advertises, plus every suite in share/vmtest/ (TESTPLAN.md rows that need no person).
|
||||
# scripts/vmtest.sh [--install] [--window] [--screen] [boot | <loader>... | <suite>...]
|
||||
# no argument everything: boot (all advertised loaders) and all suites
|
||||
# boot the boot loader test (limine, systemd-boot, grub), or name loaders
|
||||
# generic the plain-PC install check for every loader (vmbootloadertest.sh --quick --generic: a PC that is not a
|
||||
# Steam Machine must get no Steam Machine items and no DKMS modules); part of the default run
|
||||
# <suite> cli, menu, hw, installer, toggles, nvidia (folders in share/vmtest/)
|
||||
# --install install the boot loader VMs first from the newest Steamify ISO (asks before replacing a disk)
|
||||
# --window show the VMs' windows (default: headless; one shared Konsole follows ~/vms/test.log)
|
||||
# MAX_PARALLEL=3 (env) VMs at once (5 worked on a 9800X3D / 64 GB); the slowest suites start first
|
||||
# --screen run in a detached `screen` session named vmtest: `screen -r vmtest` to watch, Ctrl-a d to leave
|
||||
# The suites run on the plain CachyOS VM ~/vms/steamify-vm (Plasma, from vminstall.sh --iso <CachyOS ISO>
|
||||
# with VM_STEAMIFY=skip); the boot loader test on ~/vms/bl-<loader> (Steamify ISO).
|
||||
# Output: one line per loader/suite and the failed checks; the same in ~/vms/last-test.txt. Exit status = failures.
|
||||
# Rows that need a person (screenshots, the real Steam Machine) are listed at the end, never silently dropped.
|
||||
set -uo pipefail
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo="$(cd "$here/.." && pwd)"
|
||||
iso_repo="$repo/../steamify-cachyos-live-iso"
|
||||
args=("$@")
|
||||
install=""; window=""; screen=false; loaders=(); suites=(); want_boot=false; want_generic=false
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--install) install=--install ;;
|
||||
--window) window=--window ;;
|
||||
--screen) screen=true ;;
|
||||
boot) want_boot=true ;;
|
||||
generic) want_generic=true ;;
|
||||
limine|systemd-boot|grub) loaders+=("$a"); want_boot=true ;;
|
||||
*) [[ -d "$repo/share/vmtest/$a" ]] && suites+=("$a") || { echo "Unknown argument: $a" >&2; exit 2; } ;;
|
||||
esac
|
||||
done
|
||||
if $screen; then
|
||||
command -v screen >/dev/null || { echo "screen is not installed" >&2; exit 2; }
|
||||
screen -ls | grep -q '\.vmtest\s' && { echo "A vmtest screen session is already running: screen -r vmtest" >&2; exit 1; }
|
||||
rest=(); for a in "${args[@]}"; do [[ "$a" == --screen ]] || rest+=("$a"); done
|
||||
screen -dmS vmtest bash -c "$(printf '%q ' "$0" "${rest[@]}"); echo; echo 'Done (summary: ~/vms/last-test.txt). Press Enter to close.'; read -r _"
|
||||
echo "Running in the screen session vmtest: screen -r vmtest (Ctrl-a d to leave it running); the summary lands in ~/vms/last-test.txt"
|
||||
exit 0
|
||||
fi
|
||||
if [[ ${#loaders[@]} -eq 0 && ${#suites[@]} -eq 0 ]] && ! $want_boot && ! $want_generic; then
|
||||
want_boot=true; want_generic=true
|
||||
for d in cli menu hw installer toggles nvidia; do [[ -d "$repo/share/vmtest/$d" ]] && suites+=("$d"); done
|
||||
fi
|
||||
# What the ISO advertises: the boot loaders calamares-online.sh keeps.
|
||||
advertised="$(sed -n 's/.*"bootloader:\([^"]*\)".*/\1/p' "$iso_repo/archiso/airootfs/usr/local/bin/calamares-online.sh" 2>/dev/null | head -n 1)"
|
||||
[[ -n "$advertised" ]] || { echo "Can't read the advertised boot loaders from $iso_repo (calamares-online.sh)" >&2; exit 2; }
|
||||
$want_boot && [[ ${#loaders[@]} -eq 0 ]] && read -r -a loaders <<< "$advertised"
|
||||
gloaders=() # the plain-PC install check: the same loaders as the boot loader test, or all advertised ones
|
||||
if $want_generic; then if [[ ${#loaders[@]} -gt 0 ]]; then gloaders=("${loaders[@]}"); else read -r -a gloaders <<< "$advertised"; fi; fi
|
||||
tested="limine systemd-boot grub" # the loaders vmbootloadertest.sh has a way to boot the other kernel for
|
||||
summary="$HOME/vms/last-test.txt"
|
||||
maxp="${MAX_PARALLEL:-3}" # VMs at once (4 GB RAM each in the suites, see vmsuite.sh); 5 ran fine on a 9800X3D with 64 GB
|
||||
# Jobs, the slowest first so the last ones are short: suites, then the boot loaders.
|
||||
jobs_list=()
|
||||
for s in toggles hw cli menu installer; do [[ " ${suites[*]} " == *" $s "* ]] && jobs_list+=("suite:$s"); done
|
||||
for s in "${suites[@]}"; do [[ " toggles hw cli menu installer " == *" $s "* ]] || jobs_list+=("suite:$s"); done
|
||||
for l in "${loaders[@]}"; do jobs_list+=("loader:$l"); done
|
||||
for l in "${gloaders[@]}"; do jobs_list+=("generic:$l"); done
|
||||
jobtag() { [[ "${1%%:*}" == generic ]] && echo "${1#*:}-generic" || echo "${1#*:}"; } # files and names of a job
|
||||
run_job() { # run_job <kind:name>: output in /tmp/vmtest-<tag>.out, exit status in /tmp/vmtest-<tag>.rc
|
||||
local kind="${1%%:*}" name="${1#*:}" tag; tag="$(jobtag "$1")"
|
||||
if [[ "$kind" == loader ]]; then bash "$here/vmbootloadertest.sh" "$name" $install $window > "/tmp/vmtest-$tag.out" 2>&1
|
||||
elif [[ "$kind" == generic ]]; then bash "$here/vmbootloadertest.sh" "$name" $install --quick --generic $window > "/tmp/vmtest-$tag.out" 2>&1
|
||||
else bash "$here/vmsuite.sh" "$name" $window > "/tmp/vmtest-$tag.out" 2>&1; fi
|
||||
echo $? > "/tmp/vmtest-$tag.rc"
|
||||
}
|
||||
{
|
||||
echo "vmtest.sh $(date '+%F %T') branch $(git -C "$repo" branch --show-current) $(git -C "$repo" rev-parse --short HEAD)"
|
||||
total=0
|
||||
for l in $advertised; do
|
||||
[[ " $tested " == *" $l "* ]] || { echo "FAIL the ISO advertises $l, but there is no test for it (scripts/vmbootloadertest.sh)"; total=$((total + 1)); }
|
||||
done
|
||||
[[ ${#suites[@]} -gt 0 ]] && { bash "$here/vmsuite.sh" --prepare || { echo "FAIL no base image for the suites"; total=$((total + 1)); }; }
|
||||
rm -f /tmp/vmtest-*.rc
|
||||
for job in "${jobs_list[@]}"; do
|
||||
run_job "$job" &
|
||||
while [[ $(jobs -rp | wc -l) -ge $maxp ]]; do wait -n; done
|
||||
done
|
||||
wait
|
||||
for job in "${jobs_list[@]}"; do
|
||||
tag="$(jobtag "$job")"
|
||||
sed -n '/^== /,$p' "/tmp/vmtest-$tag.out"
|
||||
total=$((total + $(cat "/tmp/vmtest-$tag.rc" 2>/dev/null || echo 1)))
|
||||
done
|
||||
echo
|
||||
echo "SKIP (needs a person, TESTPLAN.md): U1-U9 the app's screens, R2.2 the first desktop login, H-Real the real Steam Machine"
|
||||
echo "== total: $total failed check(s); boot loaders: ${loaders[*]:-none}; plain PC: ${gloaders[*]:-none}; suites: ${suites[*]:-none}"
|
||||
} 2>&1 | tee "$summary"
|
||||
exit "$(grep -c '^FAIL' "$summary")"
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# vmview.py <VM dir or qmp socket> [seconds]: a window that shows a headless VM's screen, refreshed
|
||||
# every 2 seconds (QMP screendump; read-only, no input). For watching an automated run without
|
||||
# giving the VM a window. Needs python-pillow and tk; on WSL set the WSLg env (wsl-build-host skill).
|
||||
import io, json, os, socket, sys, tempfile, tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
target = sys.argv[1]
|
||||
sock_path = os.path.join(target, "qmp.sock") if os.path.isdir(target) else target
|
||||
every = int(float(sys.argv[2]) * 1000) if len(sys.argv) > 2 else 2000
|
||||
|
||||
def shot():
|
||||
s = socket.socket(socket.AF_UNIX); s.settimeout(5); s.connect(sock_path); f = s.makefile()
|
||||
f.readline(); s.sendall(b'{"execute":"qmp_capabilities"}'); f.readline()
|
||||
out = os.path.join(tempfile.gettempdir(), f"vmview-{os.getpid()}.ppm")
|
||||
s.sendall(json.dumps({"execute": "screendump", "arguments": {"filename": out}}).encode())
|
||||
reply = json.loads(f.readline()); s.close()
|
||||
if "error" in reply: raise RuntimeError(reply["error"].get("desc"))
|
||||
with open(out, "rb") as fh: img = Image.open(io.BytesIO(fh.read()))
|
||||
os.unlink(out); return img
|
||||
|
||||
root = tk.Tk(); root.title(f"vmview: {target}")
|
||||
label = tk.Label(root, bg="black", fg="white"); label.pack(fill="both", expand=True)
|
||||
def tick():
|
||||
try:
|
||||
img = shot(); w = min(img.width, 1280); img = img.resize((w, img.height * w // img.width))
|
||||
label.photo = ImageTk.PhotoImage(img); label.config(image=label.photo, text="")
|
||||
except Exception as e:
|
||||
label.config(image="", text=f"no screen: {e}", width=60, height=10)
|
||||
root.after(every, tick)
|
||||
tick(); root.mainloop()
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# Wake the VM from sleep (suspend to RAM), e.g. after the TV's power button
|
||||
# in the HDMI-CEC test. Uses the QMP socket run.sh opens in the VM directory.
|
||||
# Env: VM_DIR (the repo if it holds disk.qcow2, else ~/vms/cachyos-test).
|
||||
set -euo pipefail
|
||||
here="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
VM_DIR="${VM_DIR:-$([[ -f "$here/disk.qcow2" ]] && echo "$here" || echo "$HOME/vms/cachyos-test")}"
|
||||
sock="$VM_DIR/qmp.sock"
|
||||
[[ -S "$sock" ]] || { echo "No QMP socket at $sock (VM not running, or started before run.sh had -qmp)." >&2; exit 1; }
|
||||
printf '%s\n' '{"execute":"qmp_capabilities"}' '{"execute":"system_wakeup"}' |
|
||||
socat -t 2 - UNIX-CONNECT:"$sock" 2>/dev/null | grep -c '"return"' >/dev/null && echo "Wake-up sent."
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# Guest side: after a reboot, the running kernel has Steamify's modules.
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }
|
||||
k=$(uname -r)
|
||||
echo "kernel: $k"; echo "cmdline: $(cut -c1-140 /proc/cmdline)"
|
||||
if [[ "${HARDWARE:-fremont}" == generic ]]; then
|
||||
lsmod | grep -qE "^(steamify_fremont_poweroff|leds_valve)" && fail "Steam Machine modules loaded on a plain PC" || pass "no Steam Machine modules loaded (not a Steam Machine)"
|
||||
else
|
||||
compgen -G "/usr/lib/modules/$k/updates/dkms/steamify-fremont-poweroff.ko*" >/dev/null && pass "power-off fix built for the running kernel" || fail "no power-off fix for $k"
|
||||
# Loaded (a real Steam Machine), or tried at boot and refused with "No such device" (the VM has no AMD GPIO
|
||||
# controller, so the module returns -ENODEV silently). The refusal is in the journal; it only sometimes
|
||||
# reaches dmesg, which is what this used to grep.
|
||||
if lsmod | grep -q '^steamify_fremont_poweroff'; then pass "power-off fix loaded at boot"
|
||||
elif sudo journalctl -b -q --no-pager -u systemd-modules-load | grep -q "steamify_fremont_poweroff.*No such device"; then
|
||||
pass "power-off fix tried at boot (No such device: no AMD GPIO controller in the VM)"
|
||||
else fail "power-off fix not loaded, and not tried at boot"; fi
|
||||
lsmod | grep -q '^leds_valve' && pass "leds_valve loaded" || fail "leds_valve not loaded"
|
||||
fi
|
||||
[[ -z "$(systemctl --failed --no-legend)" ]] && pass "no failed units" || { fail "failed units:"; systemctl --failed --no-legend; }
|
||||
# B1: booted through the loader's own EFI boot entry, not the firmware's automatic disk entry (the
|
||||
# fallback path \EFI\BOOT\BOOTX64.EFI) and not an entry without a partition (HD(0,GPT,0000...)).
|
||||
cur=$(sudo efibootmgr | sed -n 's/^BootCurrent: //p')
|
||||
entry=$(sudo efibootmgr -v | grep -a "^Boot$cur")
|
||||
if [[ -n "$cur" && "$entry" != *auto_created_boot_option* && "$entry" =~ HD\(([0-9]+),GPT,([0-9a-fA-F-]+) \
|
||||
&& "${BASH_REMATCH[1]}" != 0 && "${BASH_REMATCH[2]}" != 00000000-0000-* ]]; then
|
||||
pass "booted through its own EFI entry: $(cut -f1 <<< "$entry" | cut -c1-60)"
|
||||
else
|
||||
fail "not booted through its own EFI entry (BootCurrent $cur: $(cut -c1-120 <<< "$entry"))"
|
||||
fi
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Guest side: reinstall both kernels + headers (what an update does: the
|
||||
# DKMS hook rebuilds the modules, mkinitcpio and the boot loader's hook run).
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }
|
||||
start=$(date +%s)
|
||||
sudo pacman -S --noconfirm linux-cachyos linux-cachyos-lts linux-cachyos-headers linux-cachyos-lts-headers > /tmp/kernel-update.log 2>&1
|
||||
rc=$?
|
||||
sed 's/\x1b\[[0-9;]*m//g' /tmp/kernel-update.log | grep -E "^==> dkms|Building image|Generating grub|sdboot|limine|error" | cut -c1-140
|
||||
[[ $rc -eq 0 ]] && pass "pacman kernel reinstall exit 0" || fail "pacman exit $rc"
|
||||
for k in $(ls /usr/lib/modules); do
|
||||
for m in cros-ec-cec leds-valve steamify-fremont-poweroff; do
|
||||
compgen -G "/usr/lib/modules/$k/updates/dkms/$m.ko*" >/dev/null || fail "$m not rebuilt for $k"
|
||||
done
|
||||
done
|
||||
[[ $(dkms status | grep -c ': installed') -eq $((3 * $(ls /usr/lib/modules | wc -w))) ]] && pass "DKMS rebuilt 3 modules for each kernel" || fail "DKMS status after the update"
|
||||
# The images: /boot/initramfs-*.img (GRUB, systemd-boot); Limine keeps them per kernel
|
||||
# under /boot/<machine-id>/<kernel>/initramfs (limine-mkinitcpio) and rewrites limine.conf.
|
||||
if [[ "$LOADER" == limine ]]; then
|
||||
# limine-entry-tool copies an image only when its content changed (same version = untouched):
|
||||
# the hook ran when both images were built and limine.conf was regenerated.
|
||||
n=$(sed 's/\x1b\[[0-9;]*m//g' /tmp/kernel-update.log | grep -c 'Creating zstd-compressed initcpio image')
|
||||
[[ $n -ge 2 ]] && pass "Limine's hook built the initramfs for both kernels" || fail "Limine's hook built $n initramfs image(s)"
|
||||
[[ $(sudo stat -c %Y /boot/limine.conf) -ge $start ]] && pass "limine.conf regenerated" || fail "limine.conf not regenerated"
|
||||
else
|
||||
for img in /boot/initramfs-linux-cachyos.img /boot/initramfs-linux-cachyos-lts.img; do
|
||||
[[ $(sudo stat -c %Y "$img") -ge $start ]] && pass "$(basename "$img") rebuilt" || fail "$(basename "$img") not rebuilt"
|
||||
done
|
||||
fi
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Guest side: Steamify's boot-loader-specific code, hdmi_remove_boot_param
|
||||
# (older versions put drm.edid_firmware= on the kernel command line): seed
|
||||
# the parameter where this loader keeps it, run the removal, and check that
|
||||
# it's gone and the boot entries are rebuilt.
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }
|
||||
sudo mountpoint -q /mnt || sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
cd /mnt || exit 1
|
||||
. lib/common.sh; . lib/state.sh; . lib/hdmi-refresh.sh
|
||||
f="$(hdmi_boot_file)"
|
||||
echo "boot file: ${f:-none}"
|
||||
[[ -n "$f" ]] && pass "hdmi_boot_file finds the loader's file" || { fail "hdmi_boot_file finds nothing"; exit 0; }
|
||||
p='drm.edid_firmware=DP-1:edid/steamify-test.bin'
|
||||
case "$f" in
|
||||
/etc/default/grub) sudo sed -i -E "s|^(GRUB_CMDLINE_LINUX_DEFAULT=\")|\1$p |" "$f" ;;
|
||||
/etc/sdboot-manage.conf) if grep -q '^LINUX_OPTIONS=' "$f"; then sudo sed -i -E "s|^(LINUX_OPTIONS=\")|\1$p |" "$f"; else echo "LINUX_OPTIONS=\"$p\"" | sudo tee -a "$f" >/dev/null; fi ;;
|
||||
/etc/default/limine) if grep -q '^KERNEL_CMDLINE\[default\]' "$f"; then sudo sed -i -E "s|^(KERNEL_CMDLINE\[default\]\+?=\")|\1$p |" "$f"; else echo "KERNEL_CMDLINE[default]+=\"$p\"" | sudo tee -a "$f" >/dev/null; fi ;;
|
||||
esac
|
||||
[[ -n "$(hdmi_cmdline_param)" ]] && pass "the seeded parameter is detected" || fail "seeded parameter not detected in $f"
|
||||
hdmi_remove_boot_param > /tmp/legacy-param.log 2>&1; rc=$?
|
||||
tail -3 /tmp/legacy-param.log | sed 's/\x1b\[[0-9;]*m//g' | cut -c1-120
|
||||
[[ $rc -eq 0 ]] && pass "hdmi_remove_boot_param exit 0" || fail "hdmi_remove_boot_param exit $rc"
|
||||
[[ -z "$(hdmi_cmdline_param)" ]] && pass "parameter removed from $f" || fail "parameter still in $f"
|
||||
case "$f" in
|
||||
/etc/default/grub) sudo grep -q edid /boot/grub/grub.cfg && fail "edid still in grub.cfg" || pass "no edid in grub.cfg"; [[ $(sudo grep -c '^\s*linux\s' /boot/grub/grub.cfg) -ge 1 ]] && pass "grub.cfg has linux entries" || fail "grub.cfg has no linux entries" ;;
|
||||
/etc/sdboot-manage.conf) sudo sh -c 'grep -l edid /boot/loader/entries/* 2>/dev/null' | grep -q . && fail "edid still in the entries" || pass "no edid in the entries"; [[ $(sudo ls /boot/loader/entries | wc -l) -ge 1 ]] && pass "boot entries exist" || fail "no boot entries" ;;
|
||||
/etc/default/limine) sudo grep -q edid /boot/limine.conf && fail "edid still in limine.conf" || pass "no edid in limine.conf"; sudo grep -q 'linux-cachyos' /boot/limine.conf && pass "limine.conf lists the kernels" || fail "limine.conf lists no kernel" ;;
|
||||
esac
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Guest side: Steamify's DKMS modules exist for every installed kernel.
|
||||
# Prints PASS/FAIL lines (scripts/vmbootloadertest.sh counts the FAILs).
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }
|
||||
# HARDWARE=generic (a plain PC): none of Steamify's Steam Machine modules belong on it.
|
||||
if [[ "${HARDWARE:-fremont}" == generic ]]; then
|
||||
for k in $(ls /usr/lib/modules); do
|
||||
for m in cros-ec-cec leds-valve steamify-fremont-poweroff; do
|
||||
compgen -G "/usr/lib/modules/$k/updates/dkms/$m.ko*" >/dev/null && fail "$m is installed for $k, but this is not a Steam Machine" || pass "no $m for $k (not a Steam Machine)"
|
||||
done
|
||||
done
|
||||
[[ -f /etc/modules-load.d/steamify-fremont-poweroff.conf ]] && fail "modules-load.d has the power-off fix on a plain PC" || pass "no modules-load.d entry (not a Steam Machine)"
|
||||
exit 0
|
||||
fi
|
||||
kernels=$(ls /usr/lib/modules)
|
||||
n=$(wc -w <<< "$kernels")
|
||||
echo "kernels: $(tr '\n' ' ' <<< "$kernels")"
|
||||
dkms status
|
||||
[[ $(dkms status | grep -c ': installed') -eq $((3 * n)) ]] && pass "3 DKMS modules x $n kernels installed" || fail "dkms status is not 3 modules x $n kernels"
|
||||
for k in $kernels; do
|
||||
for m in cros-ec-cec leds-valve steamify-fremont-poweroff; do
|
||||
compgen -G "/usr/lib/modules/$k/updates/dkms/$m.ko*" >/dev/null && pass "$m for $k" || fail "$m missing for $k"
|
||||
done
|
||||
done
|
||||
[[ -f /etc/modules-load.d/steamify-fremont-poweroff.conf ]] && pass "modules-load.d has the power-off fix" || fail "no modules-load.d entry"
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Guest side ($LOADER = limine|systemd-boot|grub): Steamify's state as its own
|
||||
# menu detects it (the same *_status functions), the OS name untouched, and
|
||||
# the boot loader as expected.
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }
|
||||
sudo mountpoint -q /mnt || sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
cd /mnt || exit 1
|
||||
for lib in $(grep -oP '^for lib in \K.*(?=; do$)' steamify.sh); do
|
||||
# shellcheck source=/dev/null
|
||||
. "lib/$lib.sh" 2>/dev/null
|
||||
done
|
||||
# Items this Steam Machine VM has on after `--options gaming,theme,glyphs,single,launcher,notify,cec,machine,poweroff`.
|
||||
# HARDWARE=generic (a plain PC): the Steam Machine items (poweroff, cec) must stay off.
|
||||
for item in gaming theme glyphs single launcher notify; do
|
||||
if "${item}_status" 2>/dev/null; then pass "$item is on (the menu shows it ticked)"; else fail "$item is off"; fi
|
||||
done
|
||||
for item in poweroff cec; do
|
||||
if [[ "${HARDWARE:-fremont}" == generic ]]; then
|
||||
if "${item}_status" 2>/dev/null; then fail "$item is on, but this is not a Steam Machine"; else pass "$item is off (not a Steam Machine)"; fi
|
||||
else
|
||||
if "${item}_status" 2>/dev/null; then pass "$item is on (the menu shows it ticked)"; else fail "$item is off"; fi
|
||||
fi
|
||||
done
|
||||
# Steamify never renames the OS (limine-snapper-sync / grub-btrfs read it).
|
||||
grep -qx 'NAME="CachyOS Linux"' /etc/os-release && grep -qx 'PRETTY_NAME="CachyOS"' /etc/os-release && pass "os-release NAME and PRETTY_NAME are CachyOS's" || fail "os-release NAME/PRETTY_NAME changed"
|
||||
grep -q 'with Steamify' /etc/lsb-release && fail "lsb-release still says 'with Steamify'" || pass "lsb-release name is CachyOS's"
|
||||
# The boot loader is the one this VM was installed with.
|
||||
case "$LOADER" in
|
||||
limine) command -v limine-update >/dev/null && [[ -f /etc/default/limine ]] && pass "Limine is installed" || fail "no Limine"
|
||||
grep -q '^TARGET_OS_NAME="CachyOS"' /etc/default/limine && pass "limine TARGET_OS_NAME is set" || fail "TARGET_OS_NAME missing in /etc/default/limine" ;;
|
||||
systemd-boot) sudo bootctl status --no-pager 2>/dev/null | grep -q 'Product: systemd-boot' && pass "systemd-boot is the boot loader" || fail "not systemd-boot" ;;
|
||||
grub) sudo bootctl status --no-pager 2>/dev/null | grep -q 'Product: GRUB' && pass "GRUB is the boot loader" || fail "not GRUB" ;;
|
||||
esac
|
||||
# "BIOS" menu item: rebooting into the firmware setup must be possible (never trigger it here).
|
||||
sudo bootctl status --no-pager 2>/dev/null | grep -q 'Boot into FW: supported' && pass "reboot into firmware setup is supported" || fail "firmware setup reboot not supported"
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# Runs in the live ISO as root (started by the systemd.run= boot parameter
|
||||
# that scripts/vminstall.sh passes): installs CachyOS unattended with its
|
||||
# headless installer (settings.json from the host), then makes the new
|
||||
# system a test VM (SSH key, passwordless sudo, test autologin), and powers
|
||||
# off. Everything it needs comes from the host's vminstall-server.py; the log
|
||||
# and the result go back there (PUT log / status).
|
||||
set -uo pipefail
|
||||
H="http://@HOST_IP@:@PORT@"
|
||||
LOG=/tmp/vminstall.log
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
up() { curl -fsS -T "$LOG" "$H/install.log" >/dev/null 2>&1; }
|
||||
finish() { echo "== $1"; up; curl -fsS -X PUT --data "$1" "$H/status" >/dev/null 2>&1; }
|
||||
fail() { finish "failed: $*"; exit 1; }
|
||||
|
||||
echo "== Steamify test VM: unattended install ($(date))"
|
||||
curl -fsS "$H/vminstall.env" -o /tmp/vminstall.env || fail "no vminstall.env from the host"
|
||||
# shellcheck disable=SC1091
|
||||
. /tmp/vminstall.env
|
||||
curl -fsS "$H/settings.json" -o /tmp/settings.json || fail "no settings.json from the host"
|
||||
curl -fsS "$H/vminstall-post.sh" -o /tmp/vminstall-post.sh || fail "no vminstall-post.sh from the host"
|
||||
|
||||
# Let whoever watches the VM's window follow along: a Konsole on the live
|
||||
# desktop that shows this log.
|
||||
(
|
||||
for _ in $(seq 100); do pgrep -u liveuser -x plasmashell >/dev/null && break; sleep 3; done
|
||||
sleep 5
|
||||
runuser -u liveuser -- env XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
|
||||
systemd-run --user -q konsole --hold -e tail -n +1 -f "$LOG"
|
||||
) &
|
||||
while sleep 20; do up; done &
|
||||
|
||||
# No `systemctl is-system-running --wait`: this script is itself the running
|
||||
# kernel-command-line.service job, so the boot never finishes while it waits.
|
||||
# The live keyring must exist before pacman-key can sign (the install failed with
|
||||
# "no secret key available to sign with" when this ran first).
|
||||
for _ in $(seq 120); do systemctl is-active --quiet pacman-init.service && break; sleep 2; done
|
||||
nm-online -t 180 >/dev/null || fail "no network"
|
||||
# The live ISO's mirror list and keys can be old; the installer's pacstrap needs current ones.
|
||||
pacman -Sy --noconfirm archlinux-keyring cachyos-keyring || echo "(keyring update failed, going on)"
|
||||
|
||||
# The host's package cache (VM_CACHE, 9p tag `cache`) as pacman's cache: the
|
||||
# installer's pacstrap -c uses the live system's, so a second install downloads nothing.
|
||||
CACHE=/var/cache/steamify-pkg
|
||||
if mkdir -p $CACHE && mount -t 9p -o trans=virtio,version=9p2000.L cache $CACHE 2>/dev/null; then
|
||||
mkdir -p $CACHE/pkg && mount --bind $CACHE/pkg /var/cache/pacman/pkg &&
|
||||
echo "== package cache: $(ls $CACHE/pkg | wc -l) files from the host"
|
||||
else
|
||||
CACHE=""; echo "== no package cache (VM_CACHE off)"
|
||||
fi
|
||||
|
||||
echo "== cachyos-installer"
|
||||
cachyos-installer --config /tmp/settings.json || fail "cachyos-installer exited with $?"
|
||||
|
||||
# The installer leaves the new system mounted on /mnt, or not: mount it.
|
||||
if ! mountpoint -q /mnt; then
|
||||
mount -o subvol=@ "${DEVICE}2" /mnt || fail "can't mount the new system"
|
||||
mount -o subvol=@home "${DEVICE}2" /mnt/home 2>/dev/null
|
||||
mount "${DEVICE}1" /mnt/boot 2>/dev/null
|
||||
fi
|
||||
[[ -d "/mnt/home/$VM_USER" ]] || fail "the installer created no /home/$VM_USER"
|
||||
|
||||
# Steamify's packages (Steam, ...) install inside the new system: same cache.
|
||||
if [[ -n "$CACHE" ]]; then
|
||||
mkdir -p /mnt/var/cache/pacman/pkg && mount --bind $CACHE/pkg /mnt/var/cache/pacman/pkg || echo "(no cache in the new system)"
|
||||
# Steamify's own downloads (the CEC driver: GitHub rate-limits it)
|
||||
mkdir -p $CACHE/steamify /mnt/var/cache/steamify && mount --bind $CACHE/steamify /mnt/var/cache/steamify || true
|
||||
fi
|
||||
|
||||
# What Calamares does after its users step (shellprocess_steamify): Steamify's
|
||||
# setup for the new user. The headless installer skips it, so a Steamify ISO
|
||||
# would otherwise leave plain CachyOS. VM_STEAMIFY: the page's choice (ids,
|
||||
# comma-separated; empty = its default, everything on; none = skip).
|
||||
if [[ -x /usr/local/bin/steamify-install && "${VM_STEAMIFY:-}" != skip ]]; then
|
||||
echo "== steamify-install (${VM_STEAMIFY:-defaults})"
|
||||
/usr/local/bin/steamify-install /mnt "$VM_USER" "${VM_STEAMIFY:-}"
|
||||
cat "/mnt/var/log/steamify-install.log" 2>/dev/null
|
||||
grep -q '^exit: 0' /mnt/var/log/steamify-install.log 2>/dev/null || fail "steamify-install did not finish cleanly"
|
||||
else
|
||||
echo "== no steamify-install on this ISO (or VM_STEAMIFY=skip): plain CachyOS"
|
||||
fi
|
||||
|
||||
mountpoint -q /mnt/var/cache/pacman/pkg && umount /mnt/var/cache/pacman/pkg
|
||||
|
||||
echo "== test VM setup"
|
||||
cp /tmp/vminstall-post.sh /tmp/vminstall.env /mnt/root/
|
||||
arch-chroot /mnt bash /root/vminstall-post.sh || fail "test VM setup exited with $?"
|
||||
rm -f /mnt/root/vminstall-post.sh /mnt/root/vminstall.env
|
||||
cp "$LOG" /mnt/var/log/vminstall.log
|
||||
sync
|
||||
finish ok
|
||||
sleep 2
|
||||
systemctl poweroff
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Runs inside the freshly installed system (arch-chroot, from
|
||||
# vminstall-live.sh): what guest-ssh-setup.sh and the old manual steps did,
|
||||
# so the VM is ssh-ready without touching it: sshd with the host's key,
|
||||
# passwordless sudo, the test autologin into Plasma, tmux and shellcheck.
|
||||
set -euo pipefail
|
||||
# shellcheck disable=SC1091
|
||||
. /root/vminstall.env
|
||||
|
||||
echo "$VM_HOSTNAME" > /etc/hostname
|
||||
pacman -S --needed --noconfirm openssh tmux shellcheck
|
||||
systemctl enable sshd
|
||||
|
||||
home="/home/$VM_USER"
|
||||
install -d -m 700 -o "$VM_USER" -g "$VM_USER" "$home/.ssh"
|
||||
printf '%s\n' "$VM_PUBKEY" > "$home/.ssh/authorized_keys"
|
||||
chown "$VM_USER:$VM_USER" "$home/.ssh/authorized_keys"
|
||||
chmod 600 "$home/.ssh/authorized_keys"
|
||||
|
||||
# Test VM only: the host drives it without a password.
|
||||
echo "$VM_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/99-test-vm
|
||||
chmod 440 /etc/sudoers.d/99-test-vm
|
||||
|
||||
# Log straight into Plasma (gamescope doesn't render in the VM). The
|
||||
# scripts expect plasma-login-manager, like a CachyOS install from Calamares.
|
||||
# Not when Steamify's conversion is on (steamify-install ran on a Steamify
|
||||
# ISO): it made SDDM boot into gamescope, and that is what gets tested; sshd
|
||||
# doesn't need a desktop.
|
||||
if [[ -f /etc/sddm.conf.d/10-gamescope-autologin.conf ]]; then
|
||||
echo "Steamify's gaming-mode login (SDDM) kept."
|
||||
else
|
||||
pacman -Q plasma-login-manager >/dev/null 2>&1 || pacman -S --needed --noconfirm plasma-login-manager
|
||||
systemctl disable sddm.service 2>/dev/null || true
|
||||
systemctl enable plasmalogin.service
|
||||
mkdir -p /etc/plasmalogin.conf.d
|
||||
printf '[Autologin]\nUser=%s\nSession=plasma.desktop\n' "$VM_USER" > /etc/plasmalogin.conf.d/00-test-autologin.conf
|
||||
fi
|
||||
|
||||
# cachyos-installer's limine.conf has no default_entry: entry 1 is the
|
||||
# "CachyOS" folder, so the first boot waits in the menu. 2 is its first
|
||||
# kernel (linux-cachyos); limine-update keeps the line.
|
||||
if [[ -f /boot/limine.conf ]] && ! grep -q '^default_entry:' /boot/limine.conf; then
|
||||
sed -i 's/^timeout: .*/&\ndefault_entry: 2/' /boot/limine.conf
|
||||
fi
|
||||
|
||||
# cachyos-installer's systemd-boot loader.conf has `#timeout 3` commented out and
|
||||
# no default: the first boot would wait in the menu for ever. Boot the first
|
||||
# kernel after 3 s.
|
||||
if [[ -f /boot/loader/loader.conf ]] && ! grep -q '^timeout' /boot/loader/loader.conf; then
|
||||
printf 'timeout 3\ndefault linux-cachyos.conf\n' >> /boot/loader/loader.conf
|
||||
fi
|
||||
|
||||
# The installer enables ufw, which drops the host's ssh connections (found
|
||||
# in the kernel log: [UFW BLOCK] DPT=22). Let ssh in; the rest stays as installed.
|
||||
if command -v ufw >/dev/null; then
|
||||
ufw allow 22/tcp || echo "(ufw rule failed: ssh may be blocked)"
|
||||
fi
|
||||
|
||||
# English everywhere, whatever the live ISO picked.
|
||||
echo 'LANG=en_US.UTF-8' > /etc/locale.conf
|
||||
|
||||
# mirror5.krfoss.org served broken signatures; skip it (vmreset.sh does too).
|
||||
for f in /etc/pacman.d/*mirrorlist*; do sed -i '/krfoss/s/^Server/#Server/' "$f"; done
|
||||
echo "Test VM setup done: $VM_USER@$VM_HOSTNAME"
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
# G1-G3 (--defaults --list), F1, F2, F6 (bad arguments): read-only, nothing may change.
|
||||
before=$(ls -R ~/.local/state/steamify 2>/dev/null | md5sum)
|
||||
rejects() { # rejects <expected message> <args...>: exit 1 and the message
|
||||
local want="$1"; shift
|
||||
out=$(bash steamify.sh "$@" 2>&1 < /dev/null); rc=$?
|
||||
[[ $rc -eq 1 ]] && grep -q "$want" <<< "$out" && pass "steamify.sh $* -> exit 1 ($want)" || fail "steamify.sh $* -> exit $rc: $(sed 's/\x1b\[[0-9;]*m//g' <<< "$out" | grep -m1 -E 'ERROR|rror')"
|
||||
}
|
||||
# F1
|
||||
rejects "needs a value" --defaults --options
|
||||
rejects "Unknown item" --defaults --options bogus
|
||||
rejects "not an item" --defaults --options boot
|
||||
rejects "can't be part of the install" --defaults --options bios
|
||||
rejects "needs a value" --defaults --boot
|
||||
rejects "gamescope or desktop" --defaults --boot sideways
|
||||
rejects "Unknown option" --defaults --skip x
|
||||
# F2
|
||||
rejects "needs the SteamOS conversion" --defaults --options theme --boot desktop
|
||||
rejects "needs the SteamOS conversion" --defaults --boot desktop --options theme
|
||||
# F6
|
||||
rejects "needs the SteamOS conversion" --boot desktop
|
||||
# G1: valid JSON, exit 0, no stderr
|
||||
out=$(bash steamify.sh --defaults --list 2>/tmp/list.err); rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "G1 --list exit 0" || fail "G1 --list exit $rc"
|
||||
[[ ! -s /tmp/list.err ]] && pass "G1 --list prints nothing on stderr" || fail "G1 --list stderr: $(head -c 100 /tmp/list.err)"
|
||||
python3 - "$out" <<'PY'
|
||||
import json, sys
|
||||
def r(ok, msg): print(("PASS " if ok else "FAIL ") + msg)
|
||||
try:
|
||||
d = json.loads(sys.argv[1]); r(isinstance(d, list) and d, "G1 --list is a JSON array")
|
||||
except Exception as e:
|
||||
r(False, f"G1 --list is not JSON: {e}"); sys.exit()
|
||||
ids = {i.get("id"): i for i in d}
|
||||
# G2: actions and retired items are not offered; poweroff needs machine, boot needs gaming
|
||||
r(not ({"bios", "kpin", "hdmi"} & set(ids)), "G2 bios, kpin and hdmi (actions, retired) are left out")
|
||||
r(not ({"vram", "steamgame"} & set(ids)), "G2 vram and steamgame (unavailable here) are left out")
|
||||
r("machine" in str(ids.get("poweroff", {}).get("parent", "")), "G2 poweroff has the parent machine")
|
||||
r("gaming" in str(ids.get("boot", {}).get("parent", "")), "G2 boot has the parent gaming")
|
||||
PY
|
||||
# G3 and nothing changed: the errors and --list must not touch the state
|
||||
after=$(ls -R ~/.local/state/steamify 2>/dev/null | md5sum)
|
||||
[[ "$before" == "$after" ]] && pass "F1/F2/F6/G1 changed nothing" || fail "the state directory changed"
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
# F3: every item and --boot desktop: all on, the boot-desktop unit enabled, VRAM left out with a warning.
|
||||
info "applying Steamify (installs packages, builds kernel modules: a few minutes)..."
|
||||
out=$(sf --defaults --options gaming,theme,glyphs,single,launcher,notify,vram,cec,machine,poweroff --boot desktop); rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "F3 exit 0" || fail "F3 exit $rc"
|
||||
expect_on gaming theme glyphs single launcher notify machine poweroff cec
|
||||
grep -qi "VRAM booster" <<< "$out" && pass "F3 VRAM booster left out with a warning" || fail "F3 no VRAM warning"
|
||||
is vram && fail "F3 vram is on (the VM has no dmem region)" || pass "F3 vram stays off"
|
||||
systemctl is-enabled steamify-boot-desktop.service >/dev/null 2>&1 && pass "F3 boot-desktop unit enabled" || fail "F3 boot-desktop unit not enabled"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# F4: --options theme,single,machine --boot desktop: single brings the conversion; the rest stays off.
|
||||
info "applying Steamify (installs packages, builds kernel modules: a few minutes)..."
|
||||
sf --defaults --options theme,single,machine --boot desktop >/dev/null; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "F4 exit 0" || fail "F4 exit $rc"
|
||||
expect_on gaming theme single machine
|
||||
expect_off poweroff cec glyphs launcher notify
|
||||
systemctl is-enabled steamify-boot-desktop.service >/dev/null 2>&1 && pass "F4 boot-desktop unit enabled" || fail "F4 boot-desktop unit not enabled"
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# F5: --options launcher,vram,poweroff: poweroff brings Steam Machine support; conversion off, plasmalogin unchanged.
|
||||
dm_before=$(current_display_manager)
|
||||
info "applying Steamify (installs packages, builds kernel modules: a few minutes)..."
|
||||
sf --defaults --options launcher,vram,poweroff >/dev/null; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "F5 exit 0" || fail "F5 exit $rc"
|
||||
expect_on machine poweroff launcher
|
||||
expect_off gaming
|
||||
[[ "$(current_display_manager)" == "$dm_before" ]] && pass "F5 display manager unchanged ($dm_before)" || fail "F5 display manager changed: $dm_before -> $(current_display_manager)"
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
# F7: on an install, --boot desktop, again, --boot gamescope: only Boot into changes; the menu ticks stay.
|
||||
info "applying Steamify (installs packages, builds kernel modules: a few minutes)..."
|
||||
sf --defaults --options gaming,theme,single,launcher,notify,machine,poweroff >/dev/null
|
||||
base=$(ticks | sed 's/boot //')
|
||||
sf --boot desktop >/dev/null; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "F7 --boot desktop exit 0" || fail "F7 --boot desktop exit $rc"
|
||||
systemctl is-enabled steamify-boot-desktop.service >/dev/null 2>&1 && pass "F7 boot-desktop unit enabled" || fail "F7 unit not enabled after --boot desktop"
|
||||
[[ "$(ticks | sed 's/boot //')" == "$base" ]] && pass "F7 the menu ticks are unchanged" || fail "F7 ticks changed: [$base] -> [$(ticks)]"
|
||||
out=$(sf --boot desktop); grep -qi "already" <<< "$out" && pass "F7 second --boot desktop says already" || fail "F7 no 'Already starting in desktop': $(tail -n 2 <<< "$out" | tr '\n' ' ')"
|
||||
sf --boot gamescope >/dev/null; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "F7 --boot gamescope exit 0" || fail "F7 --boot gamescope exit $rc"
|
||||
systemctl is-enabled steamify-boot-desktop.service >/dev/null 2>&1 && fail "F7 unit still enabled after --boot gamescope" || pass "F7 boot-desktop unit disabled again"
|
||||
[[ "$(ticks | sed 's/boot //')" == "$base" ]] && pass "F7 ticks unchanged after --boot gamescope" || fail "F7 ticks changed after gamescope"
|
||||
@@ -0,0 +1,19 @@
|
||||
# C1-C3: extended controller support: the repo's DKMS drivers (xone + dongle firmware, xpadneo) are installed and built for every
|
||||
# installed kernel; a second run is quiet; turning it off removes them again. The VM has no dongle or controller: this covers
|
||||
# the packages, the headers and the DKMS builds, not the hardware. Covers TESTPLAN C1 (on), C2 (again), C3 (off).
|
||||
info "applying extended_controller_support (kernel headers and DKMS builds for every kernel: a few minutes)..."
|
||||
sf --defaults --options extended_controller_support > /tmp/c1.log; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "C1 exit 0" || { fail "C1 exit $rc: $(tail -n 12 /tmp/c1.log)"; exit 0; }
|
||||
pacman -Q xpadneo-dkms xone-dongle-firmware >/dev/null 2>&1 && pacman -Q xone-dkms >/dev/null 2>&1 && pass "C1 xpadneo-dkms, xone-dkms and xone-dongle-firmware are installed" || fail "C1 packages: $(pacman -Qq | grep -E 'xone|xpadneo' | tr '\n' ' ')"
|
||||
expect_on extended_controller_support
|
||||
for kd in /usr/lib/modules/*/; do
|
||||
k="$(basename "$kd")"; [[ -d "$kd/kernel" ]] || continue
|
||||
n="$(dkms status -k "$k" 2>/dev/null | grep -E 'xone|xpadneo' | grep -c installed)"
|
||||
[[ "$n" -ge 2 ]] && pass "C1 $k: DKMS built xone and xpadneo ($n modules installed)" || fail "C1 $k: DKMS: $(dkms status -k "$k" 2>&1 | tr '\n' ';')"
|
||||
modinfo -k "$k" xone_dongle >/dev/null 2>&1 && modinfo -k "$k" hid_xpadneo >/dev/null 2>&1 && pass "C1 $k: xone_dongle and hid_xpadneo modules exist" || fail "C1 $k: modules missing"
|
||||
done
|
||||
sf --defaults --options extended_controller_support | grep -qiE 'nothing|already|exit: 0' && pass "C2 a second run is quiet" || info "C2 second run: $(sf --defaults --options extended_controller_support | tail -n 3)"
|
||||
sf --defaults --options notify > /tmp/c3.log; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "C3 turn-off exit 0" || fail "C3 exit $rc: $(tail -n 8 /tmp/c3.log)"
|
||||
pacman -Q xpadneo-dkms xone-dkms xone-dongle-firmware >/dev/null 2>&1 && fail "C3 packages are still installed" || pass "C3 the three packages are removed again"
|
||||
expect_off extended_controller_support
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
# H1 (LED driver) and H5 (power-off module) with the faked Steam Machine.
|
||||
info "applying Steamify (installs packages, builds kernel modules: a few minutes)..."
|
||||
sf --defaults --options gaming,theme,glyphs,single,launcher,notify,cec,machine,poweroff >/dev/null; rc=$?
|
||||
[[ $rc -eq 0 ]] && pass "Steamify applied (exit 0)" || fail "Steamify exit $rc"
|
||||
# H1
|
||||
lsmod | grep -q '^leds_valve' && pass "H1 leds_valve loaded" || fail "H1 leds_valve not loaded"
|
||||
n=$(compgen -G "/sys/class/leds/valve-leds*" | wc -l); [[ $n -eq 17 ]] && pass "H1 17 valve-leds nodes" || fail "H1 $n valve-leds nodes"
|
||||
own=$(stat -c %U /sys/class/leds/valve-leds*/brightness 2>/dev/null | sort -u | tr '\n' ' '); [[ "$own" == "$(id -un) " ]] && pass "H1 the nodes belong to the user" || fail "H1 node owner: $own"
|
||||
# H5
|
||||
for k in $(ls /usr/lib/modules); do
|
||||
compgen -G "/usr/lib/modules/$k/updates/dkms/steamify-fremont-poweroff.ko*" >/dev/null && pass "H5 power-off module built for $k" || fail "H5 no power-off module for $k"
|
||||
done
|
||||
sudo modprobe -r steamify_fremont_poweroff 2>/dev/null
|
||||
out=$(sudo modprobe steamify_fremont_poweroff 2>&1); rc=$?
|
||||
# in the VM there is no GPIO wake bit: it loads, or answers "No such device"
|
||||
[[ $rc -eq 0 ]] || grep -q "No such device" <<< "$out" && pass "H5 the module loads (or: No such device, no GPIO wake bit)" || fail "H5 modprobe: $out"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# H4: Boot into desktop, reboot: Plasma straight after boot; Boot into gaming: SDDM autologin into gamescope (session file).
|
||||
conf=/etc/sddm.conf.d/10-gamescope-autologin.conf
|
||||
info "applying Steamify (a few minutes)..."
|
||||
guest 'sf --defaults --options gaming,theme,single,machine --boot gamescope >/dev/null; echo done' | tail -n 1 | grep -q done && pass "H4 conversion applied" || fail "H4 apply"
|
||||
guest "grep -h '^Session=' $conf" | grep -qi gamescope && pass "H4 gaming: the SDDM autologin session is gamescope" || fail "H4 gaming session: $(guest "grep -h Session= $conf")"
|
||||
guest 'sf --boot desktop >/dev/null; echo done' | tail -n 1 | grep -q done && pass "H4 --boot desktop" || fail "H4 --boot desktop"
|
||||
reboot_guest && pass "H4 the VM is back after the reboot" || { fail "H4 no ssh after the reboot"; return 0 2>/dev/null || exit 0; }
|
||||
up=""; for _ in $(seq 40); do vm_ssh "pgrep -u $VM_USER -x plasmashell" >/dev/null 2>&1 && { up=1; break; }; sleep 3; done
|
||||
[[ -n "$up" ]] && pass "H4 Plasma straight after the boot (boot into desktop)" || fail "H4 no plasmashell after the reboot"
|
||||
guest 'sf --boot gamescope >/dev/null; echo done' | tail -n 1 | grep -q done && pass "H4 --boot gamescope" || fail "H4 --boot gamescope"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
# R1.7 / H2: HDMI-CEC with vivid (fake TV and remote): scripts/vmcec.sh prints PASS/FAIL/SKIP (2 SKIP in the VM: wake from TV, sleep).
|
||||
info "applying Steamify (a few minutes)..."
|
||||
guest 'sf --defaults --options gaming,theme,glyphs,single,launcher,notify,cec,machine,poweroff >/dev/null; sudo modprobe vivid; echo done' | tail -n 1 | grep -q done && pass "CEC: Steamify applied, vivid loaded" || fail "CEC setup"
|
||||
"$repo/scripts/vmcec.sh" --no-sleep 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | grep -E '^(PASS|FAIL|SKIP)|PASS|FAIL' | sed -E 's/^[^A-Z]*(PASS|FAIL|SKIP)/\1 CEC:/' | cut -c1-140
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
# H3: BIOS update, the whole flow with WIZARD_BIOS_DRY_RUN=1 (never flashes). The VM reports an older BIOS.
|
||||
# env: BIOS_VERSION=F7F0107
|
||||
# The BIOS row's number by its name (a new menu item shifts the numbers).
|
||||
n=$(printf 'q\n' | bash /mnt/steamify.sh 2>&1 | sed 's/\x1b\[[0-9;]*m//g' | sed -n 's/^ *\([0-9][0-9]*\) .*Update BIOS.*/\1/p' | head -n 1)
|
||||
[[ -n "$n" ]] && pass "H3 Update BIOS is menu row $n" || { fail "H3 no Update BIOS row in the menu"; exit 0; }
|
||||
out=$(printf "$n\n\ny\ny\nUPDATE\nm\nq\nn\n" | WIZARD_BIOS_DRY_RUN=1 bash /mnt/steamify.sh 2>&1 | sed 's/\x1b\[[0-9;]*m//g')
|
||||
grep -qi "dry run" <<< "$out" && pass "H3 the dry run went through" || fail "H3 no dry-run message: $(tail -n 3 <<< "$out" | tr '\n' ' ' | cut -c1-160)"
|
||||
grep -q '^\[ERROR\]' <<< "$out" && fail "H3 errors: $(grep -B4 -m1 '^\[ERROR\]' <<< "$out" | tr '\n' '|' | cut -c1-400)" || pass "H3 no errors"
|
||||
@@ -0,0 +1,30 @@
|
||||
# R1.7 (CEC driver cache): Valve's CEC driver is downloaded once and kept in /var/cache/steamify, named after
|
||||
# its checksum; a new pin downloads its own file and removes the old one, a damaged file is replaced.
|
||||
# Offline: a private cache folder, and the host's cached copy as the "download" (file://), so GitHub's rate
|
||||
# limit can't fail this block. Needs the host cache (vmsuite.sh binds ~/vms/pkg-cache/steamify).
|
||||
real="/var/cache/steamify/cros-ec-cec-${CEC_DRIVER_SHA256:0:12}.c"
|
||||
[[ -f "$real" ]] || { skip "R1.7 cache: no cached CEC driver on the host (~/vms/pkg-cache/steamify)"; exit 0; }
|
||||
dir=$(mktemp -d); sudo chown root: "$dir"; sudo chmod 755 "$dir" # like /var/cache/steamify: the user checks the cache
|
||||
export CEC_DRIVER_URL="file://$real" CEC_DRIVER_CACHE="$dir/cros-ec-cec-${CEC_DRIVER_SHA256:0:12}.c"
|
||||
errs=""
|
||||
run() { out=$(cec_driver_enable 2>&1 | sed 's/\x1b\[[0-9;]*m//g'); errs+=$(grep -B6 '^\[ERROR\]' <<< "$out" | tr '\n' '|'); } # sets out
|
||||
|
||||
# 1. new pin: an older pin's file is there, this one isn't
|
||||
sudo touch "$dir/cros-ec-cec-000000000000.c"
|
||||
run
|
||||
grep -q "Downloading Valve" <<< "$out" && pass "R1.7 cache: a new pin downloads its driver" || fail "R1.7 cache: no download for a new pin"
|
||||
echo "$CEC_DRIVER_SHA256 $CEC_DRIVER_CACHE" | sha256sum -c --quiet - 2>/dev/null && pass "R1.7 cache: the driver is kept, checksum OK" || fail "R1.7 cache: not kept after the download"
|
||||
[[ ! -e "$dir/cros-ec-cec-000000000000.c" ]] && pass "R1.7 cache: the older pin's file is removed" || fail "R1.7 cache: the older pin's file is still there"
|
||||
[[ -d "$CEC_DKMS_SRC" ]] && pass "R1.7 cache: the driver is in DKMS" || fail "R1.7 cache: no DKMS source"
|
||||
|
||||
# 2. cache hit: no download
|
||||
run
|
||||
grep -q "Downloading Valve" <<< "$out" && fail "R1.7 cache: downloaded again although it's cached" || pass "R1.7 cache: the cached driver is used, no download"
|
||||
|
||||
# 3. a damaged file under the right name: downloaded again and replaced
|
||||
echo broken | sudo tee "$CEC_DRIVER_CACHE" >/dev/null
|
||||
run
|
||||
grep -q "Downloading Valve" <<< "$out" && echo "$CEC_DRIVER_SHA256 $CEC_DRIVER_CACHE" | sha256sum -c --quiet - 2>/dev/null &&
|
||||
pass "R1.7 cache: a damaged cached driver is replaced" || fail "R1.7 cache: a damaged cached driver stays"
|
||||
[[ -z "$errs" ]] && pass "R1.7 cache: no errors in the three runs" || fail "R1.7 cache: errors: $(cut -c1-600 <<< "$errs")"
|
||||
sudo rm -rf "$dir"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# R2.1: the ISO's installer step: a new user who never logged in gets plain `--defaults` (scripts/vminstallsim.sh).
|
||||
info "installer simulation: a new user, --defaults (a few minutes)..."
|
||||
out=$("$repo/scripts/vminstallsim.sh" --fresh 2>&1 | sed 's/\x1b\[[0-9;]*m//g')
|
||||
grep -q '^exit: 0' <<< "$out" && pass "R2.1 exit 0" || fail "R2.1 $(grep -m1 '^exit' <<< "$out")"
|
||||
grep -q '^\[ERROR\]' <<< "$out" && fail "R2.1 errors: $(grep -m1 '^\[ERROR\]' <<< "$out")" || pass "R2.1 no errors"
|
||||
[[ $(grep -c '^Turning on' <<< "$out") -ge 5 ]] && pass "R2.1 every default item turned on ($(grep -c '^Turning on' <<< "$out"))" || fail "R2.1 only $(grep -c '^Turning on' <<< "$out") items turned on"
|
||||
vm_ssh 'sudo ls /home/isotest/.config/autostart/ 2>/dev/null' | grep -qi steamify && pass "R2.1 the first-login autostart is in place" || fail "R2.1 no first-login autostart"
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
# R1.1-R1.6, R1.10: the menu on an existing desktop, driven with scripted input (each row builds on the last).
|
||||
menu() { printf "$1" | bash /mnt/steamify.sh 2>&1 | sed 's/\x1b\[[0-9;]*m//g'; }
|
||||
# R1.1: first-run ticks: everything but Boot into desktop and BIOS
|
||||
out=$(menu 'q\n')
|
||||
# By name, not by row number (a new item shifts the numbers): the ticked ones, then the ones that are shown but never preselected.
|
||||
for item in "SteamOS conversion" "SteamOS theme" "Steam Deck/Machine icons" "Single user mode" "Steamify shortcut" "Update notifications" "HDMI-CEC" "Steam Machine support" "Power-off fix"; do
|
||||
grep -qE "^ +[0-9]+ +[a-z-]+ +\[x\] .*$item" <<< "$out" && pass "R1.1 $item ticked" || fail "R1.1 $item not ticked"
|
||||
done
|
||||
for item in "Extended controller support" "Update BIOS"; do
|
||||
grep -qE "^ +[0-9]+ +[a-z-]+ +\[ \] .*$item" <<< "$out" && pass "R1.1 $item shown, not ticked" || fail "R1.1 $item missing or ticked"
|
||||
done
|
||||
grep -q "Boot into: \[gamescope\]" <<< "$out" && pass "R1.1 boots into gamescope by default" || fail "R1.1 boot default is not gamescope"
|
||||
# R1.2: everything OK, back in the menu with all on
|
||||
info "applying Steamify (installs packages, builds kernel modules: a few minutes)..."
|
||||
out=$(menu '\ny\nm\nq\nn\n')
|
||||
grep -q '^\[ERROR\]' <<< "$out" && fail "R1.2 errors: $(grep -m1 '^\[ERROR\]' <<< "$out")" || pass "R1.2 no errors"
|
||||
grep -q "^\[OK\]" <<< "$out" && pass "R1.2 applied (the run printed [OK] lines)" || fail "R1.2 printed no [OK] line"
|
||||
expect_on gaming theme glyphs single launcher notify cec machine poweroff
|
||||
# R1.3: the state (what vmstate.sh prints)
|
||||
[[ "$(current_display_manager)" == sddm ]] && [[ -f /etc/sddm.conf.d/10-gamescope-autologin.conf ]] && pass "R1.3 SDDM with autologin" || fail "R1.3 no SDDM autologin"
|
||||
n=$(compgen -G "/sys/class/leds/valve-leds*" | wc -l); [[ $n -eq 17 ]] && pass "R1.3 17 LED nodes" || fail "R1.3 $n LED nodes"
|
||||
own=$(stat -c %U /sys/class/leds/valve-leds*/brightness 2>/dev/null | sort -u | tr '\n' ' '); [[ "$own" == "$(id -un) " ]] && pass "R1.3 LEDs owned by the user" || fail "R1.3 LED owner: $own"
|
||||
[[ $(dkms status | grep -c ': installed') -ge $((3 * $(ls /usr/lib/modules | wc -w))) ]] && pass "R1.3 DKMS installed for every kernel" || fail "R1.3 DKMS: $(dkms status | tr '\n' ';')"
|
||||
systemctl is-active --quiet steamos-manager && pass "R1.3 steamos-manager active" || systemctl --user is-active --quiet steamos-manager && pass "R1.3 steamos-manager active (user)" || fail "R1.3 steamos-manager not active"
|
||||
# R1.4: nothing to do
|
||||
out=$(menu '\nq\n'); grep -qi "already the way you want it" <<< "$out" && pass "R1.4 already the way you want it" || fail "R1.4 message missing"
|
||||
# R1.5: re-apply: no errors, no duplicates in the journals
|
||||
out=$(menu 'a\ny\nm\nq\nn\n')
|
||||
grep -q '^\[ERROR\]' <<< "$out" && fail "R1.5 errors: $(grep -m1 '^\[ERROR\]' <<< "$out")" || pass "R1.5 re-apply without errors"
|
||||
dup=""; for f in ~/.local/state/steamify/*; do [ -f "$f" ] && d=$(cut -f1-3 "$f" | sort | uniq -d) && [ -n "$d" ] && dup+="$(basename "$f"): $d; "; done
|
||||
[[ -z "$dup" ]] && pass "R1.5 no duplicates in the journals" || fail "R1.5 DUPLICATES $dup"
|
||||
# R1.6: --backend status
|
||||
ver=$(sed -n 's/^VERSION=//p' /mnt/steamify.sh | head -n 1)
|
||||
js=$(bash /mnt/steamify.sh --backend status 2>/dev/null)
|
||||
python3 - "$js" "$ver" <<'PY'
|
||||
import json, sys
|
||||
def r(ok, m): print(("PASS " if ok else "FAIL ") + m)
|
||||
try:
|
||||
d = json.loads(sys.argv[1]); r(True, "R1.6 --backend status is valid JSON")
|
||||
except Exception as e:
|
||||
r(False, f"R1.6 --backend status is not JSON: {e}"); sys.exit()
|
||||
r(sys.argv[2] in json.dumps(d), f"R1.6 the JSON has the current version {sys.argv[2]}")
|
||||
PY
|
||||
# R1.10: an item older in features.state shows as an update, and the run brings it up to date
|
||||
f=~/.local/state/steamify/features.state
|
||||
[[ -f "$f" ]] && sed -i 's/^cec=.*/cec=2.1.0/' "$f" && grep -q '^cec=2.1.0' "$f" && pass "R1.10 cec set to 2.1.0" || fail "R1.10 cannot set cec older in $f"
|
||||
out=$(menu 'q\n'); grep -q "HDMI-CEC.*(update)" <<< "$out" && pass "R1.10 the menu shows HDMI-CEC (update)" || fail "R1.10 no (update) on HDMI-CEC"
|
||||
out=$(menu '\ny\nm\nq\nn\n'); grep -q '^cec=2.1.0' "$f" && fail "R1.10 cec still 2.1.0 after the run" || pass "R1.10 cec brought up to date"
|
||||
@@ -0,0 +1,18 @@
|
||||
# N1: an NVIDIA PC (fake GPU): the menu hides the SteamOS conversion and its gamescope options, shows Gaming on NVIDIA, its
|
||||
# Big Picture option and Single user mode. Covers TESTPLAN N1.
|
||||
fake_nvidia
|
||||
out="$(sf --defaults --list)"
|
||||
ids="$(python3 -c 'import json,sys; d=json.load(sys.stdin); print(" ".join(sorted(i["id"] for i in d)))' <<< "$out" 2>/dev/null)"
|
||||
[[ -n "$ids" ]] && pass "N1 --defaults --list is valid JSON: $ids" || { fail "N1 --defaults --list is not JSON: $(echo "$out" | head -c 300)"; exit 0; }
|
||||
for i in nvidia bigpicture single; do [[ " $ids " == *" $i "* ]] && pass "N1 $i is offered" || fail "N1 $i is missing"; done
|
||||
for i in gaming boot glyphs; do [[ " $ids " == *" $i "* ]] && fail "N1 $i is offered on an NVIDIA PC" || pass "N1 $i is hidden"; done
|
||||
python3 -c '
|
||||
import json,sys
|
||||
d={i["id"]:i for i in json.load(sys.stdin)}
|
||||
ok = d["bigpicture"]["parent"]=="nvidia" and d["nvidia"]["on"] and d["bigpicture"]["on"] and d["single"]["on"]
|
||||
sys.exit(0 if ok else 1)' <<< "$out" && pass "N1 Big Picture is a sub-option of Gaming on NVIDIA, all three preselected" || fail "N1 parent or preselection wrong"
|
||||
st="$(sf --backend status)"
|
||||
python3 -c 'import json,sys; d=json.load(sys.stdin); ids=[i["id"] for i in d.get("items",d)]; sys.exit(1 if "gaming" in ids else 0)' <<< "$st" 2>/dev/null && pass "N1 --backend status (the app) has no conversion row" || fail "N1 --backend status lists the conversion"
|
||||
# Without a NVIDIA GPU nothing changes: the conversion is offered, Gaming on NVIDIA is not.
|
||||
out2="$(NVIDIA_DRM_DIR=/nonexistent sf --defaults --list)"
|
||||
python3 -c 'import json,sys; ids=[i["id"] for i in json.load(sys.stdin)]; sys.exit(0 if "gaming" in ids and "nvidia" not in ids and "bigpicture" not in ids else 1)' <<< "$out2" && pass "N1 no NVIDIA GPU: the conversion is offered, Gaming on NVIDIA is not" || fail "N1 no-NVIDIA menu wrong"
|
||||
@@ -0,0 +1,51 @@
|
||||
# N2-N5: Gaming on NVIDIA + Big Picture + Single user mode on a fake NVIDIA PC, for real: pacman installs Steam, the SDDM autologin,
|
||||
# the boot loader files and the initramfs, then a reboot into Plasma, then everything off again and a second reboot.
|
||||
# Covers TESTPLAN N2 (apply), N3 (after the reboot), N4 (turn off), N5 (after the second reboot).
|
||||
unit='~/.config/systemd/user/steamify-steam-autostart.service'
|
||||
g() { guest "$@"; }
|
||||
yes_() { [[ "$(g "$1 && echo YES || echo NO" | tail -n 1)" == YES ]]; } # a guest condition
|
||||
info "N2 applying nvidia,bigpicture,single (Steam, SDDM, initramfs: several minutes)..."
|
||||
rc="$(g 'fake_nvidia; sf --defaults --options nvidia,bigpicture,single > /tmp/n2.log; echo "rc=$?"' | tail -n 1)"
|
||||
[[ "$rc" == rc=0 ]] && pass "N2 apply exit 0" || { fail "N2 apply: $rc: $(g 'tail -n 15 /tmp/n2.log')"; return 0 2>/dev/null || exit 0; }
|
||||
yes_ 'pacman -Q steam >/dev/null 2>&1' && pass "N2 Steam is installed" || fail "N2 Steam missing"
|
||||
yes_ "grep -qx 'ExecStart=/usr/bin/steam -gamepadui' $unit" && pass "N2 the autostart unit starts Steam with -gamepadui" || fail "N2 unit: $(g "cat $unit")"
|
||||
yes_ 'systemctl --user is-enabled -q steamify-steam-autostart.service' && pass "N2 the unit is enabled" || fail "N2 unit not enabled"
|
||||
[[ "$(g 'kreadconfig6 --file ksmserverrc --group General --key loginMode' | tail -n 1)" == emptySession ]] && pass "N2 Plasma starts with an empty session" || fail "N2 loginMode"
|
||||
g 'is nvidia && is bigpicture && echo ON' | grep -qx ON && pass "N2 nvidia and bigpicture report on" || fail "N2 status functions"
|
||||
yes_ "grep -q 'nvidia-drm.modeset=1 nvidia-drm.fbdev=1' /etc/default/limine" && pass "N2 the kernel parameters are in /etc/default/limine" || fail "N2 limine defaults"
|
||||
yes_ "sudo grep -q 'nvidia-drm.fbdev=1' /boot/limine.conf" && pass "N2 and in the generated /boot/limine.conf" || fail "N2 limine.conf"
|
||||
yes_ 'test -f /etc/mkinitcpio.conf.d/90-steamify-nvidia.conf && test -x /usr/local/libexec/steamify-nvidia-initramfs && test -f /etc/pacman.d/hooks/85-steamify-nvidia-initramfs.hook' && pass "N2 early-load file, hook and its script installed" || fail "N2 initramfs files"
|
||||
yes_ 'sudo find /boot -name "initramfs*" -exec sh -c "lsinitcpio {} | grep -q nvidia" \; -print | grep -q .' && pass "N2 the NVIDIA modules are in the initramfs" || fail "N2 initramfs has no nvidia modules"
|
||||
yes_ 'systemctl is-enabled -q sddm.service' && pass "N2 SDDM is the login manager" || fail "N2 sddm not enabled"
|
||||
yes_ "grep -qx 'Session=plasma.desktop' /etc/sddm.conf.d/zzz-steamify-autologin.conf && grep -qx \"User=\$USER\" /etc/sddm.conf.d/zzz-steamify-autologin.conf" && pass "N2 SDDM autologin: the user into the Plasma session" || fail "N2 autologin file: $(g 'cat /etc/sddm.conf.d/zzz-steamify-autologin.conf')"
|
||||
yes_ 'is single' && pass "N2 single user mode is on" || fail "N2 single not on"
|
||||
g 'fake_nvidia; sf --defaults --options nvidia,bigpicture,single | tail -n 3' | grep -qiE 'nothing|already|exit: 0' && pass "N2 a second run is quiet" || info "N2 second run: $(g 'fake_nvidia; sf --defaults --options nvidia,bigpicture,single | tail -n 3')"
|
||||
|
||||
reboot_guest && pass "N3 the VM is back after the reboot" || { fail "N3 no ssh after the reboot"; return 0 2>/dev/null || exit 0; }
|
||||
up=""; for _ in $(seq 40); do vm_ssh "pgrep -u $VM_USER -x plasmashell" >/dev/null 2>&1 && { up=1; break; }; sleep 3; done
|
||||
[[ -n "$up" ]] && pass "N3 Plasma straight after the boot (SDDM autologin, no greeter)" || fail "N3 no plasmashell after the reboot"
|
||||
yes_ '! pgrep -x sddm-greeter >/dev/null' && pass "N3 no greeter running" || fail "N3 sddm-greeter is running"
|
||||
yes_ 'grep -q "nvidia-drm.modeset=1" /proc/cmdline && grep -q "nvidia-drm.fbdev=1" /proc/cmdline' && pass "N3 the kernel parameters are on the running command line" || fail "N3 cmdline: $(g 'cat /proc/cmdline')"
|
||||
yes_ 'systemctl is-active -q sddm.service' && pass "N3 sddm is the running display manager" || fail "N3 sddm not active"
|
||||
yes_ 'journalctl --user -b --no-pager -u steamify-steam-autostart.service | grep -qiE "Started|Starting"' && pass "N3 the Steam autostart unit was started at login" || fail "N3 unit never started: $(g 'systemctl --user status steamify-steam-autostart.service 2>&1 | head -n 8')"
|
||||
[[ "$(g 'kreadconfig6 --file ksmserverrc --group General --key loginMode' | tail -n 1)" == emptySession ]] && pass "N3 the empty session setting survived" || fail "N3 loginMode after the reboot"
|
||||
|
||||
info "N4 turning everything off (only notify stays)..."
|
||||
rc="$(g 'fake_nvidia; sf --defaults --options notify > /tmp/n4.log; echo "rc=$?"' | tail -n 1)"
|
||||
[[ "$rc" == rc=0 ]] && pass "N4 turn-off exit 0" || { fail "N4 turn-off: $rc: $(g 'tail -n 15 /tmp/n4.log')"; return 0 2>/dev/null || exit 0; }
|
||||
yes_ '! pacman -Q steam >/dev/null 2>&1' && pass "N4 Steam is removed again (Steamify installed it)" || fail "N4 Steam is still installed"
|
||||
yes_ "! test -e $unit" && pass "N4 the autostart unit is gone" || fail "N4 unit still there"
|
||||
[[ -z "$(g 'kreadconfig6 --file ksmserverrc --group General --key loginMode' | tail -n 1)" ]] && pass "N4 loginMode is back to not set" || fail "N4 loginMode: $(g 'kreadconfig6 --file ksmserverrc --group General --key loginMode')"
|
||||
yes_ '! grep -q nvidia-drm /etc/default/limine' && pass "N4 the kernel parameters are gone from /etc/default/limine" || fail "N4 limine defaults still have them"
|
||||
# Snapshot entries (snapper's, rootflags=.../.snapshots/N/snapshot) keep the command line of their time: a snapshot of the state with the
|
||||
# parameters is meant to boot that state. Only the current boot entries count.
|
||||
yes_ "! sudo grep 'cmdline:' /boot/limine.conf | grep -v '\.snapshots/' | grep -q nvidia-drm" && pass "N4 and from the current entries in /boot/limine.conf (snapshot entries keep their old command line)" || fail "N4 limine.conf still has them: $(g "sudo grep 'cmdline:' /boot/limine.conf | grep -v '\.snapshots/' | cut -c1-150" | tr '\n' '|')"
|
||||
yes_ '! test -e /etc/mkinitcpio.conf.d/90-steamify-nvidia.conf && ! test -e /usr/local/libexec/steamify-nvidia-initramfs && ! test -e /etc/pacman.d/hooks/85-steamify-nvidia-initramfs.hook' && pass "N4 early-load file, hook and script removed" || fail "N4 initramfs files remain"
|
||||
yes_ '! test -e /etc/sddm.conf.d/zzz-steamify-autologin.conf' && pass "N4 the SDDM autologin file is gone" || fail "N4 autologin file remains"
|
||||
yes_ 'systemctl is-enabled -q plasmalogin.service && ! systemctl is-enabled -q sddm.service' && pass "N4 plasma-login-manager is the login manager again" || fail "N4 login manager: $(g 'systemctl show -p Id --value display-manager')"
|
||||
yes_ '! is nvidia && ! is bigpicture && ! is single' && pass "N4 nvidia, bigpicture and single report off" || fail "N4 status still on"
|
||||
|
||||
reboot_guest && pass "N5 the VM is back after the second reboot" || { fail "N5 no ssh after the second reboot"; return 0 2>/dev/null || exit 0; }
|
||||
up=""; for _ in $(seq 40); do vm_ssh "pgrep -u $VM_USER -x plasmashell" >/dev/null 2>&1 && { up=1; break; }; sleep 3; done
|
||||
[[ -n "$up" ]] && pass "N5 Plasma comes up (the test autologin of plasma-login-manager)" || fail "N5 no plasmashell after the second reboot"
|
||||
yes_ '! grep -q "nvidia-drm" /proc/cmdline' && pass "N5 the kernel parameters are off the command line" || fail "N5 cmdline still has them"
|
||||
@@ -0,0 +1,8 @@
|
||||
# Sourced in front of a *.host.sh block (scripts/vmsuite.sh): the block runs on the host with the VM's ssh helpers.
|
||||
. "$repo/scripts/common.sh"
|
||||
info() { echo "INFO $*"; }
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }; skip() { echo "SKIP $*"; }
|
||||
# guest <cmd...>: a command in the guest with the Plasma session environment and Steamify's libs
|
||||
guest() { { cat "$repo/share/vmtest/prelude.sh"; echo "$*"; } | vm_ssh -o ConnectTimeout=6 -o LogLevel=ERROR bash -s 2>&1 | sed 's/\x1b\[[0-9;]*m//g'; }
|
||||
waitssh() { local _; for _ in $(seq 60); do vm_ssh -o ConnectTimeout=4 'uptime -p' >/dev/null 2>&1 && return 0; sleep 5; done; return 1; }
|
||||
reboot_guest() { vm_ssh 'sudo systemctl reboot' >/dev/null 2>&1; sleep 25; waitssh; }
|
||||
@@ -0,0 +1,38 @@
|
||||
# Sent to the guest in front of every block (scripts/vmsuite.sh): helpers and Steamify's libs.
|
||||
# ssh has no Plasma session: the theme item (Vapor layout) needs one (vmrun.sh does the same).
|
||||
export XDG_RUNTIME_DIR=/run/user/$(id -u) WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus
|
||||
info() { echo "INFO $*"; }
|
||||
pass() { echo "PASS $*"; }; fail() { echo "FAIL $*"; }; skip() { echo "SKIP $*"; }
|
||||
sudo mountpoint -q /mnt || sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt
|
||||
cd /mnt || exit 1
|
||||
export SCRIPT_DIR=/mnt # steamify.sh sets it; the libs alone read patches/ and services/ from it
|
||||
for lib in $(grep -oP '^for lib in \K.*(?=; do$)' steamify.sh); do
|
||||
# shellcheck source=/dev/null
|
||||
. "lib/$lib.sh" 2>/dev/null
|
||||
done
|
||||
sf() { bash /mnt/steamify.sh "$@" < /dev/null 2>&1 | sed 's/\x1b\[[0-9;]*m//g'; return "${PIPESTATUS[0]}"; }
|
||||
# is <item>: Steamify's own status function (what the menu ticks); isnt <item>
|
||||
is() { "${1}_status" 2>/dev/null; }
|
||||
expect_on() { local i; for i in "$@"; do is "$i" && pass "$i is on" || fail "$i is off"; done; }
|
||||
expect_off() { local i; for i in "$@"; do is "$i" && fail "$i is on" || pass "$i is off"; done; }
|
||||
ticks() { local i; for i in gaming nvidia bigpicture theme glyphs single launcher notify vram cec machine poweroff boot; do is "$i" && printf '%s ' "$i"; done; echo; }
|
||||
# fake_nvidia: an NVIDIA PC for the nvidia suite. A fake RTX 5080 in a fake DRM tree (NVIDIA_DRM_DIR is Steamify's test hook, exported
|
||||
# here: call it in every guest command that runs the wizard) and fake NVIDIA modules for every installed kernel (renamed copies of a
|
||||
# tiny module, then depmod), so `modinfo nvidia_drm` finds one and the initramfs build has modules to put in.
|
||||
fake_nvidia() {
|
||||
local d=/var/tmp/fake-drm k kd src ext m
|
||||
mkdir -p "$d/card1/device"
|
||||
echo 0x10de > "$d/card1/device/vendor"; echo 0x030000 > "$d/card1/device/class"; echo 0x2c02 > "$d/card1/device/device"
|
||||
for kd in /usr/lib/modules/*/; do
|
||||
k="$(basename "$kd")"
|
||||
[[ -d "$kd/kernel" ]] || continue
|
||||
src="$(find "$kd/kernel" -name 'dummy.ko*' | head -n 1)"
|
||||
[[ -n "$src" ]] || { echo "no dummy module for $k"; continue; }
|
||||
ext="${src##*dummy.ko}"
|
||||
for m in nvidia nvidia-modeset nvidia-uvm nvidia-drm; do
|
||||
[[ -e "$kd/kernel/drivers/video/$m.ko$ext" ]] || sudo install -Dm644 "$src" "$kd/kernel/drivers/video/$m.ko$ext"
|
||||
done
|
||||
sudo depmod -a "$k"
|
||||
done
|
||||
export NVIDIA_DRM_DIR="$d"
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
# R1.8 (and the matrix in the cachyos-vm-testing skill): switch items off and on and check the state each time.
|
||||
A=gaming,theme,glyphs,single,launcher,notify,cec,machine,poweroff
|
||||
apply() { info "apply: $1"; sf --defaults --options "$1" >/dev/null; [[ $? -eq 0 ]] && pass "options $1: exit 0" || fail "options $1: failed"; }
|
||||
apply "$A"
|
||||
expect_on gaming theme glyphs single launcher notify cec machine poweroff
|
||||
[[ "$(current_display_manager)" == sddm ]] && pass "baseline: SDDM (single user)" || fail "baseline display manager: $(current_display_manager)"
|
||||
# theme off
|
||||
apply "${A/theme,/}"
|
||||
expect_off theme; expect_on gaming single machine
|
||||
# theme on again
|
||||
apply "$A"; expect_on theme
|
||||
# single user off: plasmalogin
|
||||
apply "${A/single,/}"
|
||||
expect_off single; expect_on gaming
|
||||
[[ "$(current_display_manager)" == plasmalogin ]] && pass "single off: plasmalogin" || fail "single off: display manager $(current_display_manager)"
|
||||
# single user on: SDDM again
|
||||
apply "$A"; expect_on single
|
||||
[[ "$(current_display_manager)" == sddm ]] && pass "single on: back to SDDM" || fail "single on: display manager $(current_display_manager)"
|
||||
# Steam Machine support and power-off fix off: the driver goes, then comes back
|
||||
apply gaming,theme,glyphs,single,launcher,notify,cec
|
||||
expect_off machine poweroff
|
||||
[[ ! -f /etc/modules-load.d/steamify-fremont-poweroff.conf ]] && pass "machine off: the power-off modules-load entry is gone" || fail "machine off: modules-load entry still there"
|
||||
apply "$A"; expect_on machine poweroff
|
||||
[[ $(dkms status | grep -c ': installed') -ge $((3 * $(ls /usr/lib/modules | wc -w))) ]] && pass "machine on: DKMS installed for every kernel" || fail "machine on: DKMS $(dkms status | tr '\n' ';')"
|
||||
# HDMI-CEC off and on
|
||||
apply "${A/cec,/}"; expect_off cec
|
||||
apply "$A"; expect_on cec
|
||||
# conversion off: single user goes with it, plasmalogin's autologin is CachyOS's again
|
||||
apply launcher,notify
|
||||
expect_off gaming single theme
|
||||
[[ "$(current_display_manager)" == plasmalogin ]] && pass "conversion off: plasmalogin" || fail "conversion off: display manager $(current_display_manager)"
|
||||
sed -n '/^\[Autologin\]/,/^\[/p' /etc/plasmalogin.conf 2>/dev/null | grep -q 'Session=plasma' && pass "conversion off: plasmalogin autologin is Session=plasma" || skip "conversion off: no plasmalogin Autologin section (a VM from vminstall.sh has no /etc/plasmalogin.conf, see TESTPLAN Known gaps)"
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
# Tests lib/controllers.sh (extended controller support: xone, the Xbox wireless dongle, and xpadneo, Xbox Bluetooth) with a stub pacman
|
||||
# and a temp home. No root, VM or hardware needed: bash tests/controllers-test.sh
|
||||
# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo)
|
||||
REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; cd "$REPO" || exit 1
|
||||
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
|
||||
fail=0
|
||||
check() { if eval "$2"; then echo "ok $1"; else echo "FAIL $1"; fail=1; fi; }
|
||||
|
||||
SCRIPT_DIR="$PWD"; HOME="$T/home"; mkdir -p "$HOME"
|
||||
source lib/common.sh; source lib/state.sh; source lib/controllers.sh; source lib/menu.sh
|
||||
# Runs as the user, and refuses anything under the real system folders: a test must never touch them.
|
||||
sudo() { local a; for a in "$@"; do case "$a" in /etc/*|/usr/*|/boot/*|/var/*) echo "REFUSED sudo $*" >&2; return 1 ;; esac; done; "$@"; }
|
||||
info() { :; }; ok() { :; }; warn() { :; }; err() { echo "ERR $*"; }
|
||||
kwriteconfig6() { local f="" k="" v=""; while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --group) shift ;; *) v="$1" ;; esac; shift; done; mkdir -p "$(dirname "$f")"; sed -i "/^$k=/d" "$f" 2>/dev/null; printf '%s=%s\n' "$k" "$v" >> "$f"; }
|
||||
kreadconfig6() { local f="" k=""; while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --group|--default) shift ;; esac; shift; done; sed -n "s/^$k=//p" "$f" 2>/dev/null; }
|
||||
# A pretend package database.
|
||||
PKGS=""; PACMAN_LOG="$T/pacman.log"; : > "$PACMAN_LOG"; PACMAN_FAIL=0
|
||||
pacman() {
|
||||
case "$1" in
|
||||
-Q) local p; for p in "${@:2}"; do [[ " $PKGS " == *" $p "* ]] || return 1; done ;;
|
||||
-S) [[ $PACMAN_FAIL == 1 ]] && return 1; echo "S ${*:4}" >> "$PACMAN_LOG"; PKGS+=" ${*:4}" ;;
|
||||
-Rns) echo "R ${*:3}" >> "$PACMAN_LOG"; local p; for p in ${*:3}; do PKGS=" ${PKGS// $p / } "; done ;;
|
||||
esac
|
||||
}
|
||||
HEADERS=0; install_kernel_headers() { HEADERS=$((HEADERS + 1)); }
|
||||
|
||||
check "off to begin with" '! extended_controller_support_status'
|
||||
extended_controller_support_enable
|
||||
check "installs the repo's xpadneo-dkms, xone-dkms and the dongle firmware" 'pacman -Q xpadneo-dkms xone-dkms xone-dongle-firmware'
|
||||
check "kernel headers are made sure of first" '[[ $HEADERS == 1 ]]'
|
||||
check "what we installed is recorded" '[[ "$(state_get extended_controller_support installed_pkgs)" == "xpadneo-dkms xone-dkms xone-dongle-firmware" ]]'
|
||||
check "it is on" extended_controller_support_status
|
||||
n="$(wc -l < "$PACMAN_LOG")"; extended_controller_support_enable
|
||||
check "a second run installs nothing again" '[[ "$(wc -l < "$PACMAN_LOG")" == "$n" ]]'
|
||||
extended_controller_support_disable
|
||||
check "off: the three packages are removed again" '! pacman -Q xpadneo-dkms 2>/dev/null && ! pacman -Q xone-dkms 2>/dev/null && ! pacman -Q xone-dongle-firmware 2>/dev/null && [[ -z "$(state_get extended_controller_support installed_pkgs)" ]]'
|
||||
|
||||
# The AUR's xone-dkms-git conflicts with the repo's xone-dkms: it counts, and is kept.
|
||||
PKGS=" xone-dkms-git xone-dongle-firmware xpadneo-dkms"; : > "$PACMAN_LOG"
|
||||
check "xone-dkms-git counts as xone: already on" extended_controller_support_status
|
||||
extended_controller_support_enable; extended_controller_support_disable
|
||||
check "with xone-dkms-git: nothing installed and nothing removed" '[[ ! -s "$PACMAN_LOG" ]] && pacman -Q xone-dkms-git xpadneo-dkms xone-dongle-firmware'
|
||||
PKGS=" xone-dkms-git"
|
||||
extended_controller_support_enable
|
||||
check "xone-dkms-git present: only the missing ones are installed, never xone-dkms" 'grep -q "^S xpadneo-dkms xone-dongle-firmware$" "$PACMAN_LOG" && ! pacman -Q xone-dkms 2>/dev/null'
|
||||
extended_controller_support_disable
|
||||
check "and off removes only those two, xone-dkms-git stays" 'pacman -Q xone-dkms-git && ! pacman -Q xpadneo-dkms 2>/dev/null'
|
||||
|
||||
# A failed install is an error and records nothing.
|
||||
PKGS=""; PACMAN_FAIL=1
|
||||
check "a failed pacman is an error" '! extended_controller_support_enable >/dev/null'
|
||||
check "...and records nothing" '[[ -z "$(state_get extended_controller_support installed_pkgs)" ]]'
|
||||
PACMAN_FAIL=0
|
||||
|
||||
# Packages that were already there are never removed by turning it off.
|
||||
PKGS=" xpadneo-dkms xone-dkms xone-dongle-firmware"; : > "$PACMAN_LOG"
|
||||
extended_controller_support_enable; extended_controller_support_disable
|
||||
check "packages you had before stay" 'pacman -Q xpadneo-dkms xone-dkms xone-dongle-firmware && [[ ! -s "$PACMAN_LOG" ]]'
|
||||
|
||||
# The menu.
|
||||
check "offered on every PC" 'component_available extended_controller_support'
|
||||
check "not preselected: it builds kernel modules for every kernel" '[[ " ${NO_PRESELECT[*]} " == *" extended_controller_support "* ]]'
|
||||
check "a top-level item with a label" '[[ -z "${PARENT[extended_controller_support]:-}" && -n "${LABEL[extended_controller_support]}" ]]'
|
||||
|
||||
# The "new" badge: an opt-in option added after setup shows it, but is never ticked for you.
|
||||
component_selectable() { return 0; }
|
||||
declare -A CURRENT=([gaming]=1 [nvidia]=0 [extended_controller_support]=0 [vram]=0 [boot]=0)
|
||||
rm -f "$STATE_DIR/features.state"
|
||||
check "new opt-in option (conversion on, no record): the new badge" 'feature_new_optin extended_controller_support'
|
||||
check "...but it is not ticked for you (not a new default option)" '! feature_new extended_controller_support'
|
||||
check "...and the menu label says (new)" '[[ "$(menu_label extended_controller_support)" == *"(new)"* ]]'
|
||||
check "a new default option is still ticked for you, and is not the opt-in kind" 'feature_new vram && ! feature_new_optin vram'
|
||||
check "Boot into never gets the badge (it is a choice row)" '! feature_new_optin boot'
|
||||
state_set_features() { kwriteconfig6 --file "$STATE_DIR/features.state" --group State --key "$1" "$2"; }
|
||||
state_set_features extended_controller_support off
|
||||
check "once a run recorded it as off, the badge is gone" '! feature_new_optin extended_controller_support'
|
||||
rm -f "$STATE_DIR/features.state"
|
||||
CURRENT[gaming]=0; CURRENT[nvidia]=1
|
||||
check "on an NVIDIA PC (Gaming on NVIDIA on, no conversion) it shows too" 'feature_new_optin extended_controller_support'
|
||||
CURRENT[nvidia]=0
|
||||
check "with neither the conversion nor Gaming on NVIDIA on (a fresh PC) nothing is new" '! feature_new_optin extended_controller_support && ! feature_new vram'
|
||||
CURRENT[gaming]=1; CURRENT[extended_controller_support]=1
|
||||
check "already on: not new" '! feature_new_optin extended_controller_support'
|
||||
exit $fail
|
||||
@@ -0,0 +1,170 @@
|
||||
== pc first run
|
||||
wanted: gaming glyphs theme single launcher notify
|
||||
disable:
|
||||
enable: gaming glyphs theme single launcher notify
|
||||
== pc first run, untick gaming
|
||||
wanted: theme launcher notify
|
||||
disable:
|
||||
enable: theme launcher notify
|
||||
== pc single on
|
||||
wanted: gaming theme single launcher notify
|
||||
disable:
|
||||
enable: gaming theme single launcher notify
|
||||
== pc single then gaming off
|
||||
wanted: theme launcher notify
|
||||
disable:
|
||||
enable: theme launcher notify
|
||||
== pc boot desktop
|
||||
wanted: gaming boot glyphs theme single launcher notify
|
||||
disable:
|
||||
enable: gaming boot glyphs theme single launcher notify
|
||||
== pc boot desktop then gaming off
|
||||
wanted: theme launcher notify
|
||||
disable:
|
||||
enable: theme launcher notify
|
||||
== pc silent only
|
||||
wanted: gaming silent glyphs theme single launcher notify
|
||||
disable:
|
||||
enable: gaming silent glyphs theme single launcher notify
|
||||
== pc all on, theme off
|
||||
wanted: gaming boot silent glyphs single launcher notify
|
||||
disable: theme
|
||||
enable: gaming silent notify
|
||||
== pc launcher off
|
||||
wanted:
|
||||
disable: launcher
|
||||
enable:
|
||||
== pc steamgame on without launcher
|
||||
wanted: theme launcher steamgame notify
|
||||
disable:
|
||||
enable: theme launcher steamgame notify
|
||||
== machine first run
|
||||
wanted: gaming glyphs theme single launcher steamgame notify vram cec machine poweroff
|
||||
disable:
|
||||
enable: gaming glyphs theme single launcher steamgame notify vram cec machine poweroff
|
||||
== machine silent hidden until boot desktop
|
||||
wanted: gaming silent glyphs theme single launcher notify vram machine
|
||||
disable:
|
||||
enable: gaming silent glyphs theme single launcher notify vram machine
|
||||
== machine boot desktop keeps silent
|
||||
wanted: gaming boot silent glyphs theme single launcher notify vram machine
|
||||
disable:
|
||||
enable: gaming boot silent glyphs theme single launcher notify vram machine
|
||||
== machine off
|
||||
wanted: gaming glyphs theme single launcher notify vram
|
||||
disable: poweroff machine
|
||||
enable: gaming glyphs theme single launcher notify vram
|
||||
== machine poweroff off
|
||||
wanted: gaming glyphs theme single launcher notify vram machine
|
||||
disable: poweroff
|
||||
enable: gaming glyphs theme single launcher notify vram machine
|
||||
== machine poweroff on brings machine
|
||||
wanted: gaming glyphs theme single launcher notify vram machine poweroff
|
||||
disable:
|
||||
enable: gaming glyphs theme single launcher notify vram machine poweroff
|
||||
== machine kpin removal
|
||||
wanted: gaming glyphs theme single launcher notify vram machine poweroff
|
||||
disable: kpin
|
||||
enable: gaming glyphs theme single launcher notify vram machine poweroff
|
||||
== nvidia first run
|
||||
wanted: nvidia bigpicture theme single launcher notify
|
||||
disable:
|
||||
enable: nvidia bigpicture theme single launcher notify
|
||||
== nvidia bigpicture off
|
||||
wanted: nvidia theme single launcher notify
|
||||
disable: bigpicture
|
||||
enable: nvidia theme single launcher notify
|
||||
== nvidia nvsilent on
|
||||
wanted: nvidia nvsilent theme single launcher notify
|
||||
disable: bigpicture
|
||||
enable: nvidia nvsilent theme single launcher notify
|
||||
== nvidia nvsilent then bigpicture
|
||||
wanted: nvidia bigpicture theme single launcher notify
|
||||
disable:
|
||||
enable: nvidia bigpicture theme single launcher notify
|
||||
== nvidia off
|
||||
wanted: theme single launcher notify
|
||||
disable: nvsilent bigpicture nvidia
|
||||
enable: theme single launcher notify
|
||||
== nvidia single
|
||||
wanted: nvidia bigpicture theme launcher notify
|
||||
disable:
|
||||
enable: nvidia bigpicture theme launcher notify
|
||||
== nvidia gaming already on
|
||||
wanted: nvidia theme launcher notify
|
||||
disable: gaming
|
||||
enable: nvidia theme launcher notify
|
||||
== outdated features
|
||||
wanted: gaming glyphs theme single launcher notify
|
||||
disable:
|
||||
enable: gaming glyphs single launcher notify
|
||||
== reapply (a)
|
||||
wanted: gaming glyphs theme single launcher notify
|
||||
disable:
|
||||
enable: gaming glyphs theme single launcher notify
|
||||
== backend_apply single
|
||||
{"event":"plan","disable":[],"enable":["gaming","single"]}
|
||||
== backend_apply boot
|
||||
{"event":"plan","disable":[],"enable":[]}
|
||||
== backend_apply poweroff
|
||||
{"event":"plan","disable":[],"enable":["machine","poweroff"]}
|
||||
== backend_apply silent
|
||||
{"event":"plan","disable":[],"enable":["gaming","silent"]}
|
||||
== backend_apply kpin
|
||||
{"event":"plan","disable":[],"enable":[]}
|
||||
== backend_apply gaming boot
|
||||
{"event":"plan","disable":[],"enable":["gaming","boot"]}
|
||||
== backend_apply launcher steamgame
|
||||
{"event":"plan","disable":[],"enable":["launcher","steamgame"]}
|
||||
== backend_apply nvidia bigpicture
|
||||
{"event":"plan","disable":[],"enable":["nvidia","bigpicture"]}
|
||||
== backend_apply nvidia nvsilent
|
||||
{"event":"plan","disable":[],"enable":["nvidia","nvsilent"]}
|
||||
== backend_apply nvidia bigpicture nvsilent
|
||||
{"event":"plan","disable":[],"enable":["nvidia","bigpicture"]}
|
||||
== backend_apply nvidia single
|
||||
{"event":"plan","disable":[],"enable":["single"]}
|
||||
== backend_apply nvidia boot
|
||||
{"event":"plan","disable":[],"enable":[]}
|
||||
== defaults_list []
|
||||
{"hideWhen":"","hint":"boot into gaming mode, Steam on the desktop","id":"gaming","kind":"toggle","label":"SteamOS conversion","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"","id":"boot","kind":"choice","label":"Boot into the desktop instead of gaming mode","needs":"","on":false,"parent":"gaming","selectable":true}
|
||||
{"hideWhen":"","hint":"in the tray at login on the desktop, no window","id":"silent","kind":"toggle","label":"Start Steam silently in desktop mode","needs":"","on":false,"parent":"gaming","selectable":true}
|
||||
{"hideWhen":"","hint":"Deck button icons in gaming mode","id":"glyphs","kind":"toggle","label":"Install Steam Deck/Machine icons","needs":"","on":true,"parent":"gaming","selectable":true}
|
||||
{"hideWhen":"","hint":"Vapor look (cachyos-vapor)","id":"theme","kind":"toggle","label":"Install SteamOS theme","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"no password, lock screen or log out (SDDM)","id":"single","kind":"toggle","label":"Single user mode","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"the app on the desktop, Steamify Terminal in the launcher","id":"launcher","kind":"toggle","label":"Steamify shortcut","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"a notification when there's a new Steamify, never updates by itself","id":"notify","kind":"toggle","label":"Update notifications","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"Xbox wireless dongle (xone), Xbox controllers over Bluetooth (xpadneo)","id":"extended_controller_support","kind":"toggle","label":"Extended controller support","needs":"","on":false,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"use Steam with the TV remote, TV on/off with the PC (experimental)","id":"cec","kind":"toggle","label":"HDMI-CEC","needs":"","on":false,"parent":"","selectable":true}
|
||||
== backend_status []
|
||||
{"bios":null,"firstRun":true,"items":[{"hint":"boot into gaming mode, Steam on the desktop","id":"gaming","kind":"toggle","label":"SteamOS conversion","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"","id":"boot","kind":"choice","label":"Boot into the desktop instead of gaming mode","needs":"","new":false,"on":false,"parent":"gaming","selectable":true,"update":false,"wanted":false},{"hint":"in the tray at login on the desktop, no window","id":"silent","kind":"toggle","label":"Start Steam silently in desktop mode","needs":"","new":false,"on":false,"parent":"gaming","selectable":true,"update":false,"wanted":false},{"hint":"Deck button icons in gaming mode","id":"glyphs","kind":"toggle","label":"Install Steam Deck/Machine icons","needs":"","new":false,"on":false,"parent":"gaming","selectable":true,"update":false,"wanted":true},{"hint":"Vapor look (cachyos-vapor)","id":"theme","kind":"toggle","label":"Install SteamOS theme","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"no password, lock screen or log out (SDDM)","id":"single","kind":"toggle","label":"Single user mode","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"the app on the desktop, Steamify Terminal in the launcher","id":"launcher","kind":"toggle","label":"Steamify shortcut","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"a notification when there's a new Steamify, never updates by itself","id":"notify","kind":"toggle","label":"Update notifications","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"Xbox wireless dongle (xone), Xbox controllers over Bluetooth (xpadneo)","id":"extended_controller_support","kind":"toggle","label":"Extended controller support","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":false},{"hint":"use Steam with the TV remote, TV on/off with the PC (experimental)","id":"cec","kind":"toggle","label":"HDMI-CEC","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":false}],"pinnedKernel":"7.1.6-1","steamMachine":false,"version":"0.0.0"}
|
||||
== defaults_list [fremont steam vram]
|
||||
{"hideWhen":"","hint":"boot into gaming mode, Steam on the desktop","id":"gaming","kind":"toggle","label":"SteamOS conversion","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"","id":"boot","kind":"choice","label":"Boot into the desktop instead of gaming mode","needs":"","on":false,"parent":"gaming","selectable":true}
|
||||
{"hideWhen":"","hint":"in the tray at login on the desktop, no window","id":"silent","kind":"toggle","label":"Start Steam silently in desktop mode","needs":"","on":false,"parent":"gaming","selectable":true}
|
||||
{"hideWhen":"","hint":"Deck button icons in gaming mode","id":"glyphs","kind":"toggle","label":"Install Steam Deck/Machine icons","needs":"","on":true,"parent":"gaming","selectable":true}
|
||||
{"hideWhen":"","hint":"Vapor look (cachyos-vapor)","id":"theme","kind":"toggle","label":"Install SteamOS theme","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"no password, lock screen or log out (SDDM)","id":"single","kind":"toggle","label":"Single user mode","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"the app on the desktop, Steamify Terminal in the launcher","id":"launcher","kind":"toggle","label":"Steamify shortcut","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"Steamify in your Steam library, for the controller and gaming mode","id":"steamgame","kind":"toggle","label":"Add as non-Steam game","needs":"","on":true,"parent":"launcher","selectable":true}
|
||||
{"hideWhen":"","hint":"a notification when there's a new Steamify, never updates by itself","id":"notify","kind":"toggle","label":"Update notifications","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"Xbox wireless dongle (xone), Xbox controllers over Bluetooth (xpadneo)","id":"extended_controller_support","kind":"toggle","label":"Extended controller support","needs":"","on":false,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"the game in front keeps its VRAM, background apps make room","id":"vram","kind":"toggle","label":"VRAM booster","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"use Steam with the TV remote, TV on/off with the PC (experimental)","id":"cec","kind":"toggle","label":"HDMI-CEC","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"LED bar driver, hardware settings in Steam","id":"machine","kind":"toggle","label":"Steam Machine support","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"the Steam Machine stays off after shutting down","id":"poweroff","kind":"toggle","label":"Power-off fix","needs":"","on":true,"parent":"machine","selectable":true}
|
||||
== backend_status [fremont steam vram]
|
||||
{"bios":null,"firstRun":true,"items":[{"hint":"boot into gaming mode, Steam on the desktop","id":"gaming","kind":"toggle","label":"SteamOS conversion","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"","id":"boot","kind":"choice","label":"Boot into the desktop instead of gaming mode","needs":"","new":false,"on":false,"parent":"gaming","selectable":true,"update":false,"wanted":false},{"hint":"in the tray at login on the desktop, no window","id":"silent","kind":"toggle","label":"Start Steam silently in desktop mode","needs":"","new":false,"on":false,"parent":"gaming","selectable":true,"update":false,"wanted":false},{"hint":"Deck button icons in gaming mode","id":"glyphs","kind":"toggle","label":"Install Steam Deck/Machine icons","needs":"","new":false,"on":false,"parent":"gaming","selectable":true,"update":false,"wanted":true},{"hint":"Vapor look (cachyos-vapor)","id":"theme","kind":"toggle","label":"Install SteamOS theme","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"no password, lock screen or log out (SDDM)","id":"single","kind":"toggle","label":"Single user mode","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"the app on the desktop, Steamify Terminal in the launcher","id":"launcher","kind":"toggle","label":"Steamify shortcut","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"Steamify in your Steam library, for the controller and gaming mode","id":"steamgame","kind":"toggle","label":"Add as non-Steam game","needs":"","new":false,"on":false,"parent":"launcher","selectable":true,"update":false,"wanted":true},{"hint":"a notification when there's a new Steamify, never updates by itself","id":"notify","kind":"toggle","label":"Update notifications","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"Xbox wireless dongle (xone), Xbox controllers over Bluetooth (xpadneo)","id":"extended_controller_support","kind":"toggle","label":"Extended controller support","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":false},{"hint":"the game in front keeps its VRAM, background apps make room","id":"vram","kind":"toggle","label":"VRAM booster","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"use Steam with the TV remote, TV on/off with the PC (experimental)","id":"cec","kind":"toggle","label":"HDMI-CEC","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"LED bar driver, hardware settings in Steam","id":"machine","kind":"toggle","label":"Steam Machine support","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"the Steam Machine stays off after shutting down","id":"poweroff","kind":"toggle","label":"Power-off fix","needs":"","new":false,"on":false,"parent":"machine","selectable":true,"update":false,"wanted":true}],"pinnedKernel":"7.1.6-1","steamMachine":true,"version":"0.0.0"}
|
||||
== defaults_list [nvidia steam]
|
||||
{"hideWhen":"","hint":"Steam on the desktop, started at login","id":"nvidia","kind":"toggle","label":"Gaming on NVIDIA","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"the controller-friendly Steam (untick: Steam's normal window)","id":"bigpicture","kind":"toggle","label":"Steam starts in Big Picture","needs":"","on":true,"parent":"nvidia","selectable":true}
|
||||
{"hideWhen":"bigpicture","hint":"in the tray at login, no window (untick: Steam's normal window)","id":"nvsilent","kind":"toggle","label":"Start Steam silently","needs":"","on":false,"parent":"nvidia","selectable":true}
|
||||
{"hideWhen":"","hint":"Vapor look (cachyos-vapor)","id":"theme","kind":"toggle","label":"Install SteamOS theme","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"no password, lock screen or log out (SDDM)","id":"single","kind":"toggle","label":"Single user mode","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"the app on the desktop, Steamify Terminal in the launcher","id":"launcher","kind":"toggle","label":"Steamify shortcut","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"a notification when there's a new Steamify, never updates by itself","id":"notify","kind":"toggle","label":"Update notifications","needs":"","on":true,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"Xbox wireless dongle (xone), Xbox controllers over Bluetooth (xpadneo)","id":"extended_controller_support","kind":"toggle","label":"Extended controller support","needs":"","on":false,"parent":"","selectable":true}
|
||||
{"hideWhen":"","hint":"use Steam with the TV remote, TV on/off with the PC (experimental)","id":"cec","kind":"toggle","label":"HDMI-CEC","needs":"","on":false,"parent":"","selectable":true}
|
||||
== backend_status [nvidia steam]
|
||||
{"bios":null,"firstRun":true,"items":[{"hint":"Steam on the desktop, started at login","id":"nvidia","kind":"toggle","label":"Gaming on NVIDIA","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"the controller-friendly Steam (untick: Steam's normal window)","id":"bigpicture","kind":"toggle","label":"Steam starts in Big Picture","needs":"","new":false,"on":false,"parent":"nvidia","selectable":true,"update":false,"wanted":true},{"hint":"in the tray at login, no window (untick: Steam's normal window)","id":"nvsilent","kind":"toggle","label":"Start Steam silently","needs":"","new":false,"on":false,"parent":"nvidia","selectable":true,"update":false,"wanted":false},{"hint":"Vapor look (cachyos-vapor)","id":"theme","kind":"toggle","label":"Install SteamOS theme","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"no password, lock screen or log out (SDDM)","id":"single","kind":"toggle","label":"Single user mode","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"the app on the desktop, Steamify Terminal in the launcher","id":"launcher","kind":"toggle","label":"Steamify shortcut","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"a notification when there's a new Steamify, never updates by itself","id":"notify","kind":"toggle","label":"Update notifications","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":true},{"hint":"Xbox wireless dongle (xone), Xbox controllers over Bluetooth (xpadneo)","id":"extended_controller_support","kind":"toggle","label":"Extended controller support","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":false},{"hint":"use Steam with the TV remote, TV on/off with the PC (experimental)","id":"cec","kind":"toggle","label":"HDMI-CEC","needs":"","new":false,"on":false,"parent":"","selectable":true,"update":false,"wanted":false}],"pinnedKernel":"7.1.6-1","steamMachine":false,"version":"0.0.0"}
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
# Golden-output test for the menu rules (lib/menu.sh, lib/backend.sh): what
|
||||
# toggling does to WANTED, what plan_changes picks, and the JSON the app and
|
||||
# the installer read. Hardware and package state are stubbed, so no root, VM
|
||||
# or hardware is needed.
|
||||
# bash tests/menu-test.sh compare with menu-test.golden
|
||||
# bash tests/menu-test.sh --update rewrite the golden file (review the diff!)
|
||||
# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo)
|
||||
REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; HERE="$(cd "$(dirname "$0")" && pwd)"; cd "$REPO" || exit 1
|
||||
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
|
||||
|
||||
SCRIPT_DIR="$PWD"; HOME="$T/home"; mkdir -p "$HOME"
|
||||
VERSION=0.0.0
|
||||
for lib in $(grep -oP '^for lib in \K.*(?=; do$)' steamify.sh); do
|
||||
source "lib/$lib.sh"
|
||||
done
|
||||
info() { :; }; ok() { :; }; warn() { :; }; err() { :; }
|
||||
# From the entry point.
|
||||
RESTART_FOR_LOGIN=false; restart_needed() { return 1; }
|
||||
|
||||
# Hardware profile and what is "on", set per scenario.
|
||||
HW=""; ON=""
|
||||
has() { [[ " $HW " == *" $1 "* ]]; }
|
||||
nvidia_present() { has nvidia; }
|
||||
nvidia_available() { has nvidia; }
|
||||
bigpicture_available() { has nvidia; }
|
||||
machine_available() { has fremont; }
|
||||
detect_valve_fremont() { has fremont; }
|
||||
vram_available() { has vram; }
|
||||
vram_selectable() { return 0; }
|
||||
steamgame_available() { has steam; }
|
||||
kpin_available() { has fremont && [[ " $ON " == *" kpin "* ]]; }
|
||||
hdmi_available() { return 1; }
|
||||
bios_available() { return 1; }
|
||||
bios_selectable() { return 1; }
|
||||
cec_repair() { return 1; }
|
||||
launcher_repair() { return 1; }
|
||||
bios_lookup_newest() { :; }
|
||||
kernel_overview() { :; }
|
||||
os_version_refresh() { :; }
|
||||
for c in "${COMPONENTS[@]}"; do
|
||||
eval "${c}_status() { [[ \" \$ON \" == *\" $c \"* ]]; }"
|
||||
done
|
||||
|
||||
show() {
|
||||
# show <label>: WANTED as the ticked ids, then the plan.
|
||||
local c w=""
|
||||
for c in "${COMPONENTS[@]}"; do [[ "${WANTED[$c]:-0}" == 1 ]] && w+=" $c"; done
|
||||
echo "$1"
|
||||
echo " wanted:$w"
|
||||
plan_changes
|
||||
echo " disable: ${TO_DISABLE[*]:-}"
|
||||
echo " enable: ${TO_ENABLE[*]:-}"
|
||||
}
|
||||
|
||||
scenario() {
|
||||
# scenario <name> <hardware> <on> [toggle...]
|
||||
local name="$1"; HW="$2"; ON="$3"; shift 3
|
||||
rm -rf "$STATE_DIR"; REAPPLY=false
|
||||
detect_components
|
||||
local t
|
||||
for t in "$@"; do toggle_component "$t"; done
|
||||
show "== $name"
|
||||
}
|
||||
|
||||
run() {
|
||||
scenario "pc first run" "" ""
|
||||
scenario "pc first run, untick gaming" "" "" gaming
|
||||
scenario "pc single on" "" "" gaming single
|
||||
scenario "pc single then gaming off" "" "" single gaming
|
||||
scenario "pc boot desktop" "" "" boot
|
||||
scenario "pc boot desktop then gaming off" "" "" boot gaming
|
||||
scenario "pc silent only" "" "gaming" silent
|
||||
scenario "pc all on, theme off" "" "gaming boot silent theme glyphs single launcher steamgame notify" theme
|
||||
scenario "pc launcher off" "" "launcher steamgame" launcher
|
||||
scenario "pc steamgame on without launcher" "steam" "" gaming launcher steamgame
|
||||
scenario "machine first run" "fremont steam vram" ""
|
||||
scenario "machine silent hidden until boot desktop" "fremont steam vram" "gaming silent"
|
||||
scenario "machine boot desktop keeps silent" "fremont steam vram" "gaming silent" boot
|
||||
scenario "machine off" "fremont steam vram" "gaming machine poweroff" machine
|
||||
scenario "machine poweroff off" "fremont steam vram" "gaming machine poweroff" poweroff
|
||||
scenario "machine poweroff on brings machine" "fremont steam vram" "gaming" poweroff
|
||||
scenario "machine kpin removal" "fremont steam vram" "gaming machine poweroff kpin"
|
||||
scenario "nvidia first run" "nvidia steam" ""
|
||||
scenario "nvidia bigpicture off" "nvidia steam" "nvidia bigpicture" bigpicture
|
||||
scenario "nvidia nvsilent on" "nvidia steam" "nvidia bigpicture" nvsilent
|
||||
scenario "nvidia nvsilent then bigpicture" "nvidia steam" "nvidia" nvsilent bigpicture
|
||||
scenario "nvidia off" "nvidia steam" "nvidia bigpicture nvsilent" nvidia
|
||||
scenario "nvidia single" "nvidia steam" "" single
|
||||
scenario "nvidia gaming already on" "nvidia steam" "gaming" gaming
|
||||
|
||||
# Feature versions: an old record is ticked and re-applied.
|
||||
HW=""; ON="gaming theme"; rm -rf "$STATE_DIR"; REAPPLY=false
|
||||
state_set features gaming 2.1.0; state_set features theme 2.1.0
|
||||
detect_components; show "== outdated features"
|
||||
REAPPLY=true; ON="gaming theme launcher"; detect_components; show "== reapply (a)"
|
||||
REAPPLY=false
|
||||
|
||||
# The app's own rules (backend_apply's normalisation) go through the same plan.
|
||||
HW="fremont steam vram"; ON=""; rm -rf "$STATE_DIR"
|
||||
detect_components
|
||||
local sel
|
||||
for sel in "single" "boot" "poweroff" "silent" "kpin" "gaming boot" "launcher steamgame"; do
|
||||
echo "== backend_apply $sel"
|
||||
backend_sudo() { return 0; }
|
||||
backend_run_component() { return 0; }
|
||||
# shellcheck disable=SC2086
|
||||
backend_apply $sel | jq -c 'select(.event == "plan")'
|
||||
done
|
||||
HW="nvidia steam"; ON=""; detect_components
|
||||
for sel in "bigpicture" "nvsilent" "bigpicture nvsilent" "single" "boot"; do
|
||||
echo "== backend_apply nvidia $sel"
|
||||
# shellcheck disable=SC2086
|
||||
backend_apply $sel | jq -c 'select(.event == "plan")'
|
||||
done
|
||||
|
||||
# JSON for the app and the installer page.
|
||||
local hw
|
||||
for hw in "" "fremont steam vram" "nvidia steam"; do
|
||||
HW="$hw"; ON=""; rm -rf "$STATE_DIR"; detect_components
|
||||
echo "== defaults_list [$hw]"
|
||||
defaults_list | jq -S -c '.[]'
|
||||
echo "== backend_status [$hw]"
|
||||
backend_status | jq -S -c 'del(.kernel, .cecDevices, .leds)'
|
||||
done
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == --update ]]; then
|
||||
run > "$HERE/menu-test.golden" 2>&1
|
||||
echo "wrote $HERE/menu-test.golden ($(wc -l < "$HERE/menu-test.golden") lines)"
|
||||
elif diff -u "$HERE/menu-test.golden" <(run 2>&1); then
|
||||
echo "ok menu rules, plan and JSON match the golden file"
|
||||
else
|
||||
echo "FAIL menu rules changed (diff above; --update if intended)"; exit 1
|
||||
fi
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
# Test of the NVIDIA fix on a real PC with an NVIDIA GPU (the unit test fakes
|
||||
# one: tests/nvidia-test.sh). Run from this repo (REPO points at the Steamify checkout):
|
||||
# bash tests/nvidia-hardware-test.sh check read-only: what is detected, what the live kernel uses, what apply would change
|
||||
# bash tests/nvidia-hardware-test.sh apply really applies the fix (sudo), then reboot and run check again
|
||||
# bash tests/nvidia-hardware-test.sh visual after trying gaming mode: was the picture clean? (asks you)
|
||||
# Every run appends to ~/steamify-nvidia-report.txt: paste that file when asking for help.
|
||||
# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo)
|
||||
REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; cd "$REPO" || exit 1
|
||||
# patch_file (lib/common.sh) reads the hook script from $SCRIPT_DIR/patches.
|
||||
SCRIPT_DIR="$PWD"
|
||||
REPORT="${NVIDIA_REPORT:-$HOME/steamify-nvidia-report.txt}"
|
||||
source lib/common.sh; source lib/hdmi-refresh.sh; source lib/vram-booster.sh; source lib/nvidia.sh
|
||||
mode="${1:-check}"
|
||||
fail=0
|
||||
say() { echo "$*" | tee -a "$REPORT"; }
|
||||
pass() { say "PASS $*"; }
|
||||
bad() { say "FAIL $*"; fail=1; }
|
||||
note() { say " $*"; }
|
||||
root_cat() { cat "$1" 2>/dev/null || sudo cat "$1" 2>/dev/null || echo "(unreadable)"; }
|
||||
|
||||
say ""; say "===== $(date '+%F %T') mode=$mode kernel=$(uname -r)"
|
||||
|
||||
report_system() {
|
||||
say "-- GPU and software"
|
||||
lspci -nn 2>/dev/null | grep -iE 'vga|3d|display' | while read -r l; do note "$l"; done
|
||||
command -v nvidia-smi >/dev/null && note "driver: $(nvidia-smi --query-gpu=name,driver_version --format=csv,noheader 2>&1 | head -n 1)"
|
||||
for p in nvidia-open nvidia-utils nvidia-dkms gamescope gamescope-session-cachyos linux-cachyos-nvidia-open; do
|
||||
v="$(pacman -Q "$p" 2>/dev/null)" && note "$v"
|
||||
done
|
||||
note "boot loader file: $(nvidia_boot_file || true)"
|
||||
note "cmdline: $(cat /proc/cmdline)"
|
||||
}
|
||||
|
||||
check_state() {
|
||||
say "-- Steamify's view"
|
||||
if nvidia_present; then pass "a supported NVIDIA GPU (RTX 20 series or newer) with its driver is detected"
|
||||
elif nvidia_legacy_gpu; then bad "the NVIDIA GPU is older than RTX 20 (on chwd's legacy lists in $VRAM_CHWD_IDS): the fix is not applied"
|
||||
else bad "no NVIDIA GPU with its driver detected: the fix does nothing here"; fi
|
||||
for c in /sys/class/drm/card[0-9]*/device; do
|
||||
[[ "$(cat "$c/vendor" 2>/dev/null)" == 0x10de ]] && note "NVIDIA PCI device id $(cat "$c/device") $(vram_nvidia_legacy_id "$(cat "$c/device")" && echo '(on a chwd legacy list)' || echo '(not on a legacy list: RTX 20 or newer)')"
|
||||
done
|
||||
ls "$VRAM_CHWD_IDS"/nvidia-*.ids >/dev/null 2>&1 || note "no chwd legacy lists found: every NVIDIA card counts as supported"
|
||||
local p
|
||||
for p in $NVIDIA_PARAMS; do
|
||||
if grep -qw -- "$p" /proc/cmdline; then pass "$p is on the running kernel's command line"
|
||||
elif nvidia_param_set "$p"; then bad "$p is in the boot loader's file but not booted yet: reboot"
|
||||
else bad "$p is missing (run: bash tests/nvidia-hardware-test.sh apply)"; fi
|
||||
done
|
||||
say "-- the live NVIDIA driver"
|
||||
local m
|
||||
for m in modeset fbdev; do
|
||||
v="$(root_cat /sys/module/nvidia_drm/parameters/$m)"
|
||||
if [[ "$v" == Y ]]; then pass "nvidia_drm $m = Y"; else bad "nvidia_drm $m = $v (want Y)"; fi
|
||||
done
|
||||
lsmod | grep -q '^nvidia_drm' && pass "nvidia_drm is loaded" || bad "nvidia_drm is not loaded"
|
||||
local c found=0
|
||||
for c in /sys/class/drm/card[0-9]*/device; do
|
||||
[[ "$(cat "$c/vendor" 2>/dev/null)" == 0x10de ]] || continue
|
||||
found=1; note "NVIDIA card: ${c%/device} driver=$(basename "$(readlink -f "$c/driver" 2>/dev/null)")"
|
||||
for s in "${c%/device}"/card*-*/status; do [[ -f "$s" ]] && note " $(basename "$(dirname "$s")"): $(cat "$s")"; done
|
||||
done
|
||||
[[ $found == 1 ]] || bad "no NVIDIA card in /sys/class/drm"
|
||||
say "-- initramfs"
|
||||
if nvidia_modules_everywhere; then note "every installed kernel has nvidia_drm: the early-load drop-in is used"
|
||||
else note "not every installed kernel has nvidia_drm: the early-load drop-in is skipped (parameters still apply)"; fi
|
||||
nvidia_initramfs_ok && note "NVIDIA modules are in the initramfs config" || note "NVIDIA modules are not in the initramfs config"
|
||||
say "-- what apply would change"
|
||||
if nvidia_params_missing; then note "adds $NVIDIA_PARAMS to $(nvidia_boot_file)"; else note "kernel parameters: nothing to change"; fi
|
||||
if ! nvidia_initramfs_ok && nvidia_modules_everywhere; then note "writes $NVIDIA_INITRAMFS_CONF"; fi
|
||||
}
|
||||
|
||||
case "$mode" in
|
||||
check)
|
||||
report_system; check_state ;;
|
||||
apply)
|
||||
report_system
|
||||
say "-- applying"
|
||||
if nvidia_enable 2>&1 | tee -a "$REPORT"; [[ ${PIPESTATUS[0]} == 0 ]]; then pass "nvidia_enable finished"
|
||||
else bad "nvidia_enable failed (output above)"; fi
|
||||
say "Now reboot, then run: bash tests/nvidia-hardware-test.sh check" ;;
|
||||
visual)
|
||||
report_system
|
||||
say "-- try gaming mode now (log in to it or reboot into it), look at the screen, then answer"
|
||||
read -rp "Is the gaming mode picture clean (no corruption, no flicker)? [y/n/partly] " a
|
||||
read -rp "What did you see (one line, e.g. 'garbled colours at start, fine after login')? " what
|
||||
say "visual result: $a: $what"
|
||||
[[ "$a" == y ]] && pass "picture clean" || bad "picture not clean ($a)"
|
||||
say "-- gamescope and NVIDIA lines from this boot's log"
|
||||
{ journalctl -b --no-pager 2>/dev/null || sudo journalctl -b --no-pager 2>/dev/null; } |
|
||||
grep -iE 'gamescope|nvidia|drm|nvrm|wlserver|vulkan' | tail -n 80 | tee -a "$REPORT" | tail -n 5
|
||||
note "(more in $REPORT)" ;;
|
||||
*) echo "usage: $0 check|apply|visual"; exit 2 ;;
|
||||
esac
|
||||
say ""; say "Report: $REPORT"
|
||||
exit $fail
|
||||
Executable
+207
@@ -0,0 +1,207 @@
|
||||
#!/bin/bash
|
||||
# Tests lib/nvidia.sh ("Gaming on NVIDIA" and its Big Picture option) and the menu rules around it, against a fake RTX 5080
|
||||
# (PCI 10de:2c02) beside an Intel iGPU, a stub modinfo, pacman and systemctl, and a temp home. No root, VM or NVIDIA
|
||||
# hardware needed: bash tests/nvidia-test.sh
|
||||
# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo)
|
||||
REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"; cd "$REPO" || exit 1
|
||||
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
|
||||
fail=0
|
||||
check() { if eval "$2"; then echo "ok $1"; else echo "FAIL $1"; fail=1; fi; }
|
||||
|
||||
SCRIPT_DIR="$PWD"; HOME="$T/home"; mkdir -p "$HOME"
|
||||
source lib/common.sh; source lib/state.sh; source lib/login-manager.sh; source lib/single-user.sh; source lib/hdmi-refresh.sh; source lib/vram-booster.sh; source lib/nvidia.sh; source lib/menu.sh
|
||||
# Runs as the user, and refuses anything under the real system folders: a test must never touch them.
|
||||
sudo() { local a; for a in "$@"; do case "$a" in /etc/*|/usr/*|/boot/*|/var/*) echo "REFUSED sudo $*" >&2; return 1 ;; esac; done; "$@"; }
|
||||
info() { :; }; ok() { :; }; warn() { :; }; err() { echo "ERR $*"; }
|
||||
# KDE's config tools on plain files under the temp home (a relative file name is relative to ~/.config, as in KDE).
|
||||
kwriteconfig6() {
|
||||
local f="" k="" v="" del=0
|
||||
while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --group) shift ;; --delete) del=1 ;; *) v="$1" ;; esac; shift; done
|
||||
[[ "$f" == /* ]] || f="$HOME/.config/$f"; mkdir -p "$(dirname "$f")"
|
||||
if [[ $del == 1 ]]; then sed -i "/^$k=/d" "$f" 2>/dev/null; else sed -i "/^$k=/d" "$f" 2>/dev/null; printf '%s=%s\n' "$k" "$v" >> "$f"; fi
|
||||
}
|
||||
kreadconfig6() {
|
||||
local f="" k="" d="" r
|
||||
while [[ $# -gt 0 ]]; do case "$1" in --file) f="$2"; shift ;; --key) k="$2"; shift ;; --default) d="$2"; shift ;; --group) shift ;; esac; shift; done
|
||||
[[ "$f" == /* ]] || f="$HOME/.config/$f"
|
||||
r="$(sed -n "s/^$k=//p" "$f" 2>/dev/null)"; printf '%s\n' "${r:-$d}"
|
||||
}
|
||||
# A pretend package database and systemd: what the tests assert on.
|
||||
PKGS=""; PACMAN_LOG="$T/pacman.log"; : > "$PACMAN_LOG"; ENABLED=0
|
||||
pacman() {
|
||||
case "$1" in
|
||||
-Q) [[ " $PKGS " == *" $2 "* ]] ;;
|
||||
-S) echo "S $*" >> "$PACMAN_LOG"; PKGS+=" ${*: -1}" ;;
|
||||
-Rns) echo "R $*" >> "$PACMAN_LOG"; PKGS=" ${PKGS//${*: -1}/} " ;;
|
||||
esac
|
||||
}
|
||||
user_systemctl() {
|
||||
case "$1" in
|
||||
enable) ENABLED=1 ;;
|
||||
disable) ENABLED=0 ;;
|
||||
is-enabled) [[ $ENABLED == 1 ]] ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
FAKE_DRIVER=1; modinfo() { [[ "$FAKE_DRIVER" == 1 ]]; }
|
||||
GAMING_ON=0; gaming_status() { [[ "$GAMING_ON" == 1 ]]; }
|
||||
UNIT="$HOME/.config/systemd/user/$NVIDIA_UNIT"
|
||||
|
||||
mkdir -p "$T/drm/card0/device" "$T/drm/card1/device"
|
||||
echo 0x8086 > "$T/drm/card0/device/vendor"; echo 0x030000 > "$T/drm/card0/device/class"
|
||||
echo 0x10de > "$T/drm/card1/device/vendor"; echo 0x030000 > "$T/drm/card1/device/class"
|
||||
echo 0x2c02 > "$T/drm/card1/device/device"
|
||||
NVIDIA_DRM_DIR="$T/drm"
|
||||
backup_file() { [[ -f "$1.bak" ]] || cp "$1" "$1.bak"; }
|
||||
REBUILDS=0; nvidia_rebuild_boot() { REBUILDS=$((REBUILDS + 1)); }
|
||||
: > "$T/cmdline"; NVIDIA_CMDLINE="$T/cmdline" # the running kernel's command line: without the parameters
|
||||
NVIDIA_INITRAMFS_CONF="$T/90-steamify-nvidia.conf"
|
||||
NVIDIA_SCRIPT="$T/libexec/steamify-nvidia-initramfs"; NVIDIA_HOOK="$T/hooks/85-steamify-nvidia-initramfs.hook"
|
||||
mkdir -p "$T/mods/6.1.0-cachyos/kernel"; : > "$T/mods/6.1.0-cachyos/kernel/nvidia-drm.ko.zst"; : > "$T/mods/6.1.0-cachyos/pkgbase"
|
||||
NVIDIA_MODULES_DIR="$T/mods"
|
||||
NVIDIA_LIMINE_CONF="$T/none/limine.conf"; NVIDIA_SDBOOT_DIR="$T/none/entries"; NVIDIA_GRUB_CFG="$T/none/grub.cfg" # not generated here: not checked
|
||||
mkdir -p "$T/chwd"; VRAM_CHWD_IDS="$T/chwd"
|
||||
# The component's kernel step edits this boot loader file (systemd-boot's) in the tests, never a real one.
|
||||
printf 'LINUX_OPTIONS="quiet"\n' > "$T/sdboot-manage.conf"; nvidia_boot_file() { echo "$T/sdboot-manage.conf"; }
|
||||
|
||||
# --- detection
|
||||
check "finds the NVIDIA card as card1 beside an iGPU" nvidia_present
|
||||
FAKE_DRIVER=0; check "no nvidia_drm module (nouveau): not an NVIDIA PC" '! nvidia_present'
|
||||
FAKE_DRIVER=1; NVIDIA_DRM_DIR="$T/none"; check "no NVIDIA GPU: not an NVIDIA PC" '! nvidia_present'
|
||||
NVIDIA_DRM_DIR="$T/drm"
|
||||
|
||||
# --- which items the menu shows
|
||||
check "NVIDIA PC: the SteamOS conversion and its gamescope options are hidden" '! component_available gaming && ! component_available boot && ! component_available glyphs'
|
||||
check "NVIDIA PC: Gaming on NVIDIA and Big Picture are shown" 'component_available nvidia && component_available bigpicture'
|
||||
GAMING_ON=1
|
||||
check "NVIDIA PC with the conversion already on: it is shown, so it can be turned off" 'component_available gaming && component_available boot'
|
||||
check "...and Gaming on NVIDIA is hidden (both would start Steam)" '! component_available nvidia && ! component_available bigpicture'
|
||||
GAMING_ON=0; NVIDIA_DRM_DIR="$T/none"
|
||||
check "no NVIDIA: the conversion is shown as before" 'component_available gaming && component_available boot'
|
||||
check "...and Gaming on NVIDIA is hidden" '! component_available nvidia && ! component_available bigpicture'
|
||||
NVIDIA_DRM_DIR="$T/drm"
|
||||
|
||||
# --- "Add as non-Steam game" is for gaming mode's controller: not offered on an NVIDIA PC (unless already on)
|
||||
steamgame_available() { return 0; }; STEAMGAME_ON=0; steamgame_status() { [[ "$STEAMGAME_ON" == 1 ]]; }
|
||||
check "NVIDIA PC: Add as non-Steam game is hidden" '! component_available steamgame'
|
||||
STEAMGAME_ON=1; check "...unless it is already on (so it can be turned off)" 'component_available steamgame'; STEAMGAME_ON=0
|
||||
NVIDIA_DRM_DIR="$T/none"; check "no NVIDIA: it is offered as before" 'component_available steamgame'
|
||||
steamgame_available() { return 1; }; check "no NVIDIA, no Steam account yet: still not offered" '! component_available steamgame'
|
||||
steamgame_available() { return 0; }; NVIDIA_DRM_DIR="$T/drm"
|
||||
|
||||
# --- turning it on
|
||||
check "off to begin with" '! nvidia_status && ! bigpicture_status'
|
||||
nvidia_enable
|
||||
check "Steam is installed when missing, and recorded as ours" 'grep -q "^S " "$PACMAN_LOG" && [[ "$(state_get nvidia installed_pkgs)" == steam ]]'
|
||||
check "the autostart unit is written: Steam without arguments" 'grep -q "^ExecStart=/usr/bin/steam $" "$UNIT"'
|
||||
check "it is on" 'nvidia_status && ! bigpicture_status'
|
||||
n="$(wc -l < "$PACMAN_LOG")"; nvidia_enable
|
||||
check "a second run installs nothing again" '[[ "$(wc -l < "$PACMAN_LOG")" == "$n" ]]'
|
||||
|
||||
# --- Big Picture
|
||||
bigpicture_enable
|
||||
check "Big Picture: Steam starts with -gamepadui" 'grep -q "^ExecStart=/usr/bin/steam -gamepadui$" "$UNIT" && bigpicture_status'
|
||||
nvidia_enable
|
||||
check "turning Gaming on NVIDIA on again keeps Big Picture" 'bigpicture_status'
|
||||
check "Big Picture: Plasma starts with an empty session" '[[ "$(kreadconfig6 --file ksmserverrc --group General --key loginMode)" == emptySession ]]'
|
||||
bigpicture_disable
|
||||
check "Big Picture off: Steam's normal window again" '! bigpicture_status && nvidia_status'
|
||||
check "...and the session setting is back as it was (not set)" '[[ -z "$(kreadconfig6 --file ksmserverrc --group General --key loginMode)" ]]'
|
||||
|
||||
# --- turning it off
|
||||
bigpicture_enable; nvidia_disable
|
||||
check "off: the unit is gone and Steam, which we installed, is removed" '! nvidia_status && [[ ! -e "$UNIT" ]] && ! pacman -Q steam && [[ -z "$(state_get nvidia installed_pkgs)" ]]'
|
||||
check "Big Picture without the unit does nothing" 'bigpicture_enable && bigpicture_disable && [[ ! -e "$UNIT" ]]'
|
||||
|
||||
# --- Steam that was there already is never removed
|
||||
PKGS=" steam"; : > "$PACMAN_LOG"
|
||||
nvidia_enable; nvidia_disable
|
||||
check "Steam installed before is kept, and not installed again" '[[ ! -s "$PACMAN_LOG" ]] && pacman -Q steam'
|
||||
|
||||
# --- kernel parameters and early modules (a supported card only: RTX 20 series or newer)
|
||||
check "RTX 5080 (2c02) is supported: no chwd legacy lists" nvidia_supported
|
||||
printf '1c03\n1b80\n' > "$T/chwd/nvidia-580.ids" # a GTX 1060 and a GTX 1080: chwd's 580xx legacy list
|
||||
check "RTX 5080 is not on a legacy list" 'nvidia_supported && ! nvidia_legacy_gpu'
|
||||
echo 0x1c03 > "$T/drm/card1/device/device"
|
||||
check "GTX 1060 (older than RTX 20): no kernel setup, but still an NVIDIA PC" '! nvidia_supported && nvidia_legacy_gpu && nvidia_present'
|
||||
echo 0x2c02 > "$T/drm/card1/device/device"
|
||||
|
||||
for loader in limine limineplus sdboot grub; do
|
||||
case $loader in
|
||||
limine) F="$T/limine"; printf 'KERNEL_CMDLINE[default]="quiet splash"\n' > "$F" ;;
|
||||
# CachyOS writes its line as "+=" and may already have nvidia_drm.modeset=1 in it.
|
||||
limineplus) F="$T/limine"; printf 'KERNEL_CMDLINE[default]+="quiet splash nvidia_drm.modeset=1"\n' > "$F" ;;
|
||||
sdboot) F="$T/sdboot-manage.conf"; printf 'LINUX_OPTIONS="quiet splash"\n' > "$F" ;;
|
||||
grub) F="$T/grub"; printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"\n' > "$F" ;;
|
||||
esac
|
||||
nvidia_boot_file() { echo "$F"; }
|
||||
rm -f "$NVIDIA_INITRAMFS_CONF"; REBUILDS=0; ORIG="$(cat "$F")"
|
||||
nvidia_kernel_enable
|
||||
check "$loader: parameters added" 'grep -q "nvidia-drm.modeset=1" "$F" && grep -q "nvidia-drm.fbdev=1" "$F"'
|
||||
check "$loader: initramfs drop-in written" 'grep -q nvidia_drm "$NVIDIA_INITRAMFS_CONF"'
|
||||
check "$loader: script and pacman hook installed" '[[ -x "$NVIDIA_SCRIPT" ]] && grep -q "^Exec = $NVIDIA_SCRIPT sync" "$NVIDIA_HOOK"'
|
||||
check "$loader: backup made, rebuilt once" '[[ -f "$F.bak" && $REBUILDS == 1 ]]'
|
||||
SUM="$(cat "$F")"; nvidia_kernel_enable
|
||||
check "$loader: second run changes nothing" '[[ "$(cat "$F")" == "$SUM" && $REBUILDS == 1 ]]'
|
||||
nvidia_kernel_disable
|
||||
check "$loader: disable restores the file, drops the drop-in, script and hook" '[[ "$(cat "$F")" == "$ORIG" && ! -f "$NVIDIA_INITRAMFS_CONF" && ! -e "$NVIDIA_SCRIPT" && ! -e "$NVIDIA_HOOK" ]]'
|
||||
done
|
||||
|
||||
# A missing patches/ file must be an error, never an empty script or hook.
|
||||
( SCRIPT_DIR="$T/no-patches"; NVIDIA_SCRIPT="$T/empty/script"; NVIDIA_HOOK="$T/empty/hook"; ! nvidia_hook_install 2>/dev/null && [[ ! -s "$T/empty/script" ]] ) && echo "ok a missing patch file is an error, nothing empty installed" || { echo "FAIL a missing patch file is an error, nothing empty installed"; fail=1; }
|
||||
# The pacman hook's script decides again at every kernel change.
|
||||
syncit() { NVIDIA_INITRAMFS_CONF="$NVIDIA_INITRAMFS_CONF" NVIDIA_MODULES_DIR="$NVIDIA_MODULES_DIR" bash "$NVIDIA_SCRIPT" sync; }
|
||||
nvidia_hook_install; rm -f "$NVIDIA_INITRAMFS_CONF"
|
||||
syncit; check "hook script: every kernel has the modules -> drop-in written" '[[ -f "$NVIDIA_INITRAMFS_CONF" ]]'
|
||||
mkdir -p "$T/mods/6.6.0-lts/kernel"; : > "$T/mods/6.6.0-lts/pkgbase"
|
||||
syncit; check "hook script: a new kernel without modules -> drop-in removed" '[[ ! -f "$NVIDIA_INITRAMFS_CONF" ]]'
|
||||
: > "$T/mods/6.6.0-lts/kernel/nvidia-drm.ko.zst"
|
||||
syncit; check "hook script: the modules arrive (DKMS built) -> drop-in back" '[[ -f "$NVIDIA_INITRAMFS_CONF" ]]'
|
||||
rm -rf "$T/mods/6.6.0-lts"; mkdir -p "$T/mods/5.0.0-leftover"; : > "$T/mods/5.0.0-leftover/modules.dep"
|
||||
syncit; check "hook script: a leftover folder of a removed kernel is ignored" '[[ -f "$NVIDIA_INITRAMFS_CONF" ]]'
|
||||
rm -rf "$T/mods/5.0.0-leftover"; nvidia_kernel_disable
|
||||
# A second kernel without the NVIDIA modules: no early-load drop-in, parameters still set.
|
||||
mkdir -p "$T/mods/6.6.0-lts/kernel"; : > "$T/mods/6.6.0-lts/pkgbase"; F="$T/sdboot-manage.conf"; printf 'LINUX_OPTIONS="quiet"\n' > "$F"
|
||||
nvidia_boot_file() { echo "$F"; }; rm -f "$NVIDIA_INITRAMFS_CONF"; REBUILDS=0; nvidia_kernel_enable
|
||||
check "kernel without nvidia modules: no drop-in, parameters set" '[[ ! -f "$NVIDIA_INITRAMFS_CONF" ]] && grep -q "nvidia-drm.fbdev=1" "$F"'
|
||||
rm -rf "$T/mods/6.6.0-lts"
|
||||
# Generated boot entries without the parameters (the initramfs build failed): an error.
|
||||
printf 'linux /vmlinuz root=/dev/sda1\n' > "$T/grub.cfg"; NVIDIA_GRUB_CFG="$T/grub.cfg"
|
||||
F="$T/grub"; printf 'GRUB_CMDLINE_LINUX_DEFAULT="quiet"\n' > "$F"; nvidia_boot_file() { echo "$F"; }; rm -f "$NVIDIA_INITRAMFS_CONF"
|
||||
check "boot entries without the parameters: error" '! nvidia_kernel_enable >/dev/null'
|
||||
NVIDIA_GRUB_CFG="$T/none/grub.cfg"
|
||||
# A config file without the setting to edit: an error, not "applied".
|
||||
F="$T/sdboot-manage.conf"; printf '#LINUX_OPTIONS=""\n' > "$F"; nvidia_boot_file() { echo "$F"; }; REBUILDS=0
|
||||
check "no setting to edit: error, nothing rebuilt" '! nvidia_kernel_enable >/dev/null && [[ $REBUILDS == 0 ]]'
|
||||
FAKE_DRIVER=0; NVIDIA_DRM_DIR="$T/drm"; printf 'LINUX_OPTIONS="quiet"\n' > "$T/sdboot"; F="$T/sdboot"; REBUILDS=0; nvidia_kernel_enable
|
||||
check "no NVIDIA driver: nothing touched" '[[ "$(cat "$F")" == "LINUX_OPTIONS=\"quiet\"" && $REBUILDS == 0 ]]'
|
||||
|
||||
# --- single user mode on NVIDIA: its own login step (SDDM, autologin into Plasma) without the conversion
|
||||
FAKE_DRIVER=1; NVIDIA_DRM_DIR="$T/drm" # an earlier section ended without the driver
|
||||
SW="$T/switches.log"; : > "$SW"; TARGET_USER=tester; SINGLE_AUTOLOGIN="$T/sddm.conf.d/zzz-steamify-autologin.conf"
|
||||
switch_to_sddm() { echo sddm >> "$SW"; }; switch_to_plasmalogin() { echo plasmalogin >> "$SW"; }
|
||||
remove_sddm_base_autologin() { :; } # would look at the real /etc/sddm.conf
|
||||
GAMING_ON=0
|
||||
check "single user mode is offered on NVIDIA without the conversion..." 'component_available single'
|
||||
GAMING_ON=1; check "...and with it" 'component_available single'; GAMING_ON=0
|
||||
single_login_enable
|
||||
check "single user: SDDM, autologin of the user into the Plasma session" 'grep -qx "User=tester" "$SINGLE_AUTOLOGIN" && grep -qx "Session=plasma.desktop" "$SINGLE_AUTOLOGIN" && grep -qx "Relogin=true" "$SINGLE_AUTOLOGIN" && grep -qx sddm "$SW"'
|
||||
c1="$(cat "$SINGLE_AUTOLOGIN")"; single_login_enable
|
||||
check "single user login: a second run changes nothing" '[[ "$(cat "$SINGLE_AUTOLOGIN")" == "$c1" ]]'
|
||||
single_login_disable
|
||||
check "single user off: the file is gone and plasma-login-manager is the login manager again" '[[ ! -e "$SINGLE_AUTOLOGIN" ]] && grep -qx plasmalogin "$SW"'
|
||||
: > "$SW"; GAMING_ON=1; single_login_enable
|
||||
check "with the conversion on, single user mode leaves the login to it" '[[ ! -e "$SINGLE_AUTOLOGIN" && ! -s "$SW" ]]'
|
||||
GAMING_ON=0
|
||||
# --- the menu rules
|
||||
declare -A WANTED=([nvidia]=1 [bigpicture]=1 [gaming]=0 [boot]=0 [single]=0 [machine]=0 [poweroff]=0 [launcher]=0 [steamgame]=0)
|
||||
component_selectable() { return 0; }
|
||||
toggle_component nvidia
|
||||
check "unticking Gaming on NVIDIA unticks Big Picture" '[[ "${WANTED[nvidia]}" == 0 && "${WANTED[bigpicture]}" == 0 ]]'
|
||||
toggle_component bigpicture
|
||||
check "ticking Big Picture ticks Gaming on NVIDIA" '[[ "${WANTED[nvidia]}" == 1 && "${WANTED[bigpicture]}" == 1 ]]'
|
||||
WANTED[single]=0; WANTED[gaming]=0; toggle_component single
|
||||
check "ticking single user mode does not tick the conversion where it isn't offered" '[[ "${WANTED[single]}" == 1 && "${WANTED[gaming]}" == 0 ]]'
|
||||
check "Big Picture follows Gaming on NVIDIA in the menu" '[[ " ${COMPONENTS[*]} " == *" nvidia bigpicture "* && "${PARENT[bigpicture]}" == nvidia ]]'
|
||||
|
||||
exit $fail
|
||||
@@ -0,0 +1,161 @@
|
||||
"""Unit tests of the Python helpers in steamify-cachyos/patches (stdlib only).
|
||||
|
||||
Run through tests/python-test.sh (REPO = the Steamify checkout), or:
|
||||
REPO=../steamify-cachyos python3 -m unittest tests/patches_test.py -v
|
||||
"""
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
REPO = os.environ.get("REPO") or os.path.join(os.path.dirname(__file__), "..", "..", "steamify-cachyos")
|
||||
|
||||
|
||||
def load(name, filename):
|
||||
spec = importlib.util.spec_from_file_location(name, os.path.join(REPO, "patches", filename))
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
shortcuts = load("steam_shortcuts", "steam-shortcuts.py")
|
||||
notifier = load("steamify_notifier", "steamify-notifier.py")
|
||||
|
||||
|
||||
class VdfTests(unittest.TestCase):
|
||||
def test_roundtrip(self):
|
||||
root = [("shortcuts", [("0", [("appid", 123), ("AppName", "A"), ("tags", [])])])]
|
||||
self.assertEqual(shortcuts.parse(shortcuts.dump(root)), root)
|
||||
|
||||
def test_empty_data_is_an_empty_list(self):
|
||||
self.assertEqual(shortcuts.parse(b""), [("shortcuts", [])])
|
||||
|
||||
def test_unknown_type_is_an_error(self):
|
||||
with self.assertRaises(ValueError):
|
||||
shortcuts.parse(b"\x07key\0")
|
||||
|
||||
def test_ints_are_unsigned_32_bit(self):
|
||||
data = shortcuts.dump([("n", -1)])
|
||||
self.assertEqual(struct.unpack_from("<I", data, 3)[0], 0xFFFFFFFF)
|
||||
|
||||
def test_keys_match_regardless_of_case(self):
|
||||
entry = [("AppName", "x")]
|
||||
self.assertEqual(shortcuts.get(entry, "appname"), "x")
|
||||
shortcuts.put(entry, "APPNAME", "y")
|
||||
self.assertEqual(entry, [("AppName", "y")])
|
||||
|
||||
def test_ours(self):
|
||||
exe = "/home/u/.local/share/steamify/bin/run-app"
|
||||
self.assertTrue(shortcuts.ours([("Exe", f'"{exe}"')], exe))
|
||||
# An older install's start script counts as ours: updated, not doubled.
|
||||
self.assertTrue(shortcuts.ours([("Exe", '"/home/u/.local/share/cachyos-gamescope-boot/bin/run-app"')], exe))
|
||||
self.assertFalse(shortcuts.ours([("Exe", '"/usr/bin/other"')], exe))
|
||||
self.assertFalse(shortcuts.ours([("Exe", '"/home/u/games/run-app"')], exe))
|
||||
|
||||
|
||||
class ShortcutsCommandTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.dir = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.dir.cleanup)
|
||||
self.vdf = os.path.join(self.dir.name, "userdata", "1", "config", "shortcuts.vdf")
|
||||
self.exe = "/home/u/.local/share/steamify/bin/run-app"
|
||||
|
||||
def run_cmd(self, *args):
|
||||
out = io.StringIO()
|
||||
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(io.StringIO()):
|
||||
rc = shortcuts.main(["steam-shortcuts.py", *args])
|
||||
return rc, out.getvalue().strip()
|
||||
|
||||
def entries(self):
|
||||
return shortcuts.shortcuts(shortcuts.load(self.vdf))
|
||||
|
||||
def test_add_creates_the_file_and_an_entry(self):
|
||||
rc, out = self.run_cmd("add", self.vdf, "Steamify", self.exe, "/icon.png", "steamify.desktop")
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertTrue(out.startswith("added "))
|
||||
(_, entry), = self.entries()
|
||||
self.assertEqual(shortcuts.get(entry, "AppName"), "Steamify")
|
||||
self.assertEqual(shortcuts.get(entry, "Exe"), f'"{self.exe}"')
|
||||
# Steam's id for a shortcut has the top bit set.
|
||||
self.assertGreaterEqual(shortcuts.get(entry, "appid"), 0x80000000)
|
||||
self.assertEqual(out, f"added {shortcuts.get(entry, 'appid')}")
|
||||
|
||||
def test_add_twice_updates_instead_of_doubling(self):
|
||||
self.run_cmd("add", self.vdf, "Steamify", self.exe, "/a.png", "a.desktop")
|
||||
rc, out = self.run_cmd("add", self.vdf, "Steamify 2", self.exe, "/b.png", "b.desktop")
|
||||
self.assertTrue(out.startswith("updated "))
|
||||
entries = self.entries()
|
||||
self.assertEqual(len(entries), 1)
|
||||
self.assertEqual(shortcuts.get(entries[0][1], "AppName"), "Steamify 2")
|
||||
self.assertEqual(shortcuts.get(entries[0][1], "icon"), "/b.png")
|
||||
|
||||
def test_other_entries_are_kept(self):
|
||||
self.run_cmd("add", self.vdf, "Other", "/usr/bin/other", "", "")
|
||||
self.run_cmd("add", self.vdf, "Steamify", self.exe, "", "")
|
||||
self.assertEqual(len(self.entries()), 2)
|
||||
self.assertEqual([k for k, _ in self.entries()], ["0", "1"])
|
||||
|
||||
def test_find(self):
|
||||
self.assertEqual(self.run_cmd("find", self.vdf, self.exe)[0], 1)
|
||||
_, out = self.run_cmd("add", self.vdf, "Steamify", self.exe, "", "")
|
||||
rc, found = self.run_cmd("find", self.vdf, self.exe)
|
||||
self.assertEqual((rc, found), (0, out.split()[1]))
|
||||
|
||||
def test_remove_by_appid_renumbers(self):
|
||||
self.run_cmd("add", self.vdf, "Other", "/usr/bin/other", "", "")
|
||||
_, out = self.run_cmd("add", self.vdf, "Steamify", self.exe, "", "")
|
||||
self.assertEqual(self.run_cmd("remove", self.vdf, out.split()[1])[0], 0)
|
||||
entries = self.entries()
|
||||
self.assertEqual([k for k, _ in entries], ["0"])
|
||||
self.assertEqual(shortcuts.get(entries[0][1], "AppName"), "Other")
|
||||
|
||||
def test_remove_of_nothing_leaves_the_file_alone(self):
|
||||
self.run_cmd("add", self.vdf, "Steamify", self.exe, "", "")
|
||||
with open(self.vdf, "rb") as f:
|
||||
before = f.read()
|
||||
self.assertEqual(self.run_cmd("remove", self.vdf, "999")[0], 0)
|
||||
with open(self.vdf, "rb") as f:
|
||||
self.assertEqual(f.read(), before)
|
||||
|
||||
def test_unknown_command(self):
|
||||
self.assertEqual(self.run_cmd("bogus", self.vdf)[0], 2)
|
||||
|
||||
|
||||
class NotifierTests(unittest.TestCase):
|
||||
def test_version(self):
|
||||
self.assertEqual(notifier.version("v2.11.1"), (2, 11, 1))
|
||||
self.assertEqual(notifier.version("2.9.0"), (2, 9, 0))
|
||||
self.assertIsNone(notifier.version("nightly"))
|
||||
|
||||
def test_versions_compare_as_numbers(self):
|
||||
self.assertGreater(notifier.version("2.10.0"), notifier.version("2.9.0"))
|
||||
|
||||
def pending(self, new, seen="", skipped=""):
|
||||
values = {"seen": seen, "skipped": skipped}
|
||||
with mock.patch.object(notifier, "state", lambda key: values[key]):
|
||||
return notifier.pending(new)
|
||||
|
||||
def test_newer_than_seen(self):
|
||||
self.assertTrue(self.pending("2.12.0", seen="2.11.1"))
|
||||
|
||||
def test_same_or_older_is_not_pending(self):
|
||||
self.assertFalse(self.pending("2.11.1", seen="2.11.1"))
|
||||
self.assertFalse(self.pending("2.9.0", seen="2.11.1"))
|
||||
|
||||
def test_skipped_version_is_not_pending(self):
|
||||
self.assertFalse(self.pending("2.12.0", seen="2.11.1", skipped="2.12.0"))
|
||||
|
||||
def test_nothing_seen_yet(self):
|
||||
self.assertTrue(self.pending("2.12.0", seen=""))
|
||||
|
||||
def test_unparsable_tag_is_not_pending(self):
|
||||
# A release tag like "nightly" must not crash the daily check.
|
||||
self.assertFalse(self.pending("nightly", seen="2.11.1"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Unit tests of the Python helpers in steamify-cachyos/patches (steam-shortcuts.py,
|
||||
# steamify-notifier.py). No root, VM or hardware needed: bash tests/python-test.sh
|
||||
# The Steamify checkout (REPO, default: the steamify-cachyos next to this repo)
|
||||
REPO="${REPO:-$(cd "$(dirname "$0")/../../steamify-cachyos" && pwd)}"
|
||||
export REPO
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
if python3 -B -m unittest tests/patches_test.py 2>&1 | tail -n 25 | tee /dev/stderr | grep -q '^OK'; then
|
||||
echo "ok Python helpers"
|
||||
else
|
||||
echo "FAIL Python helpers"; exit 1
|
||||
fi
|
||||
Reference in new issue
Block a user