kuma v0.12.0
This release is about the difference between a thing being true and a thing being checked. It exists because the day v0.11.0 shipped, a machine stopped converging and only a person reading a journal could tell.
Three of its claims were true and unchecked: that a converger recovers on its own, that the walkthrough a stranger types still describes the tool, and that a declaration written for any released kuma keeps working. All three are now commands that pass or fail.
It also grew a face. kuma menu was meant for a later release and was built on a branch to keep this one honest; it earned its way in by being finished, and by being the first thing here a person sees rather than a thing they can check. The rule that governs it is the same one this release is about: a menu entry is one keystroke with no diff and no pause, so it may change machine state immediately and may never write your declaration.
Added
kuma menuopens a menu in the desktop's own launcher: apps, connect, declaration, system, notifications, power. Bound toMod+Don niri, which is the key that used to open the bare launcher; the menu lists applications itself, so two keys for one job would have left one of them showing strictly less. The stock bind is grepped for before it is replaced, so a niri release that renames it fails the build rather than shipping media whose main key does nothing.
The desktop kuma assembles had no face of its own, so every device-level setting was somebody else's control panel and nothing at all owned the system. This is not a settings application, which is a desktop environment's job and never finishes; it is a tree rendered by fuzzel --dmenu, themed by the fuzzel config kuma already ships, in the launcher the clipboard picker already uses. Nothing new is installed and nothing new is drawn.
Applications are rows in it. Apps was a group holding one entry that opened a second launcher on top of the first; now the menu lists the applications itself, so typing firefox at the top finds Firefox the same way reboot finds reboot. That means implementing the desktop entry spec rather than gesturing at it, and the parts people skip are the parts that bite: on the machine this was written on, 11 of 32 entries are NoDisplay=true and must never appear, and 12 carry field codes (%U, %F) that arrive as literal arguments if nothing strips them. TryExec, Hidden, OnlyShowIn, NotShowIn, Terminal and the spec's quoting are all honoured, entries earlier in the search path shadow later ones, and launch counts are kept so the list is not alphabetical forever.
kuma menu --list prints the rows instead of drawing them, marking which are on screen at rest and which only typing reaches. How to see the menu over ssh, in a VM with no session, or when wondering why a row is missing. scripts/smoke.sh runs it on every installed machine it boots, which is the only automated thing that touches the menu at all.
Descending narrows what is shown, never what can be found. Opening a group shows that group's rows and a way back; every other row is still in the list, so someone who opens Connect and then remembers they wanted to reboot types reboot and gets it, rather than finding that the menu quietly became a smaller menu.
It opens on its groups and searches every row. A launcher can only match against the lines it was handed, so a menu of submenus cannot be searched: typing reboot at the top of one matches nothing and the person who knew what they wanted navigates anyway. Every row is in one list, the groups come first, and the window is sized to exactly their number, so browsing sees six sections while typing reaches all of them. reboot finds the row, power finds the group and its five. A group's row descends into just its own.
The build gives the menu its own icon theme. Adwaita's symbolic icons are the right drawings and the wrong colour: each hardcodes a near-black fill, fuzzel renders the file as it is, and kuma's launcher is #0e1626, so they drew invisibly. Every icon the menu names is now copied and repainted in the launcher's own foreground into /usr/share/icons/kuma, which also buys alignment that text glyphs cannot: fuzzel's icon column is a fixed-width slot, while a proportional face gives every glyph a different width and leaves the labels ragged.
Any hex is rewritten rather than one known value, because the twenty files carry three different darks and a sed for the common one would have left two icons invisible, which reads as a glitch rather than as a bug. fill="none" is left alone, since it means transparent. Then the step checks its own work and fails the build naming the file, because a generator that cannot say whether it worked is how the icons went out invisible the first time. The colour is asserted equal to assets/fuzzel.ini's, so the icons and the text they sit beside cannot part company.
A row that prints keeps its window. A terminal launched as kitty -e <command> closes the moment the command exits, so Health would have shown a sudo prompt and vanished as the password was finished, and every row that runs for a second is exactly the one whose output is never read. The command runs through a shell that waits, which also reports a non-zero exit the closing window would otherwise have swallowed.
Selection comes back as an index rather than as text, and fuzzel is asked for --only-match, so a typo that matches nothing cannot be mistaken for a choice. The window is sized to its longest row rather than to the app launcher's width, and a row inside its own group drops the prefix that the group heading already said.
The menu never writes the declaration. kuma capture is the one deliberate path from what the machine has to what the file says, and its safety is the ceremony: dry run, review, confirm. A menu entry is one keystroke with no diff and no pause, so a second writer would not add convenience, it would remove the only thing that made the first one safe. Declaration entries open the file in your editor or run a verb that asks for itself. Machine state (lock, suspend, notification mode) changes immediately, because that is the half a launcher is better at than a panel.
Entries appear only when their program is present, so the menu offers the terminal tool where there is one, the graphical tool where there is not, and no row at all where there is neither. A group whose every entry is missing is absent rather than empty. Nothing runs as root to draw the menu, so opening it never prompts.
-
Suspend, reboot and power off have a menu. Stock niri binds a lock and a quit and nothing else, so on a laptop the only way to suspend from the desktop was a terminal.
-
NetworkManager-tuion the niri desktop. It is what the menu offers for the network in preference to the graphical editor, because a terminal program inherits the terminal's theme rather than arriving as a window from another system, and it is the only network tool left on a machine whose session will not start. -
kuma doctorreports a Flatpak permission override that points at nothing. A machine that was another distribution first can carry an override symlink into a directory that distribution shipped and kuma does not have, and it survives in/varacross every image switch because nothing has ever looked at it. Graded a warning: flatpak tolerates it and the machine is not broken, but it is a statement about an app's permissions that is not true, and the declaration cannot see it yet. Regular override files are left alone; they are somebody's settings, whoever wrote them. -
kuma doctorreports a machine that has stopped converging. It already graded whether the last convergence attempt failed; it could not see a machine whose last attempt succeeded three weeks ago, because "last run succeeded" and "timer active" are both true of a machine that quietly stopped converging. Asked only of the convergers a timer runs again, which is what makes the age meaningful: they run at boot and on a daily timer that catches up after a machine was asleep, so seven days without one is seven missed firings and every boot in between. The account converger runs at boot only, so its age would measure uptime and it is not asked. -
Every declaration this project has shipped as an example is now tested against the kuma being built. Schema v1 is claimed permanent, and nothing held anyone to it: a renamed field or a changed default would have been found by somebody upgrading a machine whose declaration predated it. The corpus is every distinct example shape from every release, and an example that changes has to be recorded there before it ships. This covers old declarations on new kuma; the other direction, a newer declaration read by an older kuma, is still a hard error and still undecided.
-
Every command the documentation tells you to run is now checked against the command line kuma actually has. Using the machine proves the tool works and never reads the docs, so a renamed flag is noticed the moment somebody types it while the page still naming the old one rots unread. This catches the words, not the behaviour: what the commands do is proven by CI where CI can reach it, and the walkthrough now carries a record of which of its commands that is true of.
-
AppImages run on a kuma machine without a declaration naming anything. An AppImage is a squashfs its runtime mounts over FUSE before any of its own code runs, and Fedora ships only FUSE 3, so a downloaded AppImage failed at
dlopen(): error loading libfuse.so.2on a machine that was otherwise complete. Both halves of FUSE 2 are in every image now: the library the runtime loads, and the setuid helper that library mounts with. They are separate packages and neither requires the other, so naming one looks like the fix and leaves a different error in the same place. Not gated on a desktop, because the packages are under a megabyte together and the failure is one somebody meets by opening a file they downloaded.
Fixed
-
Every verb is named somewhere a person reads. One test walked the docs and checked each command against the CLI; nothing walked it the other way, which is the direction a new verb goes missing in.
kuma menuwas written, tested, and documented nowhere while every test passed. The exception is named rather than filtered by a rule:kuma boot-titlesis hidden because a systemd unit runs it and nothing asks a person to. -
A keybinding that spawns a kuma verb now names one that exists. The binds are strings in a file the compiler never reads, so a renamed verb left a key that did nothing and said nothing; a test walks every
spawn "kuma"in the shipped binds and checks the verb against the CLI's own definition. -
Publishing an image now runs the checks that install and boot it. They read the moving tag a publish moves, so running them beforehand only re-tested the previous release, and keeping the order right was something a person had to remember. They run after the publish, so nothing they find can unpublish anything; what they can do is say so the same day.
-
One app's broken download no longer fails Flatpak convergence forever. A remote can serve a static delta whose decompressed part is larger than ostree will accept, and the limit is computed per machine, so the same update fails byte-identically on every retry: Flathub's Firefox did this, the unit failed six times, systemd stopped trying, and the machine sat unconverged until someone read the journal and ran flatpak by hand. Both download paths now retry without static deltas, which trades the delta's bandwidth saving for a whole download on the path that already failed and changes nothing anywhere else. The retry covers the declared-install pass as well as the update, because
--or-updatemeans the install is where an app already present takes its new version, and that is the pass that failed. -
kuma syncrecovers a converger that spent its start limit. The units are configured to retry a handful of times before systemd gives up, and a unit in that state refusessystemctl startoutright.kuma doctorprintskuma syncas the fix for a failed converger, so the prescribed fix was refused by systemd rather than run, and the only way out was knowing to runsystemctl reset-failedfirst. Sync now resets before it starts. -
kuma doctorquotes what a failed converger actually said.last run: exit-codeis true and says nothing a person can act on; the sentence naming the cause was onejournalctlaway, for anyone who knew to look. The line now carries it, read from the failed run's own output rather than filtered out of the unit's journal by string, so systemd's own "Failed to start" is never mistaken for the service's explanation of why. -
The boot menu names the version it boots. ostree rewrites a boot entry only when the kernel or the kernel arguments move, and a kuma release moves neither: the lock pins the base digest, so a rebuild reuses the same composed base and the same kernel. Every deploy therefore rotated the deployments underneath entries whose titles stayed where they were, and each entry ended up naming the version that used to hold its slot. A machine booted into 0.12.0 offered
Kuma 0.11.0as its default andKuma 0.10.0as its rollback. The order was always right, so the menu booted what it should; it named all of it wrong, and it does that at the one moment the menu is what somebody is reading, which is when the machine will not come up far enough to runkuma rollback. Each entry now takes its title from the deployment its ownostree=argument points at, rewritten at boot and again after the deployments rotate at shutdown, andkuma doctorgrades the result rather than only naming the problem.
Static x86_64 binary, no runtime dependencies.
$ curl -LO https://github.com/Letdown2491/kuma-linux/releases/download/v0.12.0/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/kumaVerify it came from this workflow:
$ cosign verify-blob \
--bundle kuma-x86_64-unknown-linux-musl.bundle \
--certificate-identity-regexp '^https://github.com/Letdown2491/kuma-linux/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
kuma-x86_64-unknown-linux-muslBuilt from 061a975e48dfdd2d67910c24c98f6fc093b33cff.