Your system is one file.
Kuma is a declarative layer over Fedora bootc.
You describe a machine in a small kuma.toml, and Kuma compiles it into a
bootable container image. Atomic updates and rollback come from bootc, and
the packages come from Fedora.
schema_version = 1
[system]
desktop = "niri" # curated sets: "niri" or "cosmic"; omit for headless
# base = "quay.io/fedora/fedora-bootc:44" # optional; see below
[user]
name = "me"
shell = "fish"
# password_hash = '$6$...' # from `kuma passwd`; applies only at creation
[packages]
rpm = ["fish", "distrobox", "tailscale"]
flatpak = ["org.mozilla.firefox"] # from Flathub, converged on boot
brew = ["ripgrep", "gh"] # CLI tools, no rebuild needed
[services]
enable = ["tailscaled.service"]
[snapshots]
enable = true # hourly read-only btrfs snapshots of /var/homeA fuller, commented version lives in
examples/, and a test keeps every example valid
against the current schema.
There is no base to name by default. With system.base unset, kuma
composes its own from Fedora's package repos, starting from Fedora's
minimal bootc manifest: bootc, systemd, the kernel, dnf, and the hardware
enablement a real machine needs. Fedora stays the package source; kuma
builds no packages and no kernels. Name a base and kuma builds on that
image instead, as any bootc image can be.
[system].firmware trims the composed base to your hardware. Unset, it
ships every vendor's, so a machine that declares nothing about its own
still boots with working GPU, wifi, and audio. LVFS firmware updates
refresh on a timer; applying them stays a deliberate fwupdmgr update.
Machine state stays out of the file by default. Timezone and hostname
belong to the machine (timedatectl, hostnamectl) and survive image
updates; pin them here only when you want every machine built from this
file to match.
NixOS and Guix own the idea: one versioned file, convergence as the only way to change anything, rollback for free. Getting there cost them an entire package universe. Kuma keeps Fedora as the package source and builds no packages and no kernels, so the declarative property arrives without an ecosystem to rebuild. Nix's purity guarantees are what you give up for that.
Universal Blue (Bluefin, Bazzite) ships the same three layers:
immutable base, flatpaks for apps, Homebrew for CLI tools. The unit of
configuration is which image you chose. Brewfiles now declare flatpaks
and formulae together, but brew bundle is a command you run rather
than a loop that runs without you, and its cleanup decides what to
remove from what is installed rather than from what it installed. Kuma
converges at boot and on a daily timer, and records what it installed,
so an app you added yourself stays yours and kuma capture offers to
write it down.
BlueBuild builds an image from a recipe and stops at the image. The
recipe never reaches the running machine. Kuma's keeps working after
install: sync converges, diff reports drift across file, image, and
machine, kuma.lock records what the last build resolved to, and
capture turns a change you made by hand into a proposal against the
declaration instead of an error to erase.
In order:
- Simple. The schema stays small and boring. Every field is a promise kept forever, so new ones have to earn their place.
- Atomic. Applying a declaration never mutates the running system: it builds an image and switches to it on next boot. Rollback is always available, and automatic when an update can't boot to a healthy system.
- Local-first.
kuma buildneeds nothing but podman. No forge account, no CI, no registry. All optional, later. - Self-describing. Every command reports where you are and ends at the legal next commands, never a dead end. The image carries the declaration it was built from, so a machine can always speak for itself.
Kuma is early. It builds, boots, and updates real hardware, and it has not
been run widely. Schema version 1 is meant to be permanent, so the fields
above are promises; everything around them can still move, and
CHANGELOG.md is where it says what moved. kuma switch
reboots you into a system image, and bootc will roll a bad one back, but
try a declaration in kuma vm before a machine you depend on.
Kuma is one self-contained binary: the wallpaper, the greeter config, and every desktop asset are compiled into it. The published build is static, so it needs nothing installed alongside it. This matters on the machines most likely to want kuma, which tend to have podman and no compiler.
This is for the machine you build from. A machine running a kuma image
already has kuma at /usr/bin/kuma, baked in by the build that made the
image, and a copy in /usr/local/bin would shadow it.
$ curl -LO https://github.com/Letdown2491/kuma-linux/releases/latest/download/kuma-x86_64-unknown-linux-musl
$ chmod +x kuma-x86_64-unknown-linux-musl
$ sudo mv kuma-x86_64-unknown-linux-musl /usr/local/bin/kumaEvery release is signed. Each one carries the cosign verify-blob command
that checks it came from this repository's release workflow.
SECURITY.md has that command, what a declaration trusts, and
where to report a vulnerability.
The latest prerelease is the same binary built from the current main,
for tracking the tree between releases.
Building it yourself needs a Rust toolchain at 1.85 or newer and a linker:
$ cargo install --git https://github.com/Letdown2491/kuma-linux --lockedKeep --locked. Without it cargo ignores the committed Cargo.lock and
resolves dependencies fresh, so you get versions nobody tested.
Cloning also gets you the example declarations and the smoke tests:
$ git clone https://github.com/Letdown2491/kuma-linux
$ cd kuma-linux && cargo install --path .kuma --version reports the commit it was built from, and says -dirty if
that tree had uncommitted changes. Worth checking when a change you just
made does not show up in the image.
What needs what. init, check, generate, and build need only
podman. switch, update, rollback, and doctor need to be running on a
bootc machine, and a kuma one already has kuma. vm and iso need KVM and
sudo.
$ kuma init # starter kuma.toml (on a kuma machine: its own declaration)
$ vim kuma.toml # declare your packages and services
$ kuma build # podman-builds localhost/kuma:latest
$ kuma switch --yes # bootc switch; takes effect on next bootWithout --yes, switch only prints what it would do.
The file stays the interface. These read it or edit it for you:
$ kuma # where this machine is, and its next moves
$ kuma add --flatpak org.mozilla.firefox # declare (--rpm / --brew too)
$ kuma remove org.mozilla.firefox # drop from whichever list declares it
$ kuma capture # declare what this machine already runs
$ kuma check # validate the declaration, build nothing
$ kuma diff # drift: kuma.toml vs image vs machine
$ kuma doctor # machine health, /etc drift, snapshots, GPU, disk
$ kuma sync # converge, and update everything installed
$ kuma snapshot # the btrfs snapshots this machine has taken
$ kuma snapshot --restore ~/notes.md # bring a path back (dry run; --yes writes)
$ kuma update --check # has the locked base moved?
$ kuma update --yes # rebuild on the latest base, stage it
$ kuma rollback --yes # boot order back to the previous deployment
$ kuma clean # reclaim dangling images, stale bases, build leftoversadd, remove, and capture preserve your comments and formatting.
check, diff, doctor, and update --check change nothing. Everything
speaks --json.
Three more exist for when you need them and never otherwise: kuma passwd
hashes a password for [user], kuma schema prints the JSON Schema for
kuma.toml, and kuma completions fish | source wires up your shell.
The verbs above are the whole interface. These explain the parts that are not obvious from them:
- How kuma behaves: why drift is a proposal rather
than an error, what
kuma.lockpins and what it only records, how/etcis merged rather than replaced, and how a bad update rolls itself back. - What a desktop contains: what
desktop = "niri"or"cosmic"installs that you didn't name, why the surprising parts are there, and what you can change. - For agents: the JSON surface, and why every response ends at the legal next commands.
- Contributing: smoke tests, booting a VM, iterating without losing state, what CI checks, and how a release is cut.
- No published images. Every image is built locally, so none is
bootc switch-able from anywhere, and no image is signed. Thekumabinary is published and signed; the images it builds are not. - No offsite backup.
[snapshots]survives a mistake, not a dead disk. Blocked on where a repository credential lives, since it cannot be the declaration. - No hibernate. A swapfile's size and
resume_offsetare properties of the installed disk, so it needs a first-boot unit rather than an image that already knows the answer. - No flatpak permission overrides. They survive image updates and are the one part of the app layer a declaration cannot see or restore.
