The lint job built every check that needs no virtual machine at once, and
the runner was shut down about six minutes in on both runs of 2026-10-28,
with nothing else failing before it. The checks are now a matrix of three
shards: the Rust workspace, the guests and their dry runs, and the rest.
A check that matches no other shard is built in the last one, so a new
one needs no edit. The lint job keeps just lint, and publish waits for
both.
A hosted runner of a private repository can land on a host with no
/dev/kvm, which failed one vm-fast job of a run (core-e2e, 2026-10-28)
and nothing else. The Enable KVM step now says so, and rerun-no-kvm
reruns the failed jobs, up to three attempts, when every failed job
failed on that step. A failed suite is not rerun.
The Komorebi License lets anyone distribute the source code but not binaries, so
the flake has no public binary cache and the binaries of komorebi come only from
this repository. The `build` job builds `komorebi` and `komorebic` with nix on
every run, on ubuntu-24.04 and on ubuntu-24.04-arm, and keeps them as an
artifact for 14 days; the arm64 job is `continue-on-error`.
As nix builds them, the binaries load libc, libm and libgcc_s through the
dynamic loader of the store and an RPATH into it, which only a machine with the
same nixpkgs revision in its store has. The job points them at the system's
loader at its standard path and drops the RPATH, with patchelf from the nixpkgs
flake.lock pins, then runs `--version` on the Ubuntu runner to check that they
start. Built from the locked nixpkgs (d3b13ad), the newest glibc symbol version
they need is 2.39, the glibc of Ubuntu 24.04.
The `publish` job runs on a `v*` tag, and on a scheduled or manual run on
master, once lint, vm-fast and build have passed. It zips the two binaries of
each architecture as `komorebi-<version>-<target>.zip` (an architecture whose
build failed is left out with a warning), adds schema.json, the schema of
komorebi.json, when the repository has one (it does), and writes checksums.txt
with sha256sum. The release notes are kokai's, from the commit messages since
the previous version tag, as komorebi for Mac writes them; kokai is not in
nixpkgs, so cargo installs it, pinned at 0.3.1. A tag becomes a release. master
replaces the rolling `nightly` prerelease and its tag; the old nightly tag is
moved to the commit first, so that the notes list the commits since the last
version and not since the last nightly. There is no Homebrew, winget or second
repository.
The cache key of the composite action gets the runner's architecture: the arm64
runner has the same `runner.os` as the x86_64 one, and would restore and save
the other's store.
The steps were run as written on aarch64 and x86_64 machines: the builds, whose
binaries then started in Ubuntu 24.04 root filesystems of both architectures,
and a first nightly, a later nightly and a tag release against a fake gh. GitHub
Actions has not run them yet. CONTRIBUTING.md says how a release is made, the
README where binaries come from and what the license allows, and DESIGN.md why.
The pages that said there were no prebuilt binaries (README, installation.md and
the entry on the binary cache in DESIGN.md) now say that the releases are the
only ones.
komorebi follows the tip of nixpkgs-unstable, so KWin and Plasma move under it
each time flake.lock does, and the pull request that moves the lock runs the
slow VM tier only when it is labeled. The `canary` job of ci.yaml, on the weekly
schedule and on a manual run, copies the checkout, runs `nix flake update
nixpkgs` in the copy and builds every check against the result: the checks that
need no virtual machine, listed as the lint job lists them, then the suites of
both tiers, listed from `vmTiers` as the discover job does. They run one after
the other on one KVM runner, so that komorebi and the guests are built once
against the new nixpkgs; the job may take the six hours a hosted runner allows.
When a check fails, the job opens an issue titled "Canary: a check fails against
the tip of nixpkgs-unstable" with the failed checks, the nixpkgs revision, and
the KWin version at the tip and in flake.lock, or comments on that issue if it
is open. The same report goes to the job summary on every run. The job's token
can read the contents and write issues, nothing more, and the checkout keeps no
credentials, so the repository is never changed.
The composite action takes `cache: "false"`, which the canary passes. The store
cache is keyed on flake.lock: what it restored would be the old nixpkgs, taking
disk the guests need, and what it saved would replace the store of the jobs that
do use the lock.
`just lint` runs actionlint, with shellcheck over the scripts, both from the
nixpkgs flake.lock pins, so the lint job and `just check` check the workflows.
The steps were run as written on an x86_64 machine with KVM, against a fake gh:
GitHub Actions has not run the job yet. CONTRIBUTING.md, docs/dev/vm-tests.md
and DESIGN.md say what it does and why.
`discover` asks the flake for the suites of each tier
(`legacyPackages.x86_64-linux.vmTiers`) and hands them to two matrices, so a new
directory under `nix/tests/vm/suites` is run without an edit to the workflow.
`vm-fast` runs the fast tier on every pull request, one machine per suite, as a
sandboxed `nix build` of the check. `vm-slow` runs on master, every week, and on
a pull request labeled `ci:vm-slow`; it builds the driver and runs it by hand,
outside the sandbox, so that the screenshots and journals of a failed run are
uploaded with `if: always()`.
A composite action installs nix with the store cached and, for the VM jobs,
frees disk space (a Plasma guest is a closure of about 10 GB), writes the udev
rule that makes /dev/kvm writable for the runner's user, checks it with `test
-w`, and adds `kvm` to nix's system features. KVM on hosted runners is
documented for public repositories only. Whether this private repository has it
is not known: the first run of these jobs says, and if it does not, they need a
different runner.
`lint` no longer runs `nix flake check`: on Linux that now boots every VM suite,
which a runner with no KVM cannot do. It builds the checks that need no virtual
machine, and `vm-lib`, the library's own tests.
CONTRIBUTING.md and docs/dev/vm-tests.md say what runs where.
The unit tests run against a fake desktop and cannot say whether KWin does what
the daemon asks. This adds the other half: NixOS machines with Plasma in them, a
real KWin, and a Python library to drive it.
Every directory under `nix/tests/vm/suites` is a test. The generator lists the
directory, so a new suite appears as `checks.<system>.vm-NAME` on Linux and as
`legacyPackages.<system>.vm.NAME` everywhere, with no edit to the flake. A suite
names its tier and its node and has a `test.py`;
`legacyPackages.<system>.vmTiers` lists the names of each tier as JSON for the
CI matrix, `vmGuests` the guests to build on a Linux builder, and `vmTcg` the
same suites for a machine with no KVM. `scale` multiplies every wait: 1 for KVM,
1.5 for HVF, 8 for TCG.
The headless node runs `kwin_wayland --virtual` as user units of a lingering
user, one pair of units per session (`session-bus@NAME`, `kwin-virtual@NAME`),
so a test can run several sessions with their own bus and socket. A virtual KWin
has no output to draw on, so anything that needs pixels or a pointer needs the
session node.
The library, `komorebi_vm`, has `Checks` (counted checks that fail once, at the
end) and `Kde` (start a session, run clients and tools inside it, read windows
through kdotool, poll with `wait_for`). It depends on a small `Machine` protocol
rather than on the test driver. The `vm-lib` check runs its unit tests against a
fake machine, with ruff and ty. The driver type checks each `test.py` with ty,
which is how a protocol that did not match `QemuMachine.execute` was caught.
`vm-kwin` starts a KWin with two outputs, spawns a foot window, reads it back,
starts a second session and checks that it cannot see the first.
On a Mac the guest is built on a Linux builder and the driver run directly,
because the macOS nix sandbox cannot give QEMU the hypervisor: `scripts/vm-mac`,
`just vm NAME`. docs/dev/vm-tests.md covers this, adding a test, and the
builder.
`ci.yaml` runs two jobs on GitHub-hosted runners, gated on the repository name
so that forks do not run them. `cargo-deny` checks licenses, advisories and
sources. `lint` runs `nix flake check`, which builds every check of the flake,
and the repository's own macro rules from the justfile. The triggers are the
ones komorebi for Mac uses: pull requests, pushes to master, feature and hotfix
branches, version tags, a weekly schedule and a manual run. The Nix store is
cached with cache-nix-action, which needs no account.
komorebi follows the tip of nixpkgs-unstable, so `flake.lock` moves, and KWin
with it. `update-flake-lock.yaml` opens a pull request each week with the title
`chore(deps): flake.lock: Update`; the same jobs run on it, which is what says
whether the new KWin still works.
Dependabot keeps the actions and the crates up to date, and the pull request
template carries over from komorebi for Mac. CONTRIBUTING.md describes what CI
runs.