Files
komorebi/nix/linux/dev/plasma-vm.nix
T
LGUG2Z f01853ceca refactor(repo): move the KDE and macOS nix files under nix/ (pure move)
komorebi-for-kde/nix/ -> nix/linux/
komorebi-for-mac/nix/ -> nix/macos/

The nix expressions of the platforms (modules, option files, VM test suites, kwin effects,
the darwin module) sit next to the existing nix/workspace-src.nix. Renames only (252
files); the flakes that import them are replaced by one root flake later in this stack, so
nix does not evaluate at this revision.
2026-11-05 15:03:27 -08:00

232 lines
7.7 KiB
Nix

{
config,
lib,
pkgs,
hostPkgs,
...
}:
# A NixOS Plasma 6 guest to develop and test against a real KWin, with komorebi installed by the
# flake's module, `services.komorebi`, from the flake's packages. The flake builds it for an Apple
# silicon Mac, which runs it under QEMU with HVF, and for a Linux machine, which runs it under QEMU
# with KVM; docs/dev/utm.md says how to build and run it. The system of the guest and the packages
# of the machine that runs QEMU (`hostPkgs`) come from the flake, so that one file serves them all.
#
# The guest mounts the repository at /repo, and keeps its disk in the directory it is started
# from, which is best .vm (gitignored):
#
# nix build .#nixosConfigurations.plasma-vm.config.system.build.vm -o .vm/result-plasma-vm
# cd .vm && KOMOREBI_REPO=$PWD/.. ./result-plasma-vm/bin/run-plasma-vm-vm
#
# The console, in the terminal that runs the guest, is logged in as the user `komorebi`. ssh is
# forwarded to localhost:2222 (2223 for the session), for the keys in ~/.ssh/authorized_keys of
# that user, which the guest's disk keeps.
#
# The session variant is the one to live in: Plasma logs in by itself, the module's unit runs
# komorebi in it, and it can be reached over RDP (krdp.nix).
let
cfg = config.komorebi.plasmaVm;
# The guest's one display, a virtio-gpu, so that KWin has a DRM device to draw on. The machine of
# an aarch64 guest has no display of its own. The machine of an x86_64 guest has a VGA, which QEMU
# adds unless it is told not to, and KWin would draw on it as well as on the virtio-gpu: a second
# head. An x86_64 guest gets `virtio-vga`, a virtio-gpu that is also the VGA, in its place.
gpu =
args:
if pkgs.stdenv.hostPlatform.isx86_64 then
[
"-vga none"
"-device virtio-vga${args}"
]
else
[ "-device virtio-gpu-pci${args}" ];
in
{
imports = [ ./krdp.nix ];
options.komorebi.plasmaVm = {
session = lib.mkEnableOption ''
a full autologin Plasma Wayland session (sddm + plasmashell), in addition to the headless
`kwin_wayland --virtual` workflow that is always available from the console and over ssh
'';
display = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether the runner gives the guest its display device, a virtio-gpu. A guest that sets this
to false is started with a display device of its own choosing, which can then differ from
one start to the next: `run-kde` starts the demo guest (run-kde.nix) with a software
`virtio-vga` or with a `virtio-vga-gl` that renders on the host's GPU, from one closure and
one disk.
'';
};
};
config = lib.mkMerge [
{
system.stateVersion = "26.05";
networking.hostName = "plasma-vm";
services.desktopManager.plasma6.enable = true;
programs.dconf.enable = true;
# mesa (llvmpipe over the virtio-gpu render node) so kwin can composite with GL
hardware.graphics.enable = true;
fonts.enableDefaultPackages = true;
# komorebi and komorebic, from the flake's packages; the unit starts with a Plasma session
services.komorebi.enable = true;
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
PermitRootLogin = "no";
};
};
# the way in before any key is in ~/.ssh/authorized_keys: there is no password anywhere
services.getty.autologinUser = "komorebi";
users.users.komorebi = {
isNormalUser = true;
uid = 1000;
extraGroups = [
"wheel"
"video"
"render"
];
# keep a user manager (and XDG_RUNTIME_DIR) alive across ssh sessions
linger = true;
};
security.sudo.wheelNeedsPassword = false;
nix.settings.experimental-features = [
"nix-command"
"flakes"
];
environment.systemPackages = with pkgs; [
# clients to tile: Qt/KDE, GTK3/4 (CSD), Electron-ish via firefox, plain wayland, XWayland
kdePackages.konsole
kdePackages.dolphin
kdePackages.kate
kdePackages.spectacle
firefox
gnome-text-editor
gnome-calculator
foot
xterm
# poking at KWin
kdePackages.qttools # qdbus6
dbus
jq
kdotool
strace
# installing plasma-utm onto a disk image (docs/dev/utm.md)
nixos-install-tools
dosfstools
parted
e2fsprogs
# building komorebi from /repo in the guest
rustc
cargo
gcc
pkg-config
git
rsync
htop
];
environment.sessionVariables = {
# kwin refuses to load animating effects (including the stock scripted ones) under software
# rendering; llvmpipe is all this vm has
KWIN_EFFECTS_FORCE_ANIMATIONS = "1";
# always build in guest-local storage; /repo is a 9p mount and is slow for target dirs
CARGO_TARGET_DIR = "/home/komorebi/target";
};
virtualisation.vmVariant = {
virtualisation = {
host.pkgs = hostPkgs;
qemu.forceAccel = true;
cores = 6;
memorySize = 8192;
diskSize = 20 * 1024;
graphics = false;
# no display, but a gpu so there is a drm render node
qemu.options = lib.mkIf (cfg.display && !cfg.session) (gpu "");
forwardPorts = [
{
from = "host";
host.address = "127.0.0.1";
host.port = if cfg.session then 2223 else 2222;
guest.port = 22;
}
];
sharedDirectories.repo = {
# the runner says so when it is not set
source = "\${KOMOREBI_REPO:?set KOMOREBI_REPO to the root of the repository}";
target = "/repo";
writable = true;
};
};
};
}
(lib.mkIf cfg.session {
# a separate disk image (named after the host) and ssh port so both vms can run at once
networking.hostName = lib.mkForce "plasma-vm-session";
services.displayManager = {
sddm = {
enable = true;
wayland.enable = true;
};
autoLogin = {
enable = true;
user = "komorebi";
};
defaultSession = "plasma";
};
# kwin drives virtio-gpu through drm on the primary node; that is the only place mesa's software
# rasterizer can allocate buffers (see docs/dev/PLATFORM.md: the headless --virtual backend
# cannot)
virtualisation.vmVariant.virtualisation.qemu.options = lib.mkIf cfg.display (
gpu ",xres=1920,yres=1080"
);
# an idle lock screen or dimmed display changes how kwin animates and what screenshots show
environment.etc."xdg/kscreenlockerrc".text = ''
[Daemon]
Autolock=false
LockOnResume=false
'';
# and so does a display that has gone to sleep: with the output off, KWin sends no frame
# callbacks, so clients stop answering resizes and every window stays where it was while komorebi
# asks for another place (found on the UTM guest after it sat idle: black screenshots, nothing tiled)
environment.etc."xdg/powerdevilrc".text = ''
[AC][Display]
DimDisplayWhenIdle=false
TurnOffDisplayWhenIdle=false
[AC][SuspendAndShutdown]
AutoSuspendAction=0
'';
komorebi.krdp.enable = true;
# only the host's loopback reaches it
virtualisation.vmVariant.virtualisation.forwardPorts = [
{
from = "host";
host.address = "127.0.0.1";
host.port = 3389;
guest.port = 3389;
}
];
})
];
}