nixos/preload: init
[NixPkgs.git] / nixos / modules / services / web-servers / ttyd.nix
blob3b1d87ccb483e98dad81d50b1d6d5c962136b0de
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.services.ttyd;
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) ];
29   ###### interface
31   options = {
32     services.ttyd = {
33       enable = mkEnableOption (lib.mdDoc "ttyd daemon");
35       port = mkOption {
36         type = types.port;
37         default = 7681;
38         description = lib.mdDoc "Port to listen on (use 0 for random port)";
39       };
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       };
48       interface = mkOption {
49         type = types.nullOr types.str;
50         default = null;
51         example = "eth0";
52         description = lib.mdDoc "Network interface to bind.";
53       };
55       username = mkOption {
56         type = types.nullOr types.str;
57         default = null;
58         description = lib.mdDoc "Username for basic authentication.";
59       };
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       };
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       };
78       clientOptions = mkOption {
79         type = types.attrsOf types.str;
80         default = {};
81         example = literalExpression ''
82           {
83             fontSize = "16";
84             fontFamily = "Fira Code";
85           }
86         '';
87         description = lib.mdDoc ''
88           Attribute set of client options for xtermjs.
89           <https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/>
90         '';
91       };
93       terminalType = mkOption {
94         type = types.str;
95         default = "xterm-256color";
96         description = lib.mdDoc "Terminal type to report.";
97       };
99       checkOrigin = mkOption {
100         type = types.bool;
101         default = false;
102         description = lib.mdDoc "Whether to allow a websocket connection from a different origin.";
103       };
105       maxClients = mkOption {
106         type = types.int;
107         default = 0;
108         description = lib.mdDoc "Maximum clients to support (0, no limit)";
109       };
111       indexFile = mkOption {
112         type = types.nullOr types.path;
113         default = null;
114         description = lib.mdDoc "Custom index.html path";
115       };
117       enableIPv6 = mkOption {
118         type = types.bool;
119         default = false;
120         description = lib.mdDoc "Whether or not to enable IPv6 support.";
121       };
123       enableSSL = mkOption {
124         type = types.bool;
125         default = false;
126         description = lib.mdDoc "Whether or not to enable SSL (https) support.";
127       };
129       certFile = mkOption {
130         type = types.nullOr types.path;
131         default = null;
132         description = lib.mdDoc "SSL certificate file path.";
133       };
135       keyFile = mkOption {
136         type = types.nullOr types.path;
137         default = null;
138         apply = value: if value == null then null else toString value;
139         description = lib.mdDoc ''
140           SSL key file path.
141           For insecurely putting the keyFile in the globally readable store use
142           `pkgs.writeText "ttydKeyFile" "SSLKEY"`.
143         '';
144       };
146       caFile = mkOption {
147         type = types.nullOr types.path;
148         default = null;
149         description = lib.mdDoc "SSL CA file path for client certificate verification.";
150       };
152       logLevel = mkOption {
153         type = types.int;
154         default = 7;
155         description = lib.mdDoc "Set log level.";
156       };
157     };
158   };
160   ###### implementation
162   config = mkIf cfg.enable {
164     assertions =
165       [ { assertion = cfg.enableSSL
166             -> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null;
167           message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specified."; }
168         { assertion = ! (cfg.interface != null && cfg.socket != null);
169           message = "Cannot set both interface and socket for ttyd."; }
170         { assertion = (cfg.username != null) == (cfg.passwordFile != null);
171           message = "Need to set both username and passwordFile for ttyd"; }
172       ];
174     systemd.services.ttyd = {
175       description = "ttyd Web Server Daemon";
177       wantedBy = [ "multi-user.target" ];
179       serviceConfig = {
180         # Runs login which needs to be run as root
181         # login: Cannot possibly work without effective root
182         User = "root";
183       };
185       script = if cfg.passwordFile != null then ''
186         PASSWORD=$(cat ${escapeShellArg cfg.passwordFile})
187         ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
188           --credential ${escapeShellArg cfg.username}:"$PASSWORD" \
189           ${pkgs.shadow}/bin/login
190       ''
191       else ''
192         ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
193           ${pkgs.shadow}/bin/login
194       '';
195     };
196   };