{ config, lib, pkgs, ... }: let inherit (lib) mkEnableOption mkOption mkIf types ; cfg = config.services.ssh-mcp; hostConfig = config; toml = pkgs.formats.toml { }; renderEnvValue = value: if builtins.isBool value then (if value then "true" else "false") else if builtins.isList value then lib.concatStringsSep "," value else toString value; authType = types.submodule { options = { method = mkOption { type = types.enum [ "agent" "key" "password" ]; default = "agent"; description = "SSH authentication method."; }; key = mkOption { type = types.nullOr types.str; default = null; description = "Private key path for `method = \"key\"`."; }; identityFingerprint = mkOption { type = types.nullOr types.str; default = null; description = "Pin one agent identity by SHA-256 fingerprint."; }; identitiesOnly = mkOption { type = types.nullOr types.bool; default = null; description = "Offer only the pinned identity to the server."; }; identityAgent = mkOption { type = types.nullOr types.str; default = null; description = "SSH agent socket to use instead of SSH_AUTH_SOCK."; }; passphraseCmd = mkOption { type = types.nullOr types.str; default = null; description = "Local command that prints the key passphrase."; }; extraConfig = mkOption { type = types.attrsOf types.anything; default = { }; description = "Escape hatch merged into this auth table."; }; }; }; defaultsType = types.submodule { options = { user = mkOption { type = types.str; default = "root"; description = "Default SSH user."; }; port = mkOption { type = types.port; default = 22; description = "Default SSH port."; }; platform = mkOption { type = types.str; default = "linux"; description = "Default target platform."; }; persistentSession = mkOption { type = types.bool; default = false; description = "Allow persistent shell sessions by default."; }; pathAllowlist = mkOption { type = types.listOf types.str; default = [ ]; description = "Default path roots. Empty means no paths are allowed."; }; restrictedPaths = mkOption { type = types.listOf types.str; default = [ ]; description = "Default paths carved out of the allowlist."; }; commandAllowlist = mkOption { type = types.listOf types.str; default = [ ]; description = "Default exec command allowlist."; }; auth = mkOption { type = authType; default = { }; description = "Default SSH authentication policy."; }; extraConfig = mkOption { type = types.attrsOf types.anything; default = { }; description = "Escape hatch merged into the `[defaults]` table."; }; }; }; hostType = types.submodule { options = { hostname = mkOption { type = types.str; description = "Canonical hostname or address used on the wire."; }; user = mkOption { type = types.nullOr types.str; default = null; description = "Per-host SSH user override."; }; port = mkOption { type = types.nullOr types.port; default = null; description = "Per-host SSH port override."; }; platform = mkOption { type = types.nullOr types.str; default = null; description = "Per-host platform override."; }; persistentSession = mkOption { type = types.nullOr types.bool; default = null; description = "Per-host persistent-session override."; }; pathAllowlist = mkOption { type = types.nullOr (types.listOf types.str); default = null; description = "Per-host path roots."; }; restrictedPaths = mkOption { type = types.nullOr (types.listOf types.str); default = null; description = "Per-host paths carved out of the allowlist."; }; commandAllowlist = mkOption { type = types.nullOr (types.listOf types.str); default = null; description = "Per-host exec command allowlist."; }; auth = mkOption { type = types.nullOr authType; default = null; description = "Per-host authentication policy override."; }; extraConfig = mkOption { type = types.attrsOf types.anything; default = { }; description = "Escape hatch merged into this host's table."; }; }; }; settingsType = types.submodule { options = { enabledGroups = mkOption { type = types.listOf types.str; default = [ ]; description = "Tool groups to expose. Empty exposes all groups."; }; hostsAllowlist = mkOption { type = types.listOf types.str; default = [ ]; description = "Additional hostnames allowed by the server."; }; hostsBlocklist = mkOption { type = types.listOf types.str; default = [ ]; description = "Hostnames denied by the server. Deny wins."; }; pathAllowlist = mkOption { type = types.listOf types.str; default = [ ]; description = "Server-wide path allowlist."; }; restrictedPaths = mkOption { type = types.listOf types.str; default = [ ]; description = "Server-wide restricted paths."; }; commandAllowlist = mkOption { type = types.listOf types.str; default = [ ]; description = "Server-wide exec command allowlist."; }; allowLowAccessTools = mkOption { type = types.bool; default = false; description = "Enable SFTP-mediated file mutation tools."; }; allowDangerousTools = mkOption { type = types.bool; default = false; description = "Enable arbitrary command execution tools."; }; allowSudo = mkOption { type = types.bool; default = false; description = "Enable sudo-tier tools. Also requires dangerous tools."; }; allowAnyCommand = mkOption { type = types.bool; default = false; description = "Permit arbitrary commands when no command allowlist is set."; }; allowPasswordAuth = mkOption { type = types.bool; default = false; description = "Permit password authentication."; }; pingIncludesNotes = mkOption { type = types.bool; default = false; description = "Include operator notes in host ping results."; }; pingIncludesAgentNotes = mkOption { type = types.bool; default = false; description = "Include agent notes in host ping results."; }; otelEnabled = mkOption { type = types.bool; default = false; description = "Enable OpenTelemetry export."; }; localTransferRoots = mkOption { type = types.listOf types.str; default = [ ]; description = "Local paths the server may read for uploads and transfers."; }; dockerCmd = mkOption { type = types.nullOr types.str; default = null; description = "Docker-compatible CLI to use, e.g. `podman`."; }; configFile = mkOption { type = types.nullOr types.str; default = null; description = "Optional OpenSSH config file to fill in unset host fields."; }; extraEnv = mkOption { type = types.attrsOf types.str; default = { }; description = "Extra environment variables for the ssh-mcp process."; }; }; }; renderAuth = auth: { inherit (auth) method; } // lib.optionalAttrs (auth.key != null) { inherit (auth) key; } // lib.optionalAttrs (auth.identityFingerprint != null) { identity_fingerprint = auth.identityFingerprint; } // lib.optionalAttrs (auth.identitiesOnly != null) { identities_only = auth.identitiesOnly; } // lib.optionalAttrs (auth.identityAgent != null) { identity_agent = auth.identityAgent; } // lib.optionalAttrs (auth.passphraseCmd != null) { passphrase_cmd = auth.passphraseCmd; } // auth.extraConfig; renderDefaults = defaults: { inherit (defaults) user port platform; persistent_session = defaults.persistentSession; path_allowlist = defaults.pathAllowlist; restricted_paths = defaults.restrictedPaths; command_allowlist = defaults.commandAllowlist; auth = renderAuth defaults.auth; } // defaults.extraConfig; renderHost = host: { inherit (host) hostname; } // lib.optionalAttrs (host.user != null) { inherit (host) user; } // lib.optionalAttrs (host.port != null) { inherit (host) port; } // lib.optionalAttrs (host.platform != null) { inherit (host) platform; } // lib.optionalAttrs (host.persistentSession != null) { persistent_session = host.persistentSession; } // lib.optionalAttrs (host.pathAllowlist != null) { path_allowlist = host.pathAllowlist; } // lib.optionalAttrs (host.restrictedPaths != null) { restricted_paths = host.restrictedPaths; } // lib.optionalAttrs (host.commandAllowlist != null) { command_allowlist = host.commandAllowlist; } // lib.optionalAttrs (host.auth != null) { auth = renderAuth host.auth; } // host.extraConfig; renderInstanceSettings = instance: { ALLOW_LOW_ACCESS_TOOLS = renderEnvValue instance.settings.allowLowAccessTools; ALLOW_DANGEROUS_TOOLS = renderEnvValue instance.settings.allowDangerousTools; ALLOW_SUDO = renderEnvValue instance.settings.allowSudo; ALLOW_ANY_COMMAND = renderEnvValue instance.settings.allowAnyCommand; ALLOW_PASSWORD_AUTH = renderEnvValue instance.settings.allowPasswordAuth; SSH_PING_INCLUDES_NOTES = renderEnvValue instance.settings.pingIncludesNotes; SSH_PING_INCLUDES_AGENT_NOTES = renderEnvValue instance.settings.pingIncludesAgentNotes; OTEL_ENABLED = renderEnvValue instance.settings.otelEnabled; } // lib.optionalAttrs (instance.settings.enabledGroups != [ ]) { SSH_ENABLED_GROUPS = renderEnvValue instance.settings.enabledGroups; } // lib.optionalAttrs (instance.settings.hostsAllowlist != [ ]) { SSH_HOSTS_ALLOWLIST = renderEnvValue instance.settings.hostsAllowlist; } // lib.optionalAttrs (instance.settings.hostsBlocklist != [ ]) { SSH_HOSTS_BLOCKLIST = renderEnvValue instance.settings.hostsBlocklist; } // lib.optionalAttrs (instance.settings.pathAllowlist != [ ]) { SSH_PATH_ALLOWLIST = renderEnvValue instance.settings.pathAllowlist; } // lib.optionalAttrs (instance.settings.restrictedPaths != [ ]) { SSH_RESTRICTED_PATHS = renderEnvValue instance.settings.restrictedPaths; } // lib.optionalAttrs (instance.settings.commandAllowlist != [ ]) { SSH_COMMAND_ALLOWLIST = renderEnvValue instance.settings.commandAllowlist; } // lib.optionalAttrs (instance.settings.localTransferRoots != [ ]) { SSH_LOCAL_TRANSFER_ROOTS = renderEnvValue instance.settings.localTransferRoots; } // lib.optionalAttrs (instance.settings.dockerCmd != null) { SSH_DOCKER_CMD = instance.settings.dockerCmd; } // lib.optionalAttrs (instance.settings.configFile != null) { SSH_CONFIG_FILE = instance.settings.configFile; } // instance.settings.extraEnv; instanceType = types.submodule ( { name, config, ... }: { options = { enable = mkEnableOption "the ssh-mcp instance ${name}"; package = mkOption { type = types.package; default = pkgs.python-ssh-mcp; description = "ssh-mcp package for this instance."; }; description = mkOption { type = types.str; default = "ssh-mcp instance ${name}"; description = "Backend description used by `gatewayBackend`."; }; hostsFileName = mkOption { type = types.str; default = "hosts.toml"; description = "Generated hosts policy filename under `~/.config/ssh-mcp/${name}`."; }; knownHostsFileName = mkOption { type = types.str; default = "known_hosts"; description = "Generated known_hosts filename under `~/.config/ssh-mcp/${name}`."; }; defaults = mkOption { type = defaultsType; default = { }; description = "Default host policy for this instance."; }; hosts = mkOption { type = types.attrsOf hostType; default = { }; description = "Hosts reachable through this instance, keyed by alias."; }; knownHosts = mkOption { type = types.attrsOf types.str; default = { }; description = '' Pinned host keys keyed by canonical hostname. Each value is the key type and public key, e.g. `ssh-ed25519 AAAA...`. ''; }; settings = mkOption { type = settingsType; default = { }; description = "ssh-mcp server-wide environment settings."; }; hostsFile = mkOption { type = types.str; readOnly = true; default = "${hostConfig.xdg.configHome}/ssh-mcp/${name}/${config.hostsFileName}"; description = "Runtime path of the generated hosts policy."; }; knownHostsFile = mkOption { type = types.str; readOnly = true; default = "${hostConfig.xdg.configHome}/ssh-mcp/${name}/${config.knownHostsFileName}"; description = "Runtime path of the generated known_hosts file."; }; gatewayBackend = mkOption { type = types.attrsOf types.anything; readOnly = true; default = { command = "${config.package}/bin/ssh-mcp"; inherit (config) description; env = { SSH_HOSTS_FILE = config.hostsFile; SSH_KNOWN_HOSTS = config.knownHostsFile; } // renderInstanceSettings config; }; description = "Backend attrset ready for `services.mcp-gateway.backends`."; }; }; } ); in { options.services.ssh-mcp = { instances = mkOption { type = types.attrsOf instanceType; default = { }; description = "Independent ssh-mcp server instances."; }; }; config = { xdg.configFile = lib.mkMerge ( lib.mapAttrsToList ( name: instance: mkIf instance.enable { "ssh-mcp/${name}/${instance.hostsFileName}".source = toml.generate "ssh-mcp-${name}-hosts.toml" { defaults = renderDefaults instance.defaults; hosts = lib.mapAttrs (_host: host: renderHost host) instance.hosts; }; "ssh-mcp/${name}/${instance.knownHostsFileName}".text = lib.optionalString (instance.knownHosts != { }) ( lib.concatStringsSep "\n" ( lib.mapAttrsToList (host: key: "${host} ${key}") instance.knownHosts ) + "\n" ); } ) cfg.instances ); }; }