···31313232- [Bazecor](https://github.com/Dygmalab/Bazecor), the graphical configurator for Dygma Products.
33333434+- [Bonsai](https://git.sr.ht/~stacyharper/bonsai), a general-purpose event mapper/state machine primarily used to create complex key shortcuts, and as part of the [SXMO](https://sxmo.org/) desktop environment. Available as [services.bonsaid](#opt-services.bonsaid.enable).
3535+3436- [scanservjs](https://github.com/sbs20/scanservjs/), a web UI for SANE scanners. Available at [services.scanservjs](#opt-services.scanservjs.enable).
35373638- [Kimai](https://www.kimai.org/), a web-based multi-user time-tracking application. Available as [services.kimai](options.html#opt-services.kimai).
···3840- [Omnom](https://github.com/asciimoo/omnom), a webpage bookmarking and snapshotting service. Available as [services.omnom](options.html#opt-services.omnom.enable).
39414042- [MaryTTS](https://github.com/marytts/marytts), an open-source, multilingual text-to-speech synthesis system written in pure Java. Available as [services.marytts](options.html#opt-services.marytts).
4343+4444+- [networking.modemmanager](options.html#opt-networking.modemmanager) has been split out of [networking.networkmanager](options.html#opt-networking.networkmanager). NetworkManager still enables ModemManager by default, but options exist now to run NetworkManager without ModemManager.
41454246- [Conduwuit](https://conduwuit.puppyirl.gay/), a federated chat server implementing the Matrix protocol, forked from Conduit. Available as [services.conduwuit](#opt-services.conduwuit.enable).
4347···6064- [mqtt-exporter](https://github.com/kpetremann/mqtt-exporter/), a Prometheus exporter for exposing messages from MQTT. Available as [services.prometheus.exporters.mqtt](#opt-services.prometheus.exporters.mqtt.enable).
61656266- [nvidia-gpu](https://github.com/utkuozdemir/nvidia_gpu_exporter), a Prometheus exporter that scrapes `nvidia-smi` for GPU metrics. Available as [services.prometheus.exporters.nvidia-gpu](#opt-services.prometheus.exporters.nvidia-gpu.enable).
6767+6868+- [InputPlumber](https://github.com/ShadowBlip/InputPlumber/), an open source input router and remapper daemon for Linux. Available as [services.inputplumber](#opt-services.inputplumber.enable).
63696470- [Buffyboard](https://gitlab.postmarketos.org/postmarketOS/buffybox/-/tree/master/buffyboard), a framebuffer on-screen keyboard. Available as [services.buffyboard](option.html#opt-services.buffyboard).
6571
···11+{
22+ config,
33+ lib,
44+ pkgs,
55+ ...
66+}:
77+let
88+ json = pkgs.formats.json { };
99+ transitionType = lib.types.submodule {
1010+ freeformType = json.type;
1111+ options.type = lib.mkOption {
1212+ type = lib.types.enum [
1313+ "delay"
1414+ "event"
1515+ "exec"
1616+ ];
1717+ description = ''
1818+ Type of transition. Determines how bonsaid interprets the other options in this transition.
1919+ '';
2020+ };
2121+ options.command = lib.mkOption {
2222+ type = lib.types.nullOr (lib.types.listOf lib.types.str);
2323+ default = null;
2424+ description = ''
2525+ Command to run when this transition is taken.
2626+ This is executed inline by `bonsaid` and blocks handling of any other events until completion.
2727+ To perform the command asynchronously, specify it like `[ "setsid" "-f" "my-command" ]`.
2828+2929+ Only effects transitions with `type = "exec"`.
3030+ '';
3131+ };
3232+ options.delay_duration = lib.mkOption {
3333+ type = lib.types.nullOr lib.types.int;
3434+ default = null;
3535+ description = ''
3636+ Nanoseconds to wait after the previous state change before performing this transition.
3737+ This can be placed at the same level as a `type = "event"` transition to achieve a
3838+ timeout mechanism.
3939+4040+ Only effects transitions with `type = "delay"`.
4141+ '';
4242+ };
4343+ options.event_name = lib.mkOption {
4444+ type = lib.types.nullOr lib.types.str;
4545+ default = null;
4646+ description = ''
4747+ Name of the event which should trigger this transition when received by `bonsaid`.
4848+ Events are sent to `bonsaid` by running `bonsaictl -e <event_name>`.
4949+5050+ Only effects transitions with `type = "event"`.
5151+ '';
5252+ };
5353+ options.transitions = lib.mkOption {
5454+ type = lib.types.listOf transitionType;
5555+ default = [ ];
5656+ description = ''
5757+ List of transitions out of this state.
5858+ If left empty, then this state is considered a terminal state and entering it will
5959+ trigger an immediate transition back to the root state (after processing side effects).
6060+ '';
6161+ visible = "shallow";
6262+ };
6363+ };
6464+ cfg = config.services.bonsaid;
6565+in
6666+{
6767+ meta.maintainers = [ lib.maintainers.colinsane ];
6868+6969+ options.services.bonsaid = {
7070+ enable = lib.mkEnableOption "bonsaid";
7171+ package = lib.mkPackageOption pkgs "bonsai" { };
7272+ extraFlags = lib.mkOption {
7373+ type = lib.types.listOf lib.types.str;
7474+ default = [ ];
7575+ description = ''
7676+ Extra flags to pass to `bonsaid`, such as `[ "-v" ]` to enable verbose logging.
7777+ '';
7878+ };
7979+ settings = lib.mkOption {
8080+ type = lib.types.listOf transitionType;
8181+ description = ''
8282+ State transition definitions. See the upstream [README](https://git.sr.ht/~stacyharper/bonsai)
8383+ for extended documentation and a more complete example.
8484+ '';
8585+ example = [
8686+ {
8787+ type = "event";
8888+ event_name = "power_button_pressed";
8989+ transitions = [
9090+ {
9191+ # Hold power button for 600ms to trigger a command
9292+ type = "delay";
9393+ delay_duration = 600000000;
9494+ transitions = [
9595+ {
9696+ type = "exec";
9797+ command = [
9898+ "swaymsg"
9999+ "--"
100100+ "output"
101101+ "*"
102102+ "power"
103103+ "off"
104104+ ];
105105+ # `transitions = []` marks this as a terminal state,
106106+ # so bonsai will return to the root state immediately after executing the above command.
107107+ transitions = [ ];
108108+ }
109109+ ];
110110+ }
111111+ {
112112+ # If the power button is released before the 600ms elapses, return to the root state.
113113+ type = "event";
114114+ event_name = "power_button_released";
115115+ transitions = [ ];
116116+ }
117117+ ];
118118+ }
119119+ ];
120120+ };
121121+ configFile = lib.mkOption {
122122+ type = lib.types.path;
123123+ description = ''
124124+ Path to a .json file specifying the state transitions.
125125+ You don't need to set this unless you prefer to provide the json file
126126+ yourself instead of using the `settings` option.
127127+ '';
128128+ };
129129+ };
130130+131131+ config = lib.mkIf cfg.enable {
132132+ services.bonsaid.configFile =
133133+ let
134134+ filterNulls =
135135+ v:
136136+ if lib.isAttrs v then
137137+ lib.mapAttrs (_: filterNulls) (lib.filterAttrs (_: a: a != null) v)
138138+ else if lib.isList v then
139139+ lib.map filterNulls (lib.filter (a: a != null) v)
140140+ else
141141+ v;
142142+ in
143143+ lib.mkDefault (json.generate "bonsai_tree.json" (filterNulls cfg.settings));
144144+145145+ # bonsaid is controlled by bonsaictl, so place the latter in the environment by default.
146146+ # bonsaictl is typically invoked by scripts or a DE so this isn't strictly necesssary,
147147+ # but it's helpful while administering the service generally.
148148+ environment.systemPackages = [ cfg.package ];
149149+150150+ systemd.user.services.bonsaid = {
151151+ description = "Bonsai Finite State Machine daemon";
152152+ documentation = [ "https://git.sr.ht/~stacyharper/bonsai" ];
153153+ wantedBy = [ "multi-user.target" ];
154154+ serviceConfig = {
155155+ ExecStart = lib.escapeShellArgs (
156156+ [
157157+ (lib.getExe' cfg.package "bonsaid")
158158+ "-t"
159159+ cfg.configFile
160160+ ]
161161+ ++ cfg.extraFlags
162162+ );
163163+ Restart = "on-failure";
164164+ RestartSec = "5s";
165165+ };
166166+ };
167167+ };
168168+}
···276276 build_attr = BuildAttr.from_arg(args.attr, args.file)
277277 drv = nix.build(attr, build_attr, **build_flags, no_out_link=True)
278278 except CalledProcessError:
279279- logger.warning("could not build a newer version of nixos-rebuild")
279279+ logger.warning(
280280+ "could not build a newer version of nixos-rebuild, "
281281+ + "using current version"
282282+ )
280283281284 if drv:
282285 new = drv / f"bin/{EXECUTABLE}"
···284287 if new != current:
285288 logging.debug(
286289 "detected newer version of script, re-exec'ing, current=%s, new=%s",
287287- argv[0],
290290+ current,
288291 new,
289292 )
290293 # Manually call clean-up functions since os.execve() will replace
291294 # the process immediately
292295 cleanup_ssh()
293296 tmpdir.TMPDIR.cleanup()
294294- os.execve(new, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"})
297297+ try:
298298+ os.execve(new, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"})
299299+ except Exception:
300300+ # Possible errors that we can have here:
301301+ # - Missing the binary
302302+ # - Exec format error (e.g.: another OS/CPU arch)
303303+ logger.warning(
304304+ "could not re-exec in a newer version of nixos-rebuild, "
305305+ + "using current version"
306306+ )
307307+ logger.debug("re-exec exception", exc_info=True)
308308+ # We already run clean-up, let's re-exec in the current version
309309+ # to avoid issues
310310+ os.execve(current, argv, os.environ | {"_NIXOS_REBUILD_REEXEC": "1"})
295311296312297313def execute(argv: list[str]) -> None: