Kieran's opinionated (and probably slightly dumb) nix config
1{
2 config,
3 lib,
4 pkgs,
5 inputs,
6 ...
7}:
8with lib;
9let
10 cfg = config.atelier.ssh;
11in
12{
13 options.atelier.ssh = {
14 enable = mkEnableOption "SSH configuration";
15
16 zmx = {
17 enable = mkEnableOption "zmx integration for persistent sessions";
18 hosts = mkOption {
19 type = types.listOf types.str;
20 default = [ ];
21 description = "List of host patterns to enable zmx auto-attach (e.g., 'd.*')";
22 };
23 };
24
25 extraConfig = mkOption {
26 type = types.lines;
27 default = "";
28 description = "Extra SSH configuration";
29 };
30
31 hosts = mkOption {
32 type = types.attrsOf (
33 types.submodule {
34 options = {
35 hostname = mkOption {
36 type = types.nullOr types.str;
37 default = null;
38 description = "Hostname or IP address";
39 };
40
41 port = mkOption {
42 type = types.nullOr types.int;
43 default = null;
44 description = "SSH port";
45 };
46
47 user = mkOption {
48 type = types.nullOr types.str;
49 default = null;
50 description = "Username for SSH connection";
51 };
52
53 identityFile = mkOption {
54 type = types.nullOr types.str;
55 default = null;
56 description = "Path to SSH identity file";
57 };
58
59 forwardAgent = mkOption {
60 type = types.nullOr types.bool;
61 default = null;
62 description = "Enable SSH agent forwarding";
63 };
64
65 extraOptions = mkOption {
66 type = types.attrsOf types.str;
67 default = { };
68 description = "Additional SSH options for this host";
69 };
70
71 zmx = mkOption {
72 type = types.bool;
73 default = false;
74 description = "Enable zmx persistent sessions for this host";
75 };
76 };
77 }
78 );
79 default = { };
80 description = "SSH host configurations";
81 };
82 };
83
84 config = mkIf cfg.enable {
85 # zmx provides pre-built binaries that we download instead of building from source
86 # This avoids the zig2nix dependency which causes issues in CI
87 home.packages =
88 (optionals cfg.zmx.enable [
89 pkgs.zmx-binary
90 pkgs.autossh
91 ]);
92
93 programs.ssh = {
94 enable = true;
95 enableDefaultConfig = false;
96
97 matchBlocks =
98 let
99 # Convert atelier.ssh.hosts to SSH matchBlocks
100 hostConfigs = mapAttrs (
101 name: hostCfg:
102 {
103 hostname = mkIf (hostCfg.hostname != null) hostCfg.hostname;
104 port = mkIf (hostCfg.port != null) hostCfg.port;
105 user = mkIf (hostCfg.user != null) hostCfg.user;
106 identityFile = mkIf (hostCfg.identityFile != null) hostCfg.identityFile;
107 forwardAgent = mkIf (hostCfg.forwardAgent != null) hostCfg.forwardAgent;
108 extraOptions = hostCfg.extraOptions // (
109 if hostCfg.zmx then
110 {
111 RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %n";
112 RequestTTY = "yes";
113 ControlPath = "~/.ssh/cm-%r@%h:%p";
114 ControlMaster = "auto";
115 ControlPersist = "10m";
116 }
117 else
118 { }
119 );
120 }
121 ) cfg.hosts;
122
123 # Create zmx pattern hosts if enabled
124 zmxPatternHosts = if cfg.zmx.enable then
125 listToAttrs (
126 map (pattern:
127 let
128 patternHost = cfg.hosts.${pattern} or {};
129 in {
130 name = pattern;
131 value = {
132 hostname = mkIf (patternHost.hostname or null != null) patternHost.hostname;
133 port = mkIf (patternHost.port or null != null) patternHost.port;
134 user = mkIf (patternHost.user or null != null) patternHost.user;
135 extraOptions = {
136 RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %k";
137 RequestTTY = "yes";
138 ControlPath = "~/.ssh/cm-%r@%h:%p";
139 ControlMaster = "auto";
140 ControlPersist = "10m";
141 };
142 };
143 }) cfg.zmx.hosts
144 )
145 else
146 { };
147
148 # Default match block for extraConfig
149 defaultBlock = if cfg.extraConfig != "" then
150 {
151 "*" = { };
152 }
153 else
154 { };
155 in
156 defaultBlock // hostConfigs // zmxPatternHosts;
157
158 extraConfig = cfg.extraConfig;
159 };
160
161 # Add shell aliases for easier zmx usage
162 programs.zsh.shellAliases = mkIf cfg.zmx.enable {
163 zmls = "zmx list";
164 zmk = "zmx kill";
165 zma = "zmx attach";
166 ash = "autossh -M 0 -q";
167 };
168
169 programs.bash.shellAliases = mkIf cfg.zmx.enable {
170 zmls = "zmx list";
171 zmk = "zmx kill";
172 zma = "zmx attach";
173 ash = "autossh -M 0 -q";
174 };
175
176 programs.fish.shellAliases = mkIf cfg.zmx.enable {
177 zmls = "zmx list";
178 zmk = "zmx kill";
179 zma = "zmx attach";
180 ash = "autossh -M 0 -q";
181 };
182 };
183}