···
{ config, lib, pkgs, ... }:
cfg = config.services.ttyd;
# Command line arguments for the ttyd daemon
args = [ "--port" (toString cfg.port) ]
++ optionals (cfg.socket != null) [ "--interface" cfg.socket ]
···
++ (concatLists (mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions))
++ [ "--terminal-type" cfg.terminalType ]
++ optionals cfg.checkOrigin [ "--check-origin" ]
23
+
++ optionals cfg.writeable [ "--writable" ] # the typo is correct
++ [ "--max-clients" (toString cfg.maxClients) ]
++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ]
++ optionals cfg.enableIPv6 [ "--ipv6" ]
···
33
-
enable = mkEnableOption (lib.mdDoc "ttyd daemon");
40
+
enable = lib.mkEnableOption ("ttyd daemon");
38
-
description = lib.mdDoc "Port to listen on (use 0 for random port)";
45
+
description = "Port to listen on (use 0 for random port)";
type = types.nullOr types.path;
example = "/var/run/ttyd.sock";
45
-
description = lib.mdDoc "UNIX domain socket path to bind.";
52
+
description = "UNIX domain socket path to bind.";
type = types.nullOr types.str;
52
-
description = lib.mdDoc "Network interface to bind.";
59
+
description = "Network interface to bind.";
type = types.nullOr types.str;
58
-
description = lib.mdDoc "Username for basic authentication.";
65
+
description = "Username for basic http authentication.";
passwordFile = mkOption {
type = types.nullOr types.path;
apply = value: if value == null then null else toString value;
65
-
description = lib.mdDoc ''
66
-
File containing the password to use for basic authentication.
73
+
File containing the password to use for basic http authentication.
For insecurely putting the password in the globally readable store use
`pkgs.writeText "ttydpw" "MyPassword"`.
···
75
-
description = lib.mdDoc "Signal to send to the command on session close.";
82
+
description = "Signal to send to the command on session close.";
85
+
entrypoint = mkOption {
86
+
type = types.listOf types.str;
87
+
default = [ "${pkgs.shadow}/bin/login" ];
88
+
defaultText = lib.literalExpression ''
89
+
[ "''${pkgs.shadow}/bin/login" ]
91
+
example = lib.literalExpression ''
92
+
[ (lib.getExe pkgs.htop) ]
94
+
description = "Which command ttyd runs.";
95
+
apply = lib.escapeShellArgs;
100
+
# `login` needs to be run as root
102
+
description = "Which unix user ttyd should run as.";
105
+
writeable = mkOption {
106
+
type = types.nullOr types.bool;
107
+
default = null; # null causes an eval error, forcing the user to consider attack surface
109
+
description = "Allow clients to write to the TTY.";
clientOptions = mkOption {
type = types.attrsOf types.str;
81
-
example = literalExpression ''
115
+
example = lib.literalExpression ''
fontFamily = "Fira Code";
87
-
description = lib.mdDoc ''
Attribute set of client options for xtermjs.
<https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/>
···
terminalType = mkOption {
default = "xterm-256color";
96
-
description = lib.mdDoc "Terminal type to report.";
130
+
description = "Terminal type to report.";
102
-
description = lib.mdDoc "Whether to allow a websocket connection from a different origin.";
136
+
description = "Whether to allow a websocket connection from a different origin.";
108
-
description = lib.mdDoc "Maximum clients to support (0, no limit)";
142
+
description = "Maximum clients to support (0, no limit)";
type = types.nullOr types.path;
114
-
description = lib.mdDoc "Custom index.html path";
148
+
description = "Custom index.html path";
120
-
description = lib.mdDoc "Whether or not to enable IPv6 support.";
154
+
description = "Whether or not to enable IPv6 support.";
126
-
description = lib.mdDoc "Whether or not to enable SSL (https) support.";
160
+
description = "Whether or not to enable SSL (https) support.";
type = types.nullOr types.path;
132
-
description = lib.mdDoc "SSL certificate file path.";
166
+
description = "SSL certificate file path.";
type = types.nullOr types.path;
apply = value: if value == null then null else toString value;
139
-
description = lib.mdDoc ''
For insecurely putting the keyFile in the globally readable store use
`pkgs.writeText "ttydKeyFile" "SSLKEY"`.
···
type = types.nullOr types.path;
149
-
description = lib.mdDoc "SSL CA file path for client certificate verification.";
183
+
description = "SSL CA file path for client certificate verification.";
155
-
description = lib.mdDoc "Set log level.";
189
+
description = "Set log level.";
162
-
config = mkIf cfg.enable {
196
+
config = lib.mkIf cfg.enable {
[ { assertion = cfg.enableSSL
-> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null;
message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specified."; }
202
+
{ assertion = cfg.writeable != null;
203
+
message = "services.ttyd.writeable must be set"; }
{ assertion = ! (cfg.interface != null && cfg.socket != null);
message = "Cannot set both interface and socket for ttyd."; }
{ assertion = (cfg.username != null) == (cfg.passwordFile != null);
···
wantedBy = [ "multi-user.target" ];
180
-
# Runs login which needs to be run as root
181
-
# login: Cannot possibly work without effective root
LoadCredential = lib.optionalString (cfg.passwordFile != null) "TTYD_PASSWORD_FILE:${cfg.passwordFile}";
script = if cfg.passwordFile != null then ''
PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/TTYD_PASSWORD_FILE")
${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
189
-
--credential ${escapeShellArg cfg.username}:"$PASSWORD" \
190
-
${pkgs.shadow}/bin/login
223
+
--credential ${lib.escapeShellArg cfg.username}:"$PASSWORD" \
${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
194
-
${pkgs.shadow}/bin/login