Ships `maestro`, `maestro-web` and the SPA bundle as flake packages, plus a `services.maestro` Home Manager module that runs the daemon and the web UI as systemd user services. `maestro-web`'s build.rs now honours MAESTRO_WEB_DIST and hands the resolved bundle path to `include_dir!` through MAESTRO_WEB_BUNDLE_DIR. Without it the Nix build would have to run `pnpm install` mid-compile, which the sandbox has no network for. Unset, the pnpm path is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
137 lines
5.0 KiB
Nix
137 lines
5.0 KiB
Nix
# 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" ];
|
|
};
|
|
})
|
|
];
|
|
};
|
|
};
|
|
}
|