From 20bd28d567b0c96b267e30d686683e33e10120ba Mon Sep 17 00:00:00 2001 From: m3ta-chiron Date: Mon, 11 May 2026 11:19:21 +0200 Subject: [PATCH] feat(m3-hermes): add Hermes Dashboard as systemd service with Netbird-only firewall - New hermes-dashboard.service: runs 'hermes dashboard' on 0.0.0.0:9119 - Firewall restricts port 9119 to Netbird mesh VPN range (100.64.0.0/16) - Runs as hermes user with NoNewPrivileges + ProtectSystem hardening - Depends on hermes-agent.service (starts after gateway) - Added placeholder hermes-api-server-key.age (needs real encryption on host) --- hosts/m3-hermes/services/default.nix | 1 + hosts/m3-hermes/services/hermes-dashboard.nix | 62 +++++++++++++++++++ secrets/hermes-api-server-key.age | 1 + 3 files changed, 64 insertions(+) create mode 100644 hosts/m3-hermes/services/hermes-dashboard.nix create mode 100644 secrets/hermes-api-server-key.age diff --git a/hosts/m3-hermes/services/default.nix b/hosts/m3-hermes/services/default.nix index 975b17e..0e92b1e 100644 --- a/hosts/m3-hermes/services/default.nix +++ b/hosts/m3-hermes/services/default.nix @@ -1,6 +1,7 @@ { imports = [ ./hermes-agent.nix + ./hermes-dashboard.nix ./netbird.nix ]; } diff --git a/hosts/m3-hermes/services/hermes-dashboard.nix b/hosts/m3-hermes/services/hermes-dashboard.nix new file mode 100644 index 0000000..20b93de --- /dev/null +++ b/hosts/m3-hermes/services/hermes-dashboard.nix @@ -0,0 +1,62 @@ +{ + config, + pkgs, + inputs, + ... +}: let + # Netbird mesh VPN range — dashboard only accessible from mesh peers + netbirdRange = "100.64.0.0/16"; + + # Reference the hermes-agent package from the running service config + hermesPkg = config.services.hermes-agent.package or (inputs.hermes-agent.packages.${pkgs.stdenv.hostPlatform.system}.default or pkgs.hermes-agent); +in { + # ── Hermes Dashboard systemd service ─────────────────────────────────── + # Web UI for managing Hermes Agent — sessions, config, kanban, cron, etc. + # Binds to 0.0.0.0:9119 but firewall restricts to Netbird mesh only. + systemd.services.hermes-dashboard = { + description = "Hermes Agent Web Dashboard"; + after = ["network.target" "hermes-agent.service"]; + wants = ["hermes-agent.service"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + Type = "simple"; + User = "hermes"; + Group = "hermes"; + + ExecStart = "${hermesPkg}/bin/hermes dashboard --host 0.0.0.0 --port 9119 --no-open"; + + # Environment matching the hermes-agent service + Environment = [ + "HERMES_HOME=/var/lib/hermes/.hermes" + "HERMES_MANAGED=true" + "HOME=/var/lib/hermes" + ]; + + # Security hardening (matching hermes-agent service pattern) + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = "read-only"; + ReadWritePaths = ["/var/lib/hermes" "/tmp"]; + PrivateTmp = true; + + # Restart policy + Restart = "on-failure"; + RestartSec = 5; + }; + }; + + # ── Firewall: Dashboard only from Netbird mesh ───────────────────────── + networking.firewall = { + # Use extraCommands for source-IP-restricted port (NixOS firewall + # allowedTCPPorts is all-or-nothing per port). + extraCommands = '' + # Allow Hermes Dashboard (9119/tcp) only from Netbird mesh VPN + ip46tables -A nixos-fw -p tcp --dport 9119 -s ${netbirdRange} -j nixos-fw-accept + ''; + + extraStopCommands = '' + ip46tables -D nixos-fw -p tcp --dport 9119 -s ${netbirdRange} -j nixos-fw-accept 2>/dev/null || true + ''; + }; +} diff --git a/secrets/hermes-api-server-key.age b/secrets/hermes-api-server-key.age new file mode 100644 index 0000000..48cdce8 --- /dev/null +++ b/secrets/hermes-api-server-key.age @@ -0,0 +1 @@ +placeholder