# Home Manager module: `services.maestro`. # # Maestro owns the user's interactive shells and tmux sessions, so both units # are *user* services, not system ones — same trust boundary as the tmux server # they wrap. { self, ... }: { flake.homeManagerModules.default = { config, lib, pkgs, ... }: let cfg = config.services.maestro; tomlFormat = pkgs.formats.toml { }; # A user unit inherits almost no PATH. Give the daemon the profile dirs a # login shell would have, so the sessions it spawns can find the user's # own tools. profilePath = lib.concatStringsSep ":" [ "%h/.local/bin" "%h/.nix-profile/bin" "/etc/profiles/per-user/%u/bin" "/run/current-system/sw/bin" ]; in { options.services.maestro = { enable = lib.mkEnableOption "Maestro — installs the CLI/TUI and, unless disabled, runs the daemon"; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.maestro; defaultText = lib.literalExpression "maestro.packages.\${system}.maestro"; description = "The unified `maestro` binary (CLI + daemon + TUI)."; }; daemon.enable = lib.mkOption { type = lib.types.bool; default = true; description = "Run `maestro daemon` as a systemd user service."; }; web = { enable = lib.mkEnableOption "the `maestro-web` browser UI as a systemd user service"; package = lib.mkOption { type = lib.types.package; default = self.packages.${pkgs.stdenv.hostPlatform.system}.maestro-web; defaultText = lib.literalExpression "maestro.packages.\${system}.maestro-web"; description = "The `maestro-web` binary, with the SPA bundle embedded."; }; }; settings = lib.mkOption { type = tomlFormat.type; default = { }; example = lib.literalExpression '' { daemon.bind_addr = "127.0.0.1:7070"; web.bind_addr = "0.0.0.0:8081"; } ''; description = '' Contents of `~/.config/maestro/config.toml`, in the schema `maestro-config` defines. Every field has a default, so `{}` is valid and writes no file. There is no auth layer — bind the web UI to a LAN/VPN address only on a trusted segment, never a public IP. ''; }; }; config = lib.mkIf cfg.enable { home.packages = [ cfg.package ] ++ lib.optional cfg.web.enable cfg.web.package; xdg.configFile."maestro/config.toml" = lib.mkIf (cfg.settings != { }) { source = tomlFormat.generate "maestro-config.toml" cfg.settings; }; systemd.user.services = lib.mkMerge [ (lib.mkIf cfg.daemon.enable { maestro-daemon = { Unit = { Description = "maestro daemon — tmux wrapper + session API"; After = [ "network.target" ]; # MUST stay 0: a fast-crashing daemon would burn the default # 5-starts-per-10s budget and park the unit in `failed` until a # manual `systemctl --user reset-failed`. StartLimitIntervalSec = 0; }; Service = { Type = "simple"; WorkingDirectory = "%h"; ExecStart = "${lib.getExe cfg.package} daemon run"; Environment = [ "PATH=${profilePath}" # Keeps the daemon on the same tmux server the user's shell # and TUI use, instead of a second one under /tmp. "TMUX_TMPDIR=%t" ]; Restart = "always"; RestartSec = 2; # If this unit boots before any shell, the shared tmux server # lands in its cgroup; the default control-group kill would take # every session down on restart. KillMode = "process"; }; Install.WantedBy = [ "default.target" ]; }; }) (lib.mkIf cfg.web.enable { maestro-web = { Unit = { Description = "maestro-web — browser UI for the local maestro daemon"; After = [ "network.target" ] ++ lib.optional cfg.daemon.enable "maestro-daemon.service"; Wants = lib.optional cfg.daemon.enable "maestro-daemon.service"; StartLimitIntervalSec = 0; }; Service = { Type = "simple"; WorkingDirectory = "%h"; ExecStart = lib.getExe cfg.web.package; Environment = [ "PATH=${profilePath}" ]; Restart = "always"; RestartSec = 2; }; Install.WantedBy = [ "default.target" ]; }; }) ]; }; }; }