{ 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 ''; }; }