mirror of
https://github.com/theupriser/steamify-cachyos-dev.git
synced 2026-10-05 12:45:56 +02:00
feat: scripts/vminstall.sh installs an ssh-ready test VM unattended; TESTPLAN.md; vm-install skill
This commit is contained in:
1 parent
21547dd7bc
commit
60a2698d02
19 files changed
+705
-72
No files matched your search
@@ -69,6 +69,43 @@ the steam-machine-testing skill). From the Mac:
|
||||
- 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
|
||||
@@ -126,7 +163,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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: steam-machine-iso
|
||||
description: Use when working on the Steam Machine CachyOS ISO (repo steammachine-cachyos-live-iso) or Steamify's install-time mode (steamify.sh --defaults, --first-login) - building the ISO in podman on the Steam Machine, the Calamares Steamify step, simulating the installer in the test VM, and testing the first desktop login.
|
||||
description: Use when working on the Steam Machine CachyOS ISO (repo steammachine-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
|
||||
@@ -57,6 +57,26 @@ ssh steammachine bash -c "'export XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BU
|
||||
`--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.
|
||||
|
||||
## 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/steammachine-cachyos-live-iso ~/projects/steammachine-cachyos-live-iso
|
||||
sudo mount -t 9p -o trans=virtio,version=9p2000.L repo /mnt # the Steamify checkout (REPO)
|
||||
cd ~/projects/steammachine-cachyos-live-iso && git checkout feat/steamify && ./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/steammachine-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
|
||||
@@ -119,6 +139,11 @@ First desktop login as that user:
|
||||
|
||||
## 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
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
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 `vminstall.sh` while it runs: bash reads it as it goes.
|
||||
- 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.
|
||||
|
||||
## The Steamify CachyOS ISO (later)
|
||||
|
||||
`--iso <steamify iso>` installs it the same way if it's archiso-based with
|
||||
`cachyos-installer`. The headless installer doesn't run Calamares, so the
|
||||
ISO's Steamify step (`steamify-install`) doesn't run by itself: run it in the
|
||||
live script after the install (`/usr/local/bin/steamify-install /mnt
|
||||
$VM_USER <options>`) when testing the ISO, and check
|
||||
`/var/log/steamify-install.log` in the installed system.
|
||||
@@ -6,12 +6,15 @@
|
||||
!/CHANGELOG.md
|
||||
!/run.sh
|
||||
!/get-iso.sh
|
||||
!/TESTPLAN.md
|
||||
!/scripts/
|
||||
!/share/
|
||||
!/.claude/
|
||||
|
||||
share/*
|
||||
!share/guest-ssh-setup.sh
|
||||
!share/vminstall-live.sh
|
||||
!share/vminstall-post.sh
|
||||
|
||||
# Belt and braces, anywhere in the tree
|
||||
*.iso
|
||||
@@ -23,3 +26,4 @@ share/*
|
||||
*.log
|
||||
*.tmp
|
||||
host-keys.pub
|
||||
__pycache__/
|
||||
+27
-1
@@ -4,11 +4,37 @@ 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.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.
|
||||
|
||||
- **feat: steam-machine-iso skill (ISO build in podman on the Steam Machine, installer simulation, first-login test) and scripts/vminstallsim.sh**
|
||||
- `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
|
||||
|
||||
|
||||
@@ -19,101 +19,108 @@ tracked file to it explicitly.
|
||||
- `curl` and `sha256sum` for `get-iso.sh`.
|
||||
- Optional: an NVIDIA dGPU for `--nvidia` (PRIME offload of the virgl renderer).
|
||||
|
||||
## 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:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519
|
||||
```
|
||||
|
||||
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:
|
||||
### The same key on the Steam Machine
|
||||
|
||||
```bash
|
||||
ssh-copy-id -p 2222 <vm-user>@localhost
|
||||
scripts/steammachine-addkey.sh [user@host] # default: steammachine (~/.ssh/config)
|
||||
```
|
||||
|
||||
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.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# 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 |
|
||||
|
||||
## F. Features per release
|
||||
|
||||
### 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).
|
||||
|
||||
## 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.
|
||||
|
||||
## 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 |
|
||||
@@ -7,6 +7,9 @@
|
||||
# --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)
|
||||
# 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).
|
||||
# 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:
|
||||
@@ -15,15 +18,23 @@
|
||||
# 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_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
|
||||
[[ -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 ;;
|
||||
@@ -56,4 +67,4 @@ exec qemu-system-x86_64 \
|
||||
-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 \
|
||||
-qmp unix:"$PWD/qmp.sock",server=on,wait=off \
|
||||
"${smbios[@]}" "${cdrom[@]}"
|
||||
"${smbios[@]}" "${cdrom[@]}" "${direct[@]}"
|
||||
+18
-2
@@ -1,5 +1,21 @@
|
||||
# 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)
|
||||
vm_ssh() { ssh -p "$VM_PORT" "${VM_SSH_OPTS[@]}" "$VM_USER@$VM_HOST" "$@"; }
|
||||
vm_scp() { scp -q -P "$VM_PORT" "${VM_SSH_OPTS[@]}" "$@"; }
|
||||
@@ -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)
|
||||
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."
|
||||
@@ -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
+174
@@ -0,0 +1,174 @@
|
||||
#!/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),
|
||||
# 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_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
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
repo="$(cd "$here/.." && pwd)"
|
||||
[[ -n "${VM_USER:-}" ]] || VM_USER="$(id -un)"
|
||||
export VM_USER
|
||||
. "$here/common.sh"
|
||||
|
||||
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 '^qemu-system' >/dev/null && { echo "A VM is running; power it off first (both use port $VM_PORT)." >&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"
|
||||
loop="$(udisksctl loop-setup -r -f "$iso" | grep -o '/dev/loop[0-9]*')"
|
||||
cleanup_loop() { udisksctl unmount -b "${loop}p1" >/dev/null 2>&1 || true; udisksctl loop-delete -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 -b "${loop}p1" >/dev/null 2>&1 || true; sleep 1
|
||||
done
|
||||
[[ -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
|
||||
|
||||
# --- 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\n' "$VM_USER" "$hostname" "$pubkey"
|
||||
} > "$www/vminstall.env"
|
||||
python3 - "$www/settings.json" "$VM_USER" "$password" "$timezone" "$hostname" << 'PY'
|
||||
import json, sys
|
||||
out, user, pw, tz, host = 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": "limine",
|
||||
}, 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_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=$!
|
||||
for _ in $(seq 1080); do kill -0 $qemu 2>/dev/null || break; sleep 5; done # 90 minutes
|
||||
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
|
||||
[[ "$status" == ok ]] || { echo "Install failed: $status. Log: $VM_DIR/install.log" >&2; exit 1; }
|
||||
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 &
|
||||
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; }
|
||||
vm_ssh bash -s << 'REMOTE'
|
||||
set -e
|
||||
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; }
|
||||
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
|
||||
while pgrep -f '^qemu-system' >/dev/null; do sleep 2; done
|
||||
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)."
|
||||
@@ -17,7 +17,7 @@ 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 \
|
||||
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
|
||||
|
||||
+4
-5
@@ -9,10 +9,6 @@
|
||||
# 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
|
||||
SNAPSHOT="${SNAPSHOT:-ssh-ready}"
|
||||
VM_LOG="${VM_LOG:-$VM_DIR/vm.log}"
|
||||
|
||||
@@ -37,7 +33,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.
|
||||
|
||||
+1
-1
@@ -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"
|
||||
+2
-2
@@ -9,7 +9,7 @@ echo "DM=$(systemctl show -p Id --value display-manager) sddm-autologin=$(yn /et
|
||||
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 "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
+57
@@ -0,0 +1,57 @@
|
||||
#!/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 &
|
||||
|
||||
systemctl is-system-running --wait >/dev/null 2>&1
|
||||
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)"
|
||||
|
||||
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"
|
||||
|
||||
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
+44
@@ -0,0 +1,44 @@
|
||||
#!/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.
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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"
|
||||
Reference in new issue
Block a user