grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-servers / ttyd.nix
blobe3cf92e179138c6b7c7696e0527a1561337987e6
1 { config, lib, pkgs, ... }:
3 let
5   cfg = config.services.ttyd;
7   inherit (lib)
8     optionals
9     types
10     mkOption
11     ;
13   # Command line arguments for the ttyd daemon
14   args = [ "--port" (toString cfg.port) ]
15          ++ optionals (cfg.socket != null) [ "--interface" cfg.socket ]
16          ++ optionals (cfg.interface != null) [ "--interface" cfg.interface ]
17          ++ [ "--signal" (toString cfg.signal) ]
18          ++ (lib.concatLists (lib.mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions))
19          ++ [ "--terminal-type" cfg.terminalType ]
20          ++ optionals cfg.checkOrigin [ "--check-origin" ]
21          ++ optionals cfg.writeable [ "--writable" ] # the typo is correct
22          ++ [ "--max-clients" (toString cfg.maxClients) ]
23          ++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ]
24          ++ optionals cfg.enableIPv6 [ "--ipv6" ]
25          ++ optionals cfg.enableSSL [ "--ssl"
26                                       "--ssl-cert" cfg.certFile
27                                       "--ssl-key" cfg.keyFile ]
28          ++ optionals ( cfg.enableSSL && cfg.caFile != null ) [ "--ssl-ca" cfg.caFile ]
29          ++ [ "--debug" (toString cfg.logLevel) ];
35   ###### interface
37   options = {
38     services.ttyd = {
39       enable = lib.mkEnableOption ("ttyd daemon");
41       port = mkOption {
42         type = types.port;
43         default = 7681;
44         description = "Port to listen on (use 0 for random port)";
45       };
47       socket = mkOption {
48         type = types.nullOr types.path;
49         default = null;
50         example = "/var/run/ttyd.sock";
51         description = "UNIX domain socket path to bind.";
52       };
54       interface = mkOption {
55         type = types.nullOr types.str;
56         default = null;
57         example = "eth0";
58         description = "Network interface to bind.";
59       };
61       username = mkOption {
62         type = types.nullOr types.str;
63         default = null;
64         description = "Username for basic http authentication.";
65       };
67       passwordFile = mkOption {
68         type = types.nullOr types.path;
69         default = null;
70         apply = value: if value == null then null else toString value;
71         description = ''
72           File containing the password to use for basic http authentication.
73           For insecurely putting the password in the globally readable store use
74           `pkgs.writeText "ttydpw" "MyPassword"`.
75         '';
76       };
78       signal = mkOption {
79         type = types.ints.u8;
80         default = 1;
81         description = "Signal to send to the command on session close.";
82       };
84       entrypoint = mkOption {
85         type = types.listOf types.str;
86         default = [ "${pkgs.shadow}/bin/login" ];
87         defaultText = lib.literalExpression ''
88           [ "''${pkgs.shadow}/bin/login" ]
89         '';
90         example = lib.literalExpression ''
91           [ (lib.getExe pkgs.htop) ]
92         '';
93         description = "Which command ttyd runs.";
94         apply = lib.escapeShellArgs;
95       };
97       user = mkOption {
98         type = types.str;
99         # `login` needs to be run as root
100         default = "root";
101         description = "Which unix user ttyd should run as.";
102       };
104       writeable = mkOption {
105         type = types.nullOr types.bool;
106         default = null; # null causes an eval error, forcing the user to consider attack surface
107         example = true;
108         description = "Allow clients to write to the TTY.";
109       };
111       clientOptions = mkOption {
112         type = types.attrsOf types.str;
113         default = {};
114         example = lib.literalExpression ''
115           {
116             fontSize = "16";
117             fontFamily = "Fira Code";
118           }
119         '';
120         description = ''
121           Attribute set of client options for xtermjs.
122           <https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/>
123         '';
124       };
126       terminalType = mkOption {
127         type = types.str;
128         default = "xterm-256color";
129         description = "Terminal type to report.";
130       };
132       checkOrigin = mkOption {
133         type = types.bool;
134         default = false;
135         description = "Whether to allow a websocket connection from a different origin.";
136       };
138       maxClients = mkOption {
139         type = types.int;
140         default = 0;
141         description = "Maximum clients to support (0, no limit)";
142       };
144       indexFile = mkOption {
145         type = types.nullOr types.path;
146         default = null;
147         description = "Custom index.html path";
148       };
150       enableIPv6 = mkOption {
151         type = types.bool;
152         default = false;
153         description = "Whether or not to enable IPv6 support.";
154       };
156       enableSSL = mkOption {
157         type = types.bool;
158         default = false;
159         description = "Whether or not to enable SSL (https) support.";
160       };
162       certFile = mkOption {
163         type = types.nullOr types.path;
164         default = null;
165         description = "SSL certificate file path.";
166       };
168       keyFile = mkOption {
169         type = types.nullOr types.path;
170         default = null;
171         apply = value: if value == null then null else toString value;
172         description = ''
173           SSL key file path.
174           For insecurely putting the keyFile in the globally readable store use
175           `pkgs.writeText "ttydKeyFile" "SSLKEY"`.
176         '';
177       };
179       caFile = mkOption {
180         type = types.nullOr types.path;
181         default = null;
182         description = "SSL CA file path for client certificate verification.";
183       };
185       logLevel = mkOption {
186         type = types.int;
187         default = 7;
188         description = "Set log level.";
189       };
190     };
191   };
193   ###### implementation
195   config = lib.mkIf cfg.enable {
197     assertions =
198       [ { assertion = cfg.enableSSL
199             -> cfg.certFile != null && cfg.keyFile != null;
200           message = "SSL is enabled for ttyd, but no certFile or keyFile has been specified."; }
201         { assertion = cfg.writeable != null;
202           message = "services.ttyd.writeable must be set"; }
203         { assertion = ! (cfg.interface != null && cfg.socket != null);
204           message = "Cannot set both interface and socket for ttyd."; }
205         { assertion = (cfg.username != null) == (cfg.passwordFile != null);
206           message = "Need to set both username and passwordFile for ttyd"; }
207       ];
209     systemd.services.ttyd = {
210       description = "ttyd Web Server Daemon";
212       wantedBy = [ "multi-user.target" ];
214       serviceConfig = {
215         User = cfg.user;
216         LoadCredential = lib.optionalString (cfg.passwordFile != null) "TTYD_PASSWORD_FILE:${cfg.passwordFile}";
217       };
219       script = if cfg.passwordFile != null then ''
220         PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/TTYD_PASSWORD_FILE")
221         ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
222           --credential ${lib.escapeShellArg cfg.username}:"$PASSWORD" \
223           ${cfg.entrypoint}
224       ''
225       else ''
226         ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
227           ${cfg.entrypoint}
228       '';
229     };
230   };