{ config, lib, pkgs, ... }: let cfg = config.services.cratedocs-mcp; in { options.services.cratedocs-mcp = { enable = lib.mkEnableOption "CrateDocs MCP server"; port = lib.mkOption { type = lib.types.int; default = 3000; description = "Port to listen on for HTTP/SSE server"; }; user = lib.mkOption { type = lib.types.str; default = "cratedocs-mcp"; description = "User to run the service as"; }; group = lib.mkOption { type = lib.types.str; default = "cratedocs-mcp"; description = "Group to run the service as"; }; }; config = lib.mkIf cfg.enable { systemd.services.cratedocs-mcp = { description = "CrateDocs MCP server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${lib.getExe pkgs.cratedocs-mcp} http -a 127.0.0.1:${toString cfg.port}"; Restart = "always"; User = cfg.user; Group = cfg.group; DynamicUser = true; StateDirectory = "cratedocs-mcp"; CacheDirectory = "cratedocs-mcp"; # Security hardening PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; NoNewPrivileges = true; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; RemoveIPC = true; PrivateMounts = true; ProtectHostname = true; ProtectClock = true; ProtectKernelTunables = true; ProtectKernelModules = true; ProtectKernelLogs = true; ProtectControlGroups = true; LockPersonality = true; MemoryDenyWriteExecute = true; RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; SystemCallFilter = "@system-service"; SystemCallErrorNumber = "EPERM"; }; }; }; }