Files
LGUG2Z 1afac08af5 fix(scripts): the Linux scripts find the repository from scripts/linux/
The scripts were written for a KDE-only repository root (komorebi-for-kde/, scripts/ one
level below it). They now live in scripts/linux/ of the consolidated repository:
- run-kde looks for the repository two directories above itself (flake.nix at the root) and
  no longer knows the komorebi-for-kde/ subdirectory; its comments name nix/linux and docs/linux
- vm-mac cd`s two levels up, usage and comments name scripts/linux/vm-mac
- sync-e2e-core and scenario-status read nix/linux/tests/vm/lib, and usage lines name the new
  paths (scenario-status resolves the library with parents[2])
- wsl2-probe.sh names docs/linux/dev/wsl2-probe.md and prints "komorebi (KDE Plasma)" instead of
  komorebi-for-kde in its report header
- compare-gen.py is stale by design: the docstring now says so a second time (layout) and
  names the new path of COMPARE.md; the logic is not repaired

Flake attribute names the scripts call (nix run .#run-kde, .#wsl2-probe, legacyPackages.*.vm.NAME
in vm-mac) are left for the one-flake revision.

Verified: bash -n and shellcheck -S warning pass on the four shell scripts; the two Python
scripts parse (ast); `bash run-kde --dry-run` run from /tmp prints KOMOREBI_REPO as this
checkout; `sync-e2e-core --check e2e/lib` prints "in step"; `scenario-status --help` runs.
Not run: the VM, vm-mac, wsl2-probe.
2026-11-05 15:03:27 -08:00

724 lines
27 KiB
Bash

#!/usr/bin/env bash
# run-kde: start the komorebi demo guest (nix/linux/dev/run-kde.nix) in a virtual machine, so that anyone
# can try tiling by hand. docs/linux/trying-it.md is the guide, docs/linux/dev/utm.md says what the guest takes
# from its runner; this is the program.
#
# nix run .#run-kde -- [options] or just run-kde [options]
#
# The flake wraps this file with the guest's runner baked in (RUN_KDE_RUNNER_BIN, RUN_KDE_GUEST) and
# the tools it needs on its PATH. Everything it reads from the machine it reads through a variable
# that a test can set (RUN_KDE_*, below), so that `--dry-run` gives the same answer on a machine
# that has none of it: the check `run-kde-dry-run` runs it in a sandbox.
set -euo pipefail
usage() {
cat <<'EOF'
Usage: run-kde [options]
Starts a NixOS guest with a Plasma session in which komorebi tiles a few windows, and shows it in
a window (QEMU through WSLg, where there is one) or over RDP. The first run builds komorebi from
source and downloads Plasma: there is no binary cache.
--doctor check every prerequisite and say how to fix what is missing; exit 1 if
one is, and always when there is no KVM
--dry-run print what would be started, and start nothing
--build-only build the guest and stop
--reset delete the state directory (the disk, the password and the key) and stop
--shell no window: this terminal is the guest's console (--display none --gpu off)
--rdp on WSL 2, open Windows' own RDP client (mstsc) when the guest is up
--display KIND gtk, vnc, rdp or none (default: gtk where there is a window, else rdp)
--gpu MODE auto, on or off (default: auto): on renders in the guest with the host's GPU
(virtio-vga-gl); off renders on the guest's CPUs (virtio-vga)
--tcg allow software emulation when there is no KVM (too slow to try anything)
--memory MB the guest's memory (default: what this machine has, 4096 to 8192)
--cores N the guest's cores (default: what this machine has, up to 6)
--size GB the size of the guest's disk when it is made (default: 20, sparse)
--resolution WxH the size of the guest's screen (default: 1280x800)
--ssh-port PORT the host's port for ssh into the guest (default: 2223)
--rdp-port PORT the host's port for RDP into the guest (default: 3389)
-h, --help this text
The repository the guest mounts at /repo is the checkout this file is in (or, from the flake, the current
checkout), or KOMOREBI_REPO.
EOF
}
# what the flake bakes in, and a guess for a run of the file by hand
guest=${RUN_KDE_GUEST:-run-kde-$(uname -m)}
runner_bin=${RUN_KDE_RUNNER_BIN:-}
# what this machine says about itself: each can be set by a test
kvm_device=${RUN_KDE_KVM_DEVICE:-/dev/kvm}
fuse_device=${RUN_KDE_FUSE_DEVICE:-/dev/fuse}
virtiofsd_bin=${RUN_KDE_VIRTIOFSD:-}
dxg_device=${RUN_KDE_DXG_DEVICE:-/dev/dxg}
dri_dir=${RUN_KDE_DRI_DIR:-/dev/dri}
meminfo=${RUN_KDE_MEMINFO:-/proc/meminfo}
cpuinfo=${RUN_KDE_CPUINFO:-/proc/cpuinfo}
osrelease=${RUN_KDE_OSRELEASE:-/proc/sys/kernel/osrelease}
sys_module_dir=${RUN_KDE_SYS_MODULE_DIR:-/sys/module}
store_dir=${RUN_KDE_STORE_DIR:-/nix}
wslconfig=${RUN_KDE_WSLCONFIG:-}
wsl_lib_dir=${RUN_KDE_WSL_LIB_DIR:-/usr/lib/wsl/lib}
va_drivers_dir=${RUN_KDE_VA_DRIVERS_DIR:-/run/opengl-driver/lib/dri}
mode=run
display=auto
gpu=auto
tcg=0
rdp=0
shell=0
memory=
cores=
size=20
resolution=1280x800
ssh_port=2223
rdp_port=3389
while [ $# -gt 0 ]; do
case $1 in
--doctor) mode=doctor ;;
--dry-run) mode=dry-run ;;
--build-only) mode=build-only ;;
--reset) mode=reset ;;
--shell) shell=1 ;;
--rdp) rdp=1 ;;
--tcg) tcg=1 ;;
--display | --gpu | --memory | --cores | --size | --resolution | --ssh-port | --rdp-port)
[ $# -ge 2 ] || {
echo "run-kde: $1 needs a value" >&2
exit 2
}
case $1 in
--display) display=$2 ;;
--gpu) gpu=$2 ;;
--memory) memory=$2 ;;
--cores) cores=$2 ;;
--size) size=$2 ;;
--resolution) resolution=$2 ;;
--ssh-port) ssh_port=$2 ;;
--rdp-port) rdp_port=$2 ;;
esac
shift
;;
-h | --help)
usage
exit 0
;;
*)
echo "run-kde: unknown option $1 (see --help)" >&2
exit 2
;;
esac
shift
done
case $display in auto | gtk | vnc | rdp | none) ;; *)
echo "run-kde: --display is gtk, vnc, rdp or none" >&2
exit 2
;;
esac
case $gpu in auto | on | off) ;; *)
echo "run-kde: --gpu is auto, on or off" >&2
exit 2
;;
esac
for number in "$size" "$ssh_port" "$rdp_port" ${memory:+"$memory"} ${cores:+"$cores"}; do
case $number in '' | *[!0-9]*)
echo "run-kde: expected a number, got $number" >&2
exit 2
;;
esac
done
case $resolution in [0-9]*x[0-9]*) ;; *)
echo "run-kde: --resolution is WIDTHxHEIGHT" >&2
exit 2
;;
esac
if [ "$shell" = 1 ]; then
display=none
gpu=off
fi
state_dir=${XDG_STATE_HOME:-$HOME/.local/state}/komorebi-run-kde
if [ "$mode" = reset ]; then
if [ -e "$state_dir" ]; then
rm -rf "$state_dir"
echo "run-kde: deleted $state_dir"
else
echo "run-kde: there is no $state_dir"
fi
exit 0
fi
# ---------------------------------------------------------------------------------------------
# what the machine is
wsl=0
if grep -qi microsoft "$osrelease" 2> /dev/null || [ -n "${WSL_DISTRO_NAME:-}" ]; then
wsl=1
fi
kvm=missing
if [ -e "$kvm_device" ]; then
if [ -r "$kvm_device" ] && [ -w "$kvm_device" ]; then kvm=ok; else kvm=denied; fi
fi
virt=none
if grep -qw svm "$cpuinfo" 2> /dev/null; then
virt=svm
elif grep -qw vmx "$cpuinfo" 2> /dev/null; then
virt=vmx
fi
have_window=0
if [ -n "${WAYLAND_DISPLAY:-}" ] || [ -n "${DISPLAY:-}" ]; then have_window=1; fi
mem_mb=$(awk '/^MemTotal:/ { print int($2 / 1024) }' "$meminfo" 2> /dev/null || true)
mem_mb=${mem_mb:-0}
cpus=${RUN_KDE_CPUS:-$(nproc 2> /dev/null || echo 1)}
# a device the host's GPU shows up as: WSL has /dev/dxg, a Linux machine a render node
gpu_device=
if [ "$wsl" = 1 ]; then
if [ -e "$dxg_device" ]; then gpu_device=$dxg_device; fi
else
for node in "$dri_dir"/renderD*; do
if [ -e "$node" ]; then
gpu_device=$node
break
fi
done
fi
host_renderer() {
if [ "${RUN_KDE_HOST_RENDERER+set}" = set ]; then
printf '%s\n' "$RUN_KDE_HOST_RENDERER"
elif [ -n "${DISPLAY:-}" ] && command -v glxinfo > /dev/null 2>&1; then
glxinfo -B 2> /dev/null | sed -n 's/^OpenGL renderer string: //p' | head -n 1 || true
elif [ -n "${WAYLAND_DISPLAY:-}" ] && command -v eglinfo > /dev/null 2>&1; then
eglinfo -B 2> /dev/null | sed -n 's/^OpenGL core profile renderer: //p' | head -n 1 || true
fi
}
free_gb() {
if [ -n "${RUN_KDE_FREE_GB:-}" ]; then
echo "$RUN_KDE_FREE_GB"
else
df -Pk "$1" 2> /dev/null | awk 'NR == 2 { print int($4 / 1048576) }' || echo 0
fi
}
# ---------------------------------------------------------------------------------------------
# what the guest gets
guest_memory=$memory
if [ -z "$guest_memory" ]; then
guest_memory=$((mem_mb - 2048))
[ "$guest_memory" -ge 4096 ] || guest_memory=4096
[ "$guest_memory" -le 8192 ] || guest_memory=8192
fi
guest_cores=$cores
if [ -z "$guest_cores" ]; then
guest_cores=$cpus
[ "$guest_cores" -le 6 ] || guest_cores=6
fi
if [ "$display" = auto ]; then
if [ "$have_window" = 1 ]; then display=gtk; else display=rdp; fi
fi
renderer=$(host_renderer)
gpu_on=0
case $gpu in
off) gpu_why="turned off with --gpu off" ;;
on)
gpu_on=1
gpu_why="turned on with --gpu on"
;;
auto)
if [ "$display" != gtk ]; then
gpu_why="there is no window to show it in (the display is $display)"
elif [ -z "$gpu_device" ]; then
if [ "$wsl" = 1 ]; then
gpu_why="there is no $dxg_device, so WSL has no GPU"
else
gpu_why="there is no render node in $dri_dir"
fi
else
case $renderer in
*llvmpipe* | *softpipe* | *swrast*)
gpu_why="the host's OpenGL is software ($renderer)"
;;
*)
gpu_on=1
gpu_why="$gpu_device is there${renderer:+ and the OpenGL of the host is $renderer}"
;;
esac
fi
;;
esac
# the virtiofsd that the guest's runner starts for each of its shares (the runner names it by its
# store path), else the one on PATH
find_virtiofsd() {
local named=
if [ -n "$virtiofsd_bin" ]; then
printf '%s\n' "$virtiofsd_bin"
return 0
fi
if [ -n "$runner_bin" ] && [ -r "$runner_bin" ]; then
named=$(grep -o '/nix/store/[a-z0-9]*-virtiofsd-[^/ ]*/bin/virtiofsd' "$runner_bin" 2> /dev/null | head -n 1 || true)
fi
if [ -n "$named" ] && [ -x "$named" ]; then
printf '%s\n' "$named"
else
command -v virtiofsd || true
fi
}
# What the host's GPU needs of the process that uses it, on WSL 2. Mesa has to be told to take the
# d3d12 driver of Windows, whose libraries are in a directory of their own, and the libva that
# virglrenderer takes the host's video encoder from has to be told which driver that is. Without
# the first OpenGL is llvmpipe, and without the second the guest's VA-API lists no encoder, so KRDP
# encodes H.264 on the guest's CPUs. What a person has set is left as it is.
host_env=()
if [ "$wsl" = 1 ] && [ "$gpu_on" = 1 ]; then
host_env+=("GALLIUM_DRIVER=${GALLIUM_DRIVER:-d3d12}")
if [ -d "$wsl_lib_dir" ]; then
case ":${LD_LIBRARY_PATH:-}:" in
*":$wsl_lib_dir:"*) ;;
*) host_env+=("LD_LIBRARY_PATH=$wsl_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}") ;;
esac
fi
if [ -e "$va_drivers_dir/d3d12_drv_video.so" ]; then
host_env+=("LIBVA_DRIVER_NAME=${LIBVA_DRIVER_NAME:-d3d12}")
host_env+=("LIBVA_DRIVERS_PATH=${LIBVA_DRIVERS_PATH:-$va_drivers_dir}")
fi
fi
# ---------------------------------------------------------------------------------------------
# the checks
errors=0
warnings=0
verbose=0
if [ "$mode" = doctor ]; then verbose=1; fi
ok() {
if [ "$verbose" = 1 ]; then printf ' ✓ %s\n' "$1"; fi
}
bad() {
errors=$((errors + 1))
printf ' ✗ %s\n' "$1"
shift
for line in "$@"; do printf ' %s\n' "$line"; done
}
warn() {
warnings=$((warnings + 1))
printf ' ! %s\n' "$1"
shift
for line in "$@"; do printf ' %s\n' "$line"; done
}
# The guest mounts its /nix/store and its shared directories by virtiofs: for each of them the
# runner starts a virtiofsd as this user, and QEMU hands the guest's virtio-fs device its socket.
# `wsl2-probe` (docs/linux/dev/wsl2-probe.md, P6) runs the same chain with a guest; its fixes are these.
check_virtiofs() {
local virtiofsd tmp pid up line lines
# virtiofsd does not open /dev/fuse (it speaks FUSE to the guest over its socket): a warning
if [ ! -e "$fuse_device" ]; then
fuse_fix=("on NixOS, boot.kernelModules = [ \"fuse\" ];")
if [ "$wsl" = 1 ]; then
fuse_fix+=("the kernel of WSL 2 has FUSE built in, so a kernel= line in %UserProfile%\\.wslconfig under [wsl2]"
"that names another kernel is the likely cause")
fi
warn "FUSE: $fuse_device does not exist; virtiofsd does not use it, so the guest can still start" "${fuse_fix[@]}"
elif [ -r "$fuse_device" ] && [ -w "$fuse_device" ]; then
ok "FUSE: $fuse_device can be opened"
else
warn "FUSE: $fuse_device is there and this user cannot open it; virtiofsd does not use it, so the guest can still start" \
"on NixOS, services.udev.extraRules = ''KERNEL==\"fuse\", MODE=\"0666\"'';" \
"for now: sudo chmod 666 $fuse_device"
fi
virtiofsd=$(find_virtiofsd)
if [ -z "$virtiofsd" ]; then
bad "virtiofsd: the program is not there, and the guest mounts its /nix/store through it" \
"start run-kde as nix run .#run-kde, which brings the virtiofsd of nixpkgs"
return 0
fi
# the options of nixpkgs' runner, for a directory that is empty
tmp=$(mktemp -d)
RUST_LOG=error "$virtiofsd" --socket-path="$tmp/socket" --shared-dir="$tmp" --readonly \
--sandbox=none --seccomp=none --cache=always --no-announce-submounts \
--translate-uid=host:65534:0:1 --translate-gid=host:65534:0:1 > "$tmp/log" 2>&1 < /dev/null &
pid=$!
up=0
for _ in $(seq 1 50); do
if [ -S "$tmp/socket" ]; then
up=1
break
fi
kill -0 "$pid" 2> /dev/null || break
sleep 0.1
done
kill "$pid" 2> /dev/null || true
wait "$pid" 2> /dev/null || true
if [ "$up" = 1 ]; then
ok "virtiofsd: starts as this user and serves a directory"
else
lines=()
while IFS= read -r line; do lines+=("virtiofsd said: $line"); done < <(head -n 6 "$tmp/log")
bad "virtiofsd: does not start as this user, and the guest mounts its /nix/store through it" \
"${lines[@]}" \
"virtiofsd ran with the options of the guest's runner and could not serve a directory: the lines above say why." \
"A socket path is 107 bytes at most, so a long \$TMPDIR is one cause: TMPDIR=/tmp run-kde" \
"Say what virtiofsd printed when you ask for help: the guest cannot start without it"
fi
rm -rf "$tmp"
}
check_everything() {
if [ "$wsl" = 1 ]; then
ok "this is WSL 2 ($(cat "$osrelease" 2> /dev/null || echo unknown))"
else
ok "this is Linux, not WSL 2 ($(cat "$osrelease" 2> /dev/null || echo unknown))"
fi
if command -v nix > /dev/null 2>&1; then
ok "nix is installed"
else
bad "nix is not installed" "install Nix with flakes enabled: https://nixos.org/download"
fi
case $kvm in
ok) ok "KVM: $kvm_device can be used" ;;
denied)
bad "KVM: $kvm_device is there and this user cannot open it" \
"put this user in the group kvm: on NixOS, users.users.<you>.extraGroups = [ \"kvm\" ];" \
"then open a new login (on WSL: run \`wsl --shutdown\` in PowerShell and start it again)"
;;
missing)
kvm_fix=()
if [ "$virt" = none ]; then
kvm_fix+=("the CPU's virtualization flags (svm or vmx) are not visible here, so nested virtualization is off")
if [ "$wsl" = 1 ]; then
kvm_fix+=("in %UserProfile%\\.wslconfig, under [wsl2], set: nestedVirtualization=true")
fi
kvm_fix+=("and turn on SVM (AMD) or VT-x (Intel) in the firmware setup of the machine")
else
kvm_fix+=("the CPU has $virt, so the kernel has no KVM or has not loaded it")
if [ "$virt" = svm ] && [ ! -e "$sys_module_dir/kvm_amd" ]; then
kvm_fix+=("kvm_amd is not loaded: modprobe kvm_amd (on NixOS, boot.kernelModules = [ \"kvm-amd\" ];)")
elif [ "$virt" = vmx ] && [ ! -e "$sys_module_dir/kvm_intel" ]; then
kvm_fix+=("kvm_intel is not loaded: modprobe kvm_intel (on NixOS, boot.kernelModules = [ \"kvm-intel\" ];)")
fi
if [ "$wsl" = 1 ]; then
kvm_fix+=("in %UserProfile%\\.wslconfig, under [wsl2], set: nestedVirtualization=true, then run \`wsl --shutdown\`")
fi
fi
if [ "$tcg" = 1 ]; then
warn "KVM: $kvm_device does not exist; --tcg lets the guest start without it, and Plasma is too slow to try anything that way" "${kvm_fix[@]}"
else
bad "KVM: $kvm_device does not exist, and the guest needs it" "${kvm_fix[@]}"
fi
;;
esac
check_virtiofs
if [ "$mem_mb" -lt 5120 ]; then
bad "memory: this machine has $mem_mb MB, and the guest needs at least 4096 MB of it" \
"in %UserProfile%\\.wslconfig, under [wsl2], set: memory=12GB (WSL gets half of the machine's memory by default)"
elif [ "$mem_mb" -lt 10240 ] && [ -z "$memory" ]; then
warn "memory: this machine has $mem_mb MB, so the guest gets $guest_memory MB (8192 is comfortable)" \
"in %UserProfile%\\.wslconfig, under [wsl2], set: memory=12GB"
else
ok "memory: this machine has $mem_mb MB, and the guest gets $guest_memory MB"
fi
ok "cores: this machine has $cpus, and the guest gets $guest_cores"
store_free=$(free_gb "$store_dir")
state_parent=${state_dir%/*}
[ -d "$state_parent" ] || state_parent=$HOME
state_free=$(free_gb "$state_parent")
if [ "$store_free" -lt 12 ]; then
bad "disk: $store_free GB are free for the Nix store ($store_dir), and the guest's closure is about 11 GB" \
"free some room (nix-collect-garbage -d), or give WSL's disk more: \`wsl --manage <distro> --resize <size>\`"
elif [ "$store_free" -lt 25 ]; then
warn "disk: $store_free GB are free for the Nix store ($store_dir): the first run needs about 16 GB" \
"nix-collect-garbage -d frees room"
else
ok "disk: $store_free GB are free for the Nix store ($store_dir)"
fi
if [ "$state_free" -lt "$size" ]; then
warn "disk: $state_free GB are free for the guest's disk in $state_dir, which can grow to $size GB"
fi
if [ "$have_window" = 1 ]; then
window=
if [ -n "${WAYLAND_DISPLAY:-}" ]; then window="WAYLAND_DISPLAY=$WAYLAND_DISPLAY"; fi
if [ -n "${DISPLAY:-}" ]; then window="${window:+$window }DISPLAY=$DISPLAY"; fi
ok "a window can be shown ($window)"
elif [ "$wsl" = 1 ]; then
warn "WSLg is not there (neither WAYLAND_DISPLAY nor DISPLAY is set), so the guest is reached over RDP" \
"WSLg comes with WSL 2 on Windows 11: run \`wsl --update\` in PowerShell, and do not set guiApplications=false in .wslconfig"
else
warn "there is no window to show the guest in (neither WAYLAND_DISPLAY nor DISPLAY is set), so the guest is reached over RDP"
fi
if [ "$wsl" = 1 ]; then
if command -v mstsc.exe > /dev/null 2>&1 && command -v cmdkey.exe > /dev/null 2>&1; then
ok "Windows interop: mstsc.exe and cmdkey.exe can be run, for --rdp"
else
warn "Windows interop: mstsc.exe or cmdkey.exe cannot be run, which --rdp needs" \
"on NixOS-WSL, wsl.interop.includePath = true; (and wsl.interop.register = true;)"
fi
fi
if [ -n "$gpu_device" ]; then
ok "GPU: $gpu_device is there"
elif [ "$wsl" = 1 ]; then
warn "GPU: there is no $dxg_device, so the guest renders on its CPUs" \
"install the Windows GPU driver with WSL support; on NixOS-WSL, wsl.useWindowsDriver = true;"
else
warn "GPU: there is no render node in $dri_dir, so the guest renders on its CPUs"
fi
if [ -n "$gpu_device" ]; then
case $renderer in
'') warn "GPU: the host's OpenGL renderer is not known (glxinfo and eglinfo say nothing)" ;;
*llvmpipe* | *softpipe* | *swrast*)
warn "GPU: the host's OpenGL is software ($renderer), so the guest renders on its CPUs" \
"on NixOS-WSL, wsl.useWindowsDriver = true; gives Mesa the Windows driver (d3d12)"
;;
*) ok "GPU: the host's OpenGL renderer is $renderer" ;;
esac
fi
if [ "$wsl" = 1 ] && [ -n "$gpu_device" ]; then
if [ -d "$wsl_lib_dir" ]; then
ok "GPU: the Windows driver is in $wsl_lib_dir"
else
warn "GPU: there is no $wsl_lib_dir, so Mesa cannot take the Windows driver (d3d12)" \
"run \`wsl --update\` in PowerShell, and install the Windows GPU driver with WSL support"
fi
if [ -e "$va_drivers_dir/d3d12_drv_video.so" ]; then
ok "GPU: the video driver of Mesa for it (d3d12) is in $va_drivers_dir"
else
warn "GPU: there is no d3d12 video driver in $va_drivers_dir, so KRDP encodes H.264 on the guest's CPUs" \
"on NixOS-WSL, wsl.useWindowsDriver = true; with a Mesa that is built with video codecs"
fi
fi
if [ "$gpu_on" = 1 ]; then
ok "the guest will use the GPU: $gpu_why"
else
ok "the guest will not use the GPU: $gpu_why"
fi
if [ "$wsl" = 1 ]; then
if [ -z "$wslconfig" ] && command -v cmd.exe > /dev/null 2>&1 && command -v wslpath > /dev/null 2>&1; then
profile=$(cmd.exe /C 'echo %USERPROFILE%' 2> /dev/null | tr -d '\r' || true)
if [ -n "$profile" ]; then wslconfig=$(wslpath -u "$profile")/.wslconfig; fi
fi
if [ -n "$wslconfig" ] && [ -r "$wslconfig" ]; then
if grep -qi '^ *networkingMode *= *mirrored' "$wslconfig"; then
warn "network: .wslconfig has networkingMode=mirrored, which may not forward localhost to the guest's RDP" \
"ssh and the window do not depend on it; if RDP does not connect, remove that line and run \`wsl --shutdown\`"
else
ok "network: $wslconfig does not set mirrored networking"
fi
fi
fi
}
if [ "$mode" = doctor ] || [ "$mode" = run ] || [ "$mode" = build-only ]; then
if [ "$mode" = doctor ]; then echo "run-kde: what the guest needs, and what this machine has"; fi
check_everything
if [ "$errors" -gt 0 ]; then
echo "run-kde: $errors thing(s) to fix first" >&2
exit 1
fi
if [ "$mode" = doctor ]; then
echo "run-kde: $errors error(s), $warnings warning(s)"
exit 0
fi
fi
# ---------------------------------------------------------------------------------------------
# the guest
repo=${KOMOREBI_REPO:-}
if [ -z "$repo" ]; then
# The repository is the one whose root has the flake.nix. A run of the file by hand is in
# scripts/linux/ of it, two directories down; the flake's wrapper is in the store, and falls back
# to the checkout the command is run from.
here=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2> /dev/null && pwd || true)
top=$(git rev-parse --show-toplevel 2> /dev/null || pwd)
case $here in
/nix/store/* | "") here= ;;
esac
if [ -n "$here" ] && [ -e "$here/flake.nix" ]; then
repo=$here
else
repo=$top
fi
fi
if [ ! -e "$repo/flake.nix" ]; then
echo "run-kde: $repo is not a checkout of the repository (there is no flake.nix): run it from one, or set KOMOREBI_REPO" >&2
exit 1
fi
disk=$state_dir/disk.qcow2
# the arguments of the runner, and the environment it reads (docs/linux/dev/utm.md says what each does)
if [ "$gpu_on" = 1 ]; then gpu_device_name=virtio-vga-gl; else gpu_device_name=virtio-vga; fi
width=${resolution%x*}
height=${resolution#*x}
qemu_opts=()
if [ "$tcg" != 1 ] || [ "$kvm" = ok ]; then qemu_opts+=(-cpu host); fi
case $display in
gtk)
if [ "$gpu_on" = 1 ]; then qemu_opts+=(-display "gtk,gl=on"); else qemu_opts+=(-display gtk); fi
;;
vnc) qemu_opts+=(-display none -vnc 127.0.0.1:0) ;;
rdp | none)
if [ "$gpu_on" = 1 ]; then qemu_opts+=(-display egl-headless); else qemu_opts+=(-display none); fi
;;
esac
qemu_opts+=(-vga none -device "$gpu_device_name,xres=$width,yres=$height")
net_opts="hostfwd=tcp:127.0.0.1:$ssh_port-:22,hostfwd=tcp:127.0.0.1:$rdp_port-:3389"
runner_args=(-m "$guest_memory" -smp "$guest_cores")
if [ "$wsl" = 1 ]; then host_kind="WSL 2"; else host_kind=Linux; fi
if [ "$kvm" = ok ]; then kvm_word=yes; else kvm_word=no; fi
if [ "$gpu_on" = 1 ]; then
gpu_word=on
gpu_label="GPU (virgl)"
else
gpu_word=off
gpu_label="software (llvmpipe)"
fi
if [ "$mode" = dry-run ]; then
echo "run-kde would start $guest:"
echo " host: $host_kind, KVM $kvm_word"
echo " display: $display"
echo " gpu: $gpu_word, because $gpu_why"
echo " environment:"
echo " NIX_DISK_IMAGE=$disk"
echo " KOMOREBI_REPO=$repo"
echo " RUN_KDE_STATE=$state_dir"
echo " RUN_KDE_MEMORY=$guest_memory"
echo " QEMU_NET_OPTS=$net_opts"
echo " QEMU_OPTS=${qemu_opts[*]}"
for var in "${host_env[@]}"; do echo " $var"; done
echo " command: ${runner_bin:-<the runner, built from $repo>} ${runner_args[*]}"
exit 0
fi
# the guest's runner, built when it is not there. RUN_KDE_QEMU_OPTS adds to the QEMU options.
if [ -z "$runner_bin" ] || [ ! -x "$runner_bin" ]; then
echo "run-kde: building the guest ($guest). There is no binary cache for komorebi: the first run"
echo "run-kde: builds it from source and downloads about 4 GB of Plasma (11 GB in the Nix store), which takes a while."
runner=$(nix build --no-link --print-out-paths -L "$repo#nixosConfigurations.$guest.config.system.build.vm")
set -- "$runner"/bin/run-*-vm
runner_bin=$1
if [ ! -x "$runner_bin" ]; then
echo "run-kde: $runner has no runner in bin" >&2
exit 1
fi
fi
if [ "$mode" = build-only ]; then
echo "run-kde: the guest is built: $runner_bin"
exit 0
fi
# the state: what a run keeps, and what the guest takes from it. Nothing of it is in the Nix store.
umask 077
mkdir -p "$state_dir"
chmod 700 "$state_dir"
if [ ! -s "$state_dir/rdp-password" ]; then
head -c 15 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$state_dir/rdp-password"
fi
if [ ! -s "$state_dir/ssh_key" ]; then
rm -f "$state_dir/ssh_key" "$state_dir/ssh_key.pub"
ssh-keygen -q -t ed25519 -N '' -C run-kde -f "$state_dir/ssh_key"
fi
if [ ! -e "$disk" ]; then
echo "run-kde: making the guest's disk: $size GB, which takes room as the guest writes to it"
raw=$state_dir/disk.raw
qemu-img create -q -f raw "$raw" "${size}G"
mkfs.ext4 -q -L nixos "$raw"
qemu-img convert -f raw -O qcow2 "$raw" "$disk"
rm -f "$raw"
fi
password=$(cat "$state_dir/rdp-password")
case $display in
gtk) sees="a window of QEMU, with the guest's screen in it" ;;
vnc) sees="nothing here: connect a VNC client to 127.0.0.1:5900, or RDP below" ;;
rdp) sees="nothing here: connect over RDP, below" ;;
none) sees="nothing but this terminal, which is the guest's console" ;;
esac
cat <<EOF
run-kde: starting $guest
You see $sees
Graphics $gpu_label: $gpu_why
the guest says what KWin draws with in its cheat sheet, and here once it is up
RDP 127.0.0.1:$rdp_port user komorebi password $password
ssh ssh -i $state_dir/ssh_key -p $ssh_port komorebi@127.0.0.1
Repository $repo is /repo in the guest
Quit run \`sudo poweroff\` in the guest, or press Ctrl-a x in this terminal
State $state_dir (run-kde --reset deletes it)
The guest takes about a minute to start. This terminal is the guest's console.
EOF
export NIX_DISK_IMAGE=$disk
export KOMOREBI_REPO=$repo
export RUN_KDE_STATE=$state_dir
export RUN_KDE_MEMORY=$guest_memory
export QEMU_NET_OPTS=$net_opts
export QEMU_OPTS="${qemu_opts[*]}${RUN_KDE_QEMU_OPTS:+ $RUN_KDE_QEMU_OPTS}"
for var in "${host_env[@]}"; do
# shellcheck disable=SC2163 # the variable named by what `var` holds, as NAME=value
export "$var"
done
# virtiofsd says a great deal about being run by a user and not by root, none of it a problem
export RUST_LOG=${RUST_LOG:-error}
# Windows' own RDP client, once the guest has a session: its credentials are made for this run and
# removed again at the end
rdp_helper=
cleanup() {
if [ -n "$rdp_helper" ]; then
kill "$rdp_helper" 2> /dev/null || true
cmdkey.exe /delete:TERMSRV/localhost > /dev/null 2>&1 || true
fi
}
if [ "$rdp" = 1 ]; then
if [ "$wsl" = 1 ] && command -v mstsc.exe > /dev/null 2>&1 && command -v cmdkey.exe > /dev/null 2>&1; then
trap cleanup EXIT
(
for _ in $(seq 1 300); do
if ssh -i "$state_dir/ssh_key" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=3 \
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR \
-p "$ssh_port" komorebi@127.0.0.1 'systemctl --user is-active --quiet krdp' 2> /dev/null; then
break
fi
sleep 2
done
cmdkey.exe /generic:TERMSRV/localhost /user:komorebi "/pass:$password" > /dev/null
mstsc.exe "/v:localhost:$rdp_port"
) &
rdp_helper=$!
else
echo "run-kde: --rdp needs WSL 2 with mstsc.exe and cmdkey.exe (run-kde --doctor says how); use an RDP client of your own with the address above" >&2
fi
fi
"$runner_bin" "${runner_args[@]}"