TL;DR: Extend existing switch-to-configuration script with a new flag --varlink-stdio which exposes an Varlink API over stdout/stdin.
Audience
This is a design discussion for people who work on the activation pipeline, Varlink or any deployment tool interacting with NixOS. Drive-by comments are not helpful there.
Motivation
switch-to-configuration (abbreviated stc here) is one of the most well-known “public” APIs of the NixOS project, derivatives tend to reproduce similar concepts to enable the activation process of their generations (if they have generations). It only applies to store-based NixOS, image-based NixOS do not make use of this concept.
After a recent rewrite in Rust, stc remains a program that many pieces of the ecosystem interact with via free-text stderr and exit codes.
Structured APIs in Linux distributions have always existed via D-Bus but certain programs cannot easily expose a D-Bus API due to the requirement of a centralized broker. Recently, systemd has started to adopt Varlink, a broker-free approach to system IPCs and has converted a significant amount of their APIs to it (varlinkctl can be used to play with this).
The fact that stc is not a structured API cause at least three problems:
- No machine-readable progress, e.g. it is only possible to stream back the contents of stc.
- Deployment tools (generic term to refer to things like nixos-rebuild but also out-of-tree deployment tools) reimplements their ways of knowing how to parse the results of a dry-run and so on.
- Passing more structured information to tools that stc calls itself, e.g. the bootloader installation phase or the switch inhibitor hook.
This RFC proposes that we explore a Varlink stdio interface so that deployers can consume the same “binary/script” that the toplevel’s stc consumes, instead of screen scraping it.
Goals
- Add an optional Varlink entrypoint to
switch-to-configuration-ng, without removing the existing CLI surface. - Provide a typed, streamable public API contract for
Switch,DryActivate,Boot,Checkwith structuredProgressandResultand errors
Non-goals
- No long-lived or socket-activated
org.nixos.SwitchToConfigurationVarlink service (See the “Why not a socket?” below). - No fancy auth/authnz for allowing unprivileged stc.
- No persistent
Planthat can be replayed. - No change to the activation script contract.
$out/activateremains a shell script.
Why not a socket?
A long-lived (or even socket-activated) Varlink service would mean that switching to a new NixOS system would reuse the code of an arbitrary older generation (exactly the one that the previous stc has put in place). The design of stc is one that you use the newer stc to stc into a newer generation, therefore, it’s not possible to start a stc server in systemd and have a mechanism to reexec remotely the stc server via the newer generation stc client.
Additionally, stc has requirements to operate in systemdless environments for recovery reasons. For this, a stdio transport is the simplest.
If people wants to be able to observe ongoing stc at a global level for desktop-level or fleet-level observability, it should either be done at the deployment tool level or stc can register a standard event socket location which talks a standard event vocabulary, this is out of scope for this proposal.
Expected approx. implementation plan
- Move most of the
main.rsinto alib.rs - Add a dependency on
zlinktostc-ng(is it a problem? If so, we can have our own impl of Varlink “low-tech” easily). - Add
--varlink-stdioflag or subcommand + an IDL file shipped as part of the output. The CLI remains untouched. - Have
nixos-rebuild-ngconsume the Varlink output. - Have the
stcclient consume the Varlink stdio.
cc @arianvp @flokli @nikstur @ElvishJerricco @Ramses (cannot find his @) for the systemd maintainers.
cc @jmbaur for the author of stc-ng (thank you!)
cc @k0kada for the author of nixos-rebuild-ng (thank you!)
cc @lewo for comin.
If there’s no objection in 2 weeks, I will start with the trivial refactors and open PRs in nixpkgs.