{ config, lib, pkgs, ... }: let cfg = config.services.lab-monitoring; mkHttpFlags = httpCfg: lib.concatStringsSep " " ([ "--transport http" "--http-address '${httpCfg.address}'" "--http-endpoint '${httpCfg.endpoint}'" "--session-ttl '${httpCfg.sessionTTL}'" ] ++ lib.optionals (httpCfg.allowedOrigins != []) ( map (origin: "--allowed-origins '${origin}'") httpCfg.allowedOrigins ) ++ lib.optionals httpCfg.tls.enable [ "--tls-cert '${httpCfg.tls.certFile}'" "--tls-key '${httpCfg.tls.keyFile}'" ]); in { options.services.lab-monitoring = { enable = lib.mkEnableOption "Lab Monitoring MCP server"; package = lib.mkPackageOption pkgs "lab-monitoring" { }; prometheusUrl = lib.mkOption { type = lib.types.str; default = "http://localhost:9090"; description = "Prometheus base URL."; }; alertmanagerUrl = lib.mkOption { type = lib.types.str; default = "http://localhost:9093"; description = "Alertmanager base URL."; }; lokiUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Loki base URL. When set, enables log query tools (query_logs, list_labels, list_label_values)."; }; lokiUsername = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Username for Loki basic authentication."; }; lokiPasswordFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Path to a file containing the password for Loki basic authentication. Recommended over storing secrets in the Nix store."; }; enableSilences = lib.mkOption { type = lib.types.bool; default = false; description = "Enable the create_silence tool (write operation, disabled by default)."; }; http = { address = lib.mkOption { type = lib.types.str; default = "127.0.0.1:8084"; description = "HTTP listen address for the MCP server."; }; endpoint = lib.mkOption { type = lib.types.str; default = "/mcp"; description = "HTTP endpoint path for MCP requests."; }; allowedOrigins = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Allowed Origin headers for CORS."; }; sessionTTL = lib.mkOption { type = lib.types.str; default = "30m"; description = "Session TTL for HTTP transport."; }; tls = { enable = lib.mkEnableOption "TLS for HTTP transport"; certFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Path to TLS certificate file."; }; keyFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "Path to TLS private key file."; }; }; }; openFirewall = lib.mkOption { type = lib.types.bool; default = false; description = "Whether to open the firewall for the MCP HTTP server."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = !cfg.http.tls.enable || (cfg.http.tls.certFile != null && cfg.http.tls.keyFile != null); message = "services.lab-monitoring.http.tls: both certFile and keyFile must be set when TLS is enabled"; } ]; systemd.services.lab-monitoring = { description = "Lab Monitoring MCP Server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; environment = { PROMETHEUS_URL = cfg.prometheusUrl; ALERTMANAGER_URL = cfg.alertmanagerUrl; } // lib.optionalAttrs (cfg.lokiUrl != null) { LOKI_URL = cfg.lokiUrl; } // lib.optionalAttrs (cfg.lokiUsername != null) { LOKI_USERNAME = cfg.lokiUsername; }; script = let httpFlags = mkHttpFlags cfg.http; silenceFlag = lib.optionalString cfg.enableSilences "--enable-silences"; in '' ${lib.optionalString (cfg.lokiPasswordFile != null) '' export LOKI_PASSWORD="$(< "$CREDENTIALS_DIRECTORY/loki-password")" ''} exec ${cfg.package}/bin/lab-monitoring serve ${httpFlags} ${silenceFlag} ''; serviceConfig = { Type = "simple"; DynamicUser = true; Restart = "on-failure"; RestartSec = "5s"; } // lib.optionalAttrs (cfg.lokiPasswordFile != null) { LoadCredential = [ "loki-password:${cfg.lokiPasswordFile}" ]; } // { # Hardening NoNewPrivileges = true; ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; PrivateDevices = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; MemoryDenyWriteExecute = true; LockPersonality = true; }; }; networking.firewall = lib.mkIf cfg.openFirewall (let addressParts = lib.splitString ":" cfg.http.address; port = lib.toInt (lib.last addressParts); in { allowedTCPPorts = [ port ]; }); }; }