The browser reached the daemon through maestro-web, a second binary that served the SPA and reverse-proxied /api/* — two TCP connections and a WebSocket frame re-encode on every keystroke and every byte of PTY output. On one machine that hop buys nothing. The daemon now embeds the bundle and serves it off its own listener, behind a default-on `web` feature. `--no-default-features` builds without pnpm or node; MAESTRO_WEB_DIST still takes a prebuilt bundle. The router's fallback answers an unmatched /api/* with 404 rather than the SPA shell, so a client expecting JSON gets a status it can read instead of HTML. Removes maestro-web and the [web] config section. An existing [web] block is ignored, not rejected. The installer deletes a leftover maestro-web.service on upgrade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
116 lines
4.2 KiB
Nix
116 lines
4.2 KiB
Nix
# Home Manager module: `services.maestro`.
|
|
#
|
|
# Maestro owns the user's interactive shells and tmux sessions, so the daemon
|
|
# is a *user* service, not a system one — same trust boundary as the tmux
|
|
# server it wraps. It serves the browser UI too; there is no second unit.
|
|
{ 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
|
|
{
|
|
imports = [
|
|
(lib.mkRemovedOptionModule [ "services" "maestro" "web" "enable" ] ''
|
|
The daemon serves the browser UI off its own listener now, so there is
|
|
no second unit to enable. Point a browser at
|
|
`services.maestro.settings.daemon.bind_addr` instead.
|
|
'')
|
|
(lib.mkRemovedOptionModule [ "services" "maestro" "web" "package" ] ''
|
|
The `maestro-web` package is gone; the SPA is embedded in the
|
|
`maestro` binary under its default-on `web` cargo feature.
|
|
'')
|
|
];
|
|
|
|
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.";
|
|
};
|
|
|
|
settings = lib.mkOption {
|
|
type = tomlFormat.type;
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
daemon.bind_addr = "127.0.0.1:7070";
|
|
}
|
|
'';
|
|
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. `daemon.bind_addr` is also the address the
|
|
browser UI is served on. There is no auth layer — bind it to a
|
|
LAN/VPN address only on a trusted segment, never a public IP.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
home.packages = [ cfg.package ];
|
|
|
|
xdg.configFile."maestro/config.toml" = lib.mkIf (cfg.settings != { }) {
|
|
source = tomlFormat.generate "maestro-config.toml" cfg.settings;
|
|
};
|
|
|
|
systemd.user.services = lib.mkIf cfg.daemon.enable {
|
|
maestro-daemon = {
|
|
Unit = {
|
|
Description = "maestro daemon — tmux wrapper + session API + browser UI";
|
|
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" ];
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|