at 25.11-pre 7.3 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 10 cfg = config.services.ttyd; 11 12 inherit (lib) 13 optionals 14 types 15 mkOption 16 ; 17 18 # Command line arguments for the ttyd daemon 19 args = 20 [ 21 "--port" 22 (toString cfg.port) 23 ] 24 ++ optionals (cfg.socket != null) [ 25 "--interface" 26 cfg.socket 27 ] 28 ++ optionals (cfg.interface != null) [ 29 "--interface" 30 cfg.interface 31 ] 32 ++ [ 33 "--signal" 34 (toString cfg.signal) 35 ] 36 ++ (lib.concatLists ( 37 lib.mapAttrsToList (_k: _v: [ 38 "--client-option" 39 "${_k}=${_v}" 40 ]) cfg.clientOptions 41 )) 42 ++ [ 43 "--terminal-type" 44 cfg.terminalType 45 ] 46 ++ optionals cfg.checkOrigin [ "--check-origin" ] 47 ++ optionals cfg.writeable [ "--writable" ] # the typo is correct 48 ++ [ 49 "--max-clients" 50 (toString cfg.maxClients) 51 ] 52 ++ optionals (cfg.indexFile != null) [ 53 "--index" 54 cfg.indexFile 55 ] 56 ++ optionals cfg.enableIPv6 [ "--ipv6" ] 57 ++ optionals cfg.enableSSL [ 58 "--ssl" 59 "--ssl-cert" 60 cfg.certFile 61 "--ssl-key" 62 cfg.keyFile 63 ] 64 ++ optionals (cfg.enableSSL && cfg.caFile != null) [ 65 "--ssl-ca" 66 cfg.caFile 67 ] 68 ++ [ 69 "--debug" 70 (toString cfg.logLevel) 71 ]; 72 73in 74 75{ 76 77 ###### interface 78 79 options = { 80 services.ttyd = { 81 enable = lib.mkEnableOption ("ttyd daemon"); 82 83 port = mkOption { 84 type = types.port; 85 default = 7681; 86 description = "Port to listen on (use 0 for random port)"; 87 }; 88 89 socket = mkOption { 90 type = types.nullOr types.path; 91 default = null; 92 example = "/var/run/ttyd.sock"; 93 description = "UNIX domain socket path to bind."; 94 }; 95 96 interface = mkOption { 97 type = types.nullOr types.str; 98 default = null; 99 example = "eth0"; 100 description = "Network interface to bind."; 101 }; 102 103 username = mkOption { 104 type = types.nullOr types.str; 105 default = null; 106 description = "Username for basic http authentication."; 107 }; 108 109 passwordFile = mkOption { 110 type = types.nullOr types.path; 111 default = null; 112 apply = value: if value == null then null else toString value; 113 description = '' 114 File containing the password to use for basic http authentication. 115 For insecurely putting the password in the globally readable store use 116 `pkgs.writeText "ttydpw" "MyPassword"`. 117 ''; 118 }; 119 120 signal = mkOption { 121 type = types.ints.u8; 122 default = 1; 123 description = "Signal to send to the command on session close."; 124 }; 125 126 entrypoint = mkOption { 127 type = types.listOf types.str; 128 default = [ "${pkgs.shadow}/bin/login" ]; 129 defaultText = lib.literalExpression '' 130 [ "''${pkgs.shadow}/bin/login" ] 131 ''; 132 example = lib.literalExpression '' 133 [ (lib.getExe pkgs.htop) ] 134 ''; 135 description = "Which command ttyd runs."; 136 apply = lib.escapeShellArgs; 137 }; 138 139 user = mkOption { 140 type = types.str; 141 # `login` needs to be run as root 142 default = "root"; 143 description = "Which unix user ttyd should run as."; 144 }; 145 146 writeable = mkOption { 147 type = types.nullOr types.bool; 148 default = null; # null causes an eval error, forcing the user to consider attack surface 149 example = true; 150 description = "Allow clients to write to the TTY."; 151 }; 152 153 clientOptions = mkOption { 154 type = types.attrsOf types.str; 155 default = { }; 156 example = lib.literalExpression '' 157 { 158 fontSize = "16"; 159 fontFamily = "Fira Code"; 160 } 161 ''; 162 description = '' 163 Attribute set of client options for xtermjs. 164 <https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/> 165 ''; 166 }; 167 168 terminalType = mkOption { 169 type = types.str; 170 default = "xterm-256color"; 171 description = "Terminal type to report."; 172 }; 173 174 checkOrigin = mkOption { 175 type = types.bool; 176 default = false; 177 description = "Whether to allow a websocket connection from a different origin."; 178 }; 179 180 maxClients = mkOption { 181 type = types.int; 182 default = 0; 183 description = "Maximum clients to support (0, no limit)"; 184 }; 185 186 indexFile = mkOption { 187 type = types.nullOr types.path; 188 default = null; 189 description = "Custom index.html path"; 190 }; 191 192 enableIPv6 = mkOption { 193 type = types.bool; 194 default = false; 195 description = "Whether or not to enable IPv6 support."; 196 }; 197 198 enableSSL = mkOption { 199 type = types.bool; 200 default = false; 201 description = "Whether or not to enable SSL (https) support."; 202 }; 203 204 certFile = mkOption { 205 type = types.nullOr types.path; 206 default = null; 207 description = "SSL certificate file path."; 208 }; 209 210 keyFile = mkOption { 211 type = types.nullOr types.path; 212 default = null; 213 apply = value: if value == null then null else toString value; 214 description = '' 215 SSL key file path. 216 For insecurely putting the keyFile in the globally readable store use 217 `pkgs.writeText "ttydKeyFile" "SSLKEY"`. 218 ''; 219 }; 220 221 caFile = mkOption { 222 type = types.nullOr types.path; 223 default = null; 224 description = "SSL CA file path for client certificate verification."; 225 }; 226 227 logLevel = mkOption { 228 type = types.int; 229 default = 7; 230 description = "Set log level."; 231 }; 232 }; 233 }; 234 235 ###### implementation 236 237 config = lib.mkIf cfg.enable { 238 239 assertions = [ 240 { 241 assertion = cfg.enableSSL -> cfg.certFile != null && cfg.keyFile != null; 242 message = "SSL is enabled for ttyd, but no certFile or keyFile has been specified."; 243 } 244 { 245 assertion = cfg.writeable != null; 246 message = "services.ttyd.writeable must be set"; 247 } 248 { 249 assertion = !(cfg.interface != null && cfg.socket != null); 250 message = "Cannot set both interface and socket for ttyd."; 251 } 252 { 253 assertion = (cfg.username != null) == (cfg.passwordFile != null); 254 message = "Need to set both username and passwordFile for ttyd"; 255 } 256 ]; 257 258 systemd.services.ttyd = { 259 description = "ttyd Web Server Daemon"; 260 261 wantedBy = [ "multi-user.target" ]; 262 263 serviceConfig = { 264 User = cfg.user; 265 LoadCredential = lib.optionalString ( 266 cfg.passwordFile != null 267 ) "TTYD_PASSWORD_FILE:${cfg.passwordFile}"; 268 }; 269 270 script = 271 if cfg.passwordFile != null then 272 '' 273 PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/TTYD_PASSWORD_FILE") 274 ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \ 275 --credential ${lib.escapeShellArg cfg.username}:"$PASSWORD" \ 276 ${cfg.entrypoint} 277 '' 278 else 279 '' 280 ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \ 281 ${cfg.entrypoint} 282 ''; 283 }; 284 }; 285}