{ config, lib, pkgs, ... }: with lib; let cfg = config.jsp.ssh; in { options.jsp.ssh = { enable = mkEnableOption "SSH configuration"; zmx = { enable = mkEnableOption "zmx integration for persistent sessions"; hosts = mkOption { type = types.listOf types.str; default = [ ]; description = "List of host patterns to enable zmx auto-attach (e.g., 'pve.*')"; }; }; extraConfig = mkOption { type = types.lines; default = ""; description = "Extra SSH configuration"; }; hosts = mkOption { type = types.attrsOf ( types.submodule { options = { hostname = mkOption { type = types.nullOr types.str; default = null; description = "Hostname or IP address"; }; port = mkOption { type = types.nullOr types.int; default = null; description = "SSH port"; }; user = mkOption { type = types.nullOr types.str; default = null; description = "Username for SSH connection"; }; identityFile = mkOption { type = types.nullOr types.str; default = null; description = "Path to SSH identity file"; }; identitiesOnly = mkOption { type = types.nullOr types.bool; default = null; description = "Only use specified identity files"; }; forwardAgent = mkOption { type = types.nullOr types.bool; default = null; description = "Enable SSH agent forwarding"; }; addKeysToAgent = mkOption { type = types.nullOr types.str; default = null; description = "Add keys to SSH agent (yes/no/confirm/ask)"; }; extraOptions = mkOption { type = types.attrsOf types.str; default = { }; description = "Additional SSH options for this host"; }; zmx = mkOption { type = types.bool; default = false; description = "Enable zmx persistent sessions for this host"; }; }; } ); default = { }; description = "SSH host configurations"; }; }; config = mkIf cfg.enable { # Install zmx and autossh when zmx is enabled home.packages = optionals cfg.zmx.enable [ pkgs.zmx-binary pkgs.autossh ]; programs.ssh = { enable = true; enableDefaultConfig = false; matchBlocks = let # Convert jsp.ssh.hosts to SSH matchBlocks hostConfigs = mapAttrs (name: hostCfg: { hostname = mkIf (hostCfg.hostname != null) hostCfg.hostname; port = mkIf (hostCfg.port != null) hostCfg.port; user = mkIf (hostCfg.user != null) hostCfg.user; identityFile = mkIf (hostCfg.identityFile != null) hostCfg.identityFile; identitiesOnly = mkIf (hostCfg.identitiesOnly != null) hostCfg.identitiesOnly; forwardAgent = mkIf (hostCfg.forwardAgent != null) hostCfg.forwardAgent; addKeysToAgent = mkIf (hostCfg.addKeysToAgent != null) hostCfg.addKeysToAgent; extraOptions = hostCfg.extraOptions // ( if hostCfg.zmx then { RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %n"; RequestTTY = "yes"; ControlPath = "~/.ssh/cm-%r@%h:%p"; ControlMaster = "auto"; ControlPersist = "10m"; } else { } ); }) cfg.hosts; # Create zmx pattern hosts if enabled zmxPatternHosts = if cfg.zmx.enable then listToAttrs ( map ( pattern: let patternHost = cfg.hosts.${pattern} or { }; in { name = pattern; value = { hostname = mkIf (patternHost.hostname or null != null) patternHost.hostname; port = mkIf (patternHost.port or null != null) patternHost.port; user = mkIf (patternHost.user or null != null) patternHost.user; extraOptions = { RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %k"; RequestTTY = "yes"; ControlPath = "~/.ssh/cm-%r@%h:%p"; ControlMaster = "auto"; ControlPersist = "10m"; }; }; } ) cfg.zmx.hosts ) else { }; # Default match block for extraConfig defaultBlock = if cfg.extraConfig != "" then { "*" = { }; } else { }; in defaultBlock // hostConfigs // zmxPatternHosts; extraConfig = cfg.extraConfig; }; # Add shell aliases for zmx usage programs.zsh.shellAliases = mkIf cfg.zmx.enable { zmls = "zmx list"; zmk = "zmx kill"; zma = "zmx attach"; ash = "autossh -M 0 -q"; }; }; }