From 8ea75424b0196733ba736025e6f32ab99adcee81 Mon Sep 17 00:00:00 2001 From: Rick Peters Date: Tue, 29 Sep 2026 11:23:50 +0200 Subject: [PATCH] feat: vminstall.sh installs any boot loader and the Steamify ISO's Steamify step, with a package cache VM_BOOTLOADER=limine|systemd-boot|grub, VM_STEAMIFY (steamify-install runs after the headless installer, like Calamares does), VM_CACHE (host pacman cache shared as 9p, default ~/vms/pkg-cache). run.sh --headless / VM_HEADLESS=1: no window. Fixes found on the way: the live script deadlocked on is-system-running (it is the running kernel-command-line.service) and now waits for pacman-init; the post script kept forcing the Plasma login over Steamify's gaming-mode SDDM, gave systemd-boot no timeout/default (it waited in the menu for ever), and left ufw blocking the host's ssh; the first-boot check wanted plasmashell in gaming mode. --- run.sh | 11 +++++++++-- scripts/vminstall.sh | 25 ++++++++++++++++++------- share/vminstall-live.sh | 36 +++++++++++++++++++++++++++++++++++- share/vminstall-post.sh | 30 +++++++++++++++++++++++++----- 4 files changed, 87 insertions(+), 15 deletions(-) diff --git a/run.sh b/run.sh index ac1b3d4..b03afae 100755 --- a/run.sh +++ b/run.sh @@ -1,11 +1,12 @@ #!/bin/bash # CachyOS test VM for steamify.sh. -# [REPO=/path/to/cachyos-gamescope-boot] ./run.sh [install] [--nvidia] [--vulkan] [--amd] [--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= boots that ISO for `install` instead of cachyos.iso; with # VM_KERNEL/VM_INITRD/VM_APPEND its kernel is booted directly with those @@ -48,11 +49,17 @@ for arg in "$@"; do __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; } @@ -70,7 +77,7 @@ exec qemu-system-x86_64 \ -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=$gl,zoom-to-fit=off \ + -device "$gpu" "${display[@]}" \ -device qemu-xhci -device usb-tablet \ -nic user,model=virtio-net-pci,hostfwd=tcp::${VM_PORT:-2222}-:22 \ -virtfs local,path="$repo",mount_tag=repo,security_model=mapped-xattr \ diff --git a/scripts/vminstall.sh b/scripts/vminstall.sh index 10c6f23..9358af8 100755 --- a/scripts/vminstall.sh +++ b/scripts/vminstall.sh @@ -11,7 +11,9 @@ # 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, +# 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. @@ -21,6 +23,9 @@ repo="$(cd "$here/.." && pwd)" [[ -n "${VM_USER:-}" ]] || VM_USER="$(id -un)" 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 @@ -106,11 +111,11 @@ port="$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); p 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" + 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" << 'PY' +python3 - "$www/settings.json" "$VM_USER" "$password" "$timezone" "$hostname" "${VM_BOOTLOADER:-limine}" << 'PY' import json, sys -out, user, pw, tz, host = sys.argv[1:] +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", @@ -120,7 +125,7 @@ json.dump({ ], "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", + "kernel": "linux-cachyos linux-cachyos-lts", "desktop": "kde", "bootloader": bootloader, }, open(out, "w"), indent=2) PY python3 "$here/vminstall-server.py" "$www" "$port" & @@ -161,8 +166,14 @@ for _ in $(seq 100); do vm_ssh -o ConnectTimeout=3 true 2>/dev/null && break; sl 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; } +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 diff --git a/share/vminstall-live.sh b/share/vminstall-live.sh index 9213184..1715a27 100755 --- a/share/vminstall-live.sh +++ b/share/vminstall-live.sh @@ -30,11 +30,25 @@ curl -fsS "$H/vminstall-post.sh" -o /tmp/vminstall-post.sh || fail "no vminstall ) & while sleep 20; do up; done & -systemctl is-system-running --wait >/dev/null 2>&1 +# 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 $?" @@ -46,6 +60,26 @@ if ! mountpoint -q /mnt; then 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)" +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 $?" diff --git a/share/vminstall-post.sh b/share/vminstall-post.sh index eedb181..6528fe7 100755 --- a/share/vminstall-post.sh +++ b/share/vminstall-post.sh @@ -23,11 +23,18 @@ 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 +# 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 @@ -36,6 +43,19 @@ if [[ -f /boot/limine.conf ]] && ! grep -q '^default_entry:' /boot/limine.conf; 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