feat(maestro): add a nix flake and Home Manager module
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c856333e4a
commit
401afbc812
@@ -0,0 +1,136 @@
|
||||
# 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" ];
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user