···1+{ config, lib, pkgs, ... }:
2+3+with lib;
4+5+let
6+7+ cfg = config.services.shellinabox;
8+9+ # If a certificate file is specified, shellinaboxd requires
10+ # a file descriptor to retrieve it
11+ fd = "3";
12+ createFd = optionalString (cfg.certFile != null) "${fd}<${cfg.certFile}";
13+14+ # Command line arguments for the shellinabox daemon
15+ args = [ "--background" ]
16+ ++ optional (! cfg.enableSSL) "--disable-ssl"
17+ ++ optional (cfg.certFile != null) "--cert-fd=${fd}"
18+ ++ optional (cfg.certDirectory != null) "--cert=${cfg.certDirectory}"
19+ ++ cfg.extraOptions;
20+21+ # Command to start shellinaboxd
22+ cmd = "${pkgs.shellinabox}/bin/shellinaboxd ${concatStringsSep " " args}";
23+24+ # Command to start shellinaboxd if certFile is specified
25+ wrappedCmd = "${pkgs.bash}/bin/bash -c 'exec ${createFd} && ${cmd}'";
26+27+in
28+29+{
30+31+ ###### interface
32+33+ options = {
34+ services.shellinabox = {
35+ enable = mkEnableOption "shellinabox daemon";
36+37+ user = mkOption {
38+ type = types.str;
39+ default = "root";
40+ description = ''
41+ User to run shellinaboxd as. If started as root, the server drops
42+ privileges by changing to nobody, unless overridden by the
43+ <literal>--user</literal> option.
44+ '';
45+ };
46+47+ enableSSL = mkOption {
48+ type = types.bool;
49+ default = false;
50+ description = ''
51+ Whether or not to enable SSL (https) support.
52+ '';
53+ };
54+55+ certDirectory = mkOption {
56+ type = types.nullOr types.path;
57+ default = null;
58+ example = "/var/certs";
59+ description = ''
60+ The daemon will look in this directory far any certificates.
61+ If the browser negotiated a Server Name Identification the daemon
62+ will look for a matching certificate-SERVERNAME.pem file. If no SNI
63+ handshake takes place, it will fall back on using the certificate in the
64+ certificate.pem file.
65+66+ If no suitable certificate is installed, shellinaboxd will attempt to
67+ create a new self-signed certificate. This will only succeed if, after
68+ dropping privileges, shellinaboxd has write permissions for this
69+ directory.
70+ '';
71+ };
72+73+ certFile = mkOption {
74+ type = types.nullOr types.path;
75+ default = null;
76+ example = "/var/certificate.pem";
77+ description = "Path to server SSL certificate.";
78+ };
79+80+ extraOptions = mkOption {
81+ type = types.listOf types.str;
82+ default = [ ];
83+ example = [ "--port=443" "--service /:LOGIN" ];
84+ description = ''
85+ A list of strings to be appended to the command line arguments
86+ for shellinaboxd. Please see the manual page
87+ <link xlink:href="https://code.google.com/p/shellinabox/wiki/shellinaboxd_man"/>
88+ for a full list of available arguments.
89+ '';
90+ };
91+92+ };
93+ };
94+95+ ###### implementation
96+97+ config = mkIf cfg.enable {
98+99+ assertions =
100+ [ { assertion = cfg.enableSSL == true
101+ -> cfg.certDirectory != null || cfg.certFile != null;
102+ message = "SSL is enabled for shellinabox, but no certDirectory or certFile has been specefied."; }
103+ { assertion = ! (cfg.certDirectory != null && cfg.certFile != null);
104+ message = "Cannot set both certDirectory and certFile for shellinabox."; }
105+ ];
106+107+ systemd.services.shellinaboxd = {
108+ description = "Shellinabox Web Server Daemon";
109+110+ wantedBy = [ "multi-user.target" ];
111+ requires = [ "sshd.service" ];
112+ after = [ "sshd.service" ];
113+114+ serviceConfig = {
115+ Type = "forking";
116+ User = "${cfg.user}";
117+ ExecStart = "${if cfg.certFile == null then "${cmd}" else "${wrappedCmd}"}";
118+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
119+ };
120+ };
121+ };
122+}