at 23.11-pre 6.1 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.ttyd; 8 9 # Command line arguments for the ttyd daemon 10 args = [ "--port" (toString cfg.port) ] 11 ++ optionals (cfg.socket != null) [ "--interface" cfg.socket ] 12 ++ optionals (cfg.interface != null) [ "--interface" cfg.interface ] 13 ++ [ "--signal" (toString cfg.signal) ] 14 ++ (concatLists (mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions)) 15 ++ [ "--terminal-type" cfg.terminalType ] 16 ++ optionals cfg.checkOrigin [ "--check-origin" ] 17 ++ [ "--max-clients" (toString cfg.maxClients) ] 18 ++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ] 19 ++ optionals cfg.enableIPv6 [ "--ipv6" ] 20 ++ optionals cfg.enableSSL [ "--ssl-cert" cfg.certFile 21 "--ssl-key" cfg.keyFile 22 "--ssl-ca" cfg.caFile ] 23 ++ [ "--debug" (toString cfg.logLevel) ]; 24 25in 26 27{ 28 29 ###### interface 30 31 options = { 32 services.ttyd = { 33 enable = mkEnableOption (lib.mdDoc "ttyd daemon"); 34 35 port = mkOption { 36 type = types.port; 37 default = 7681; 38 description = lib.mdDoc "Port to listen on (use 0 for random port)"; 39 }; 40 41 socket = mkOption { 42 type = types.nullOr types.path; 43 default = null; 44 example = "/var/run/ttyd.sock"; 45 description = lib.mdDoc "UNIX domain socket path to bind."; 46 }; 47 48 interface = mkOption { 49 type = types.nullOr types.str; 50 default = null; 51 example = "eth0"; 52 description = lib.mdDoc "Network interface to bind."; 53 }; 54 55 username = mkOption { 56 type = types.nullOr types.str; 57 default = null; 58 description = lib.mdDoc "Username for basic authentication."; 59 }; 60 61 passwordFile = mkOption { 62 type = types.nullOr types.path; 63 default = null; 64 apply = value: if value == null then null else toString value; 65 description = lib.mdDoc '' 66 File containing the password to use for basic authentication. 67 For insecurely putting the password in the globally readable store use 68 `pkgs.writeText "ttydpw" "MyPassword"`. 69 ''; 70 }; 71 72 signal = mkOption { 73 type = types.ints.u8; 74 default = 1; 75 description = lib.mdDoc "Signal to send to the command on session close."; 76 }; 77 78 clientOptions = mkOption { 79 type = types.attrsOf types.str; 80 default = {}; 81 example = literalExpression ''{ 82 fontSize = "16"; 83 fontFamily = "Fira Code"; 84 85 }''; 86 description = lib.mdDoc '' 87 Attribute set of client options for xtermjs. 88 <https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/> 89 ''; 90 }; 91 92 terminalType = mkOption { 93 type = types.str; 94 default = "xterm-256color"; 95 description = lib.mdDoc "Terminal type to report."; 96 }; 97 98 checkOrigin = mkOption { 99 type = types.bool; 100 default = false; 101 description = lib.mdDoc "Whether to allow a websocket connection from a different origin."; 102 }; 103 104 maxClients = mkOption { 105 type = types.int; 106 default = 0; 107 description = lib.mdDoc "Maximum clients to support (0, no limit)"; 108 }; 109 110 indexFile = mkOption { 111 type = types.nullOr types.path; 112 default = null; 113 description = lib.mdDoc "Custom index.html path"; 114 }; 115 116 enableIPv6 = mkOption { 117 type = types.bool; 118 default = false; 119 description = lib.mdDoc "Whether or not to enable IPv6 support."; 120 }; 121 122 enableSSL = mkOption { 123 type = types.bool; 124 default = false; 125 description = lib.mdDoc "Whether or not to enable SSL (https) support."; 126 }; 127 128 certFile = mkOption { 129 type = types.nullOr types.path; 130 default = null; 131 description = lib.mdDoc "SSL certificate file path."; 132 }; 133 134 keyFile = mkOption { 135 type = types.nullOr types.path; 136 default = null; 137 apply = value: if value == null then null else toString value; 138 description = lib.mdDoc '' 139 SSL key file path. 140 For insecurely putting the keyFile in the globally readable store use 141 `pkgs.writeText "ttydKeyFile" "SSLKEY"`. 142 ''; 143 }; 144 145 caFile = mkOption { 146 type = types.nullOr types.path; 147 default = null; 148 description = lib.mdDoc "SSL CA file path for client certificate verification."; 149 }; 150 151 logLevel = mkOption { 152 type = types.int; 153 default = 7; 154 description = lib.mdDoc "Set log level."; 155 }; 156 }; 157 }; 158 159 ###### implementation 160 161 config = mkIf cfg.enable { 162 163 assertions = 164 [ { assertion = cfg.enableSSL 165 -> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null; 166 message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specified."; } 167 { assertion = ! (cfg.interface != null && cfg.socket != null); 168 message = "Cannot set both interface and socket for ttyd."; } 169 { assertion = (cfg.username != null) == (cfg.passwordFile != null); 170 message = "Need to set both username and passwordFile for ttyd"; } 171 ]; 172 173 systemd.services.ttyd = { 174 description = "ttyd Web Server Daemon"; 175 176 wantedBy = [ "multi-user.target" ]; 177 178 serviceConfig = { 179 # Runs login which needs to be run as root 180 # login: Cannot possibly work without effective root 181 User = "root"; 182 }; 183 184 script = if cfg.passwordFile != null then '' 185 PASSWORD=$(cat ${escapeShellArg cfg.passwordFile}) 186 ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \ 187 --credential ${escapeShellArg cfg.username}:"$PASSWORD" \ 188 ${pkgs.shadow}/bin/login 189 '' 190 else '' 191 ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \ 192 ${pkgs.shadow}/bin/login 193 ''; 194 }; 195 }; 196}