1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.soju;
7 stateDir = "/var/lib/soju";
8 runtimeDir = "/run/soju";
9 listen = cfg.listen
10 ++ optional cfg.adminSocket.enable "unix+admin://${runtimeDir}/admin";
11 listenCfg = concatMapStringsSep "\n" (l: "listen ${l}") listen;
12 tlsCfg = optionalString (cfg.tlsCertificate != null)
13 "tls ${cfg.tlsCertificate} ${cfg.tlsCertificateKey}";
14 logCfg = optionalString cfg.enableMessageLogging
15 "log fs ${stateDir}/logs";
16
17 configFile = pkgs.writeText "soju.conf" ''
18 ${listenCfg}
19 hostname ${cfg.hostName}
20 ${tlsCfg}
21 db sqlite3 ${stateDir}/soju.db
22 ${logCfg}
23 http-origin ${concatStringsSep " " cfg.httpOrigins}
24 accept-proxy-ip ${concatStringsSep " " cfg.acceptProxyIP}
25
26 ${cfg.extraConfig}
27 '';
28
29 sojuctl = pkgs.writeShellScriptBin "sojuctl" ''
30 exec ${cfg.package}/bin/sojuctl --config ${configFile} "$@"
31 '';
32in
33{
34 ###### interface
35
36 options.services.soju = {
37 enable = mkEnableOption "soju";
38
39 package = mkPackageOption pkgs "soju" { };
40
41 listen = mkOption {
42 type = types.listOf types.str;
43 default = [ ":6697" ];
44 description = ''
45 Where soju should listen for incoming connections. See the
46 `listen` directive in
47 {manpage}`soju(1)`.
48 '';
49 };
50
51 hostName = mkOption {
52 type = types.str;
53 default = config.networking.hostName;
54 defaultText = literalExpression "config.networking.hostName";
55 description = "Server hostname.";
56 };
57
58 tlsCertificate = mkOption {
59 type = types.nullOr types.path;
60 default = null;
61 example = "/var/host.cert";
62 description = "Path to server TLS certificate.";
63 };
64
65 tlsCertificateKey = mkOption {
66 type = types.nullOr types.path;
67 default = null;
68 example = "/var/host.key";
69 description = "Path to server TLS certificate key.";
70 };
71
72 enableMessageLogging = mkOption {
73 type = types.bool;
74 default = true;
75 description = "Whether to enable message logging.";
76 };
77
78 adminSocket.enable = mkOption {
79 type = types.bool;
80 default = true;
81 description = ''
82 Listen for admin connections from sojuctl at /run/soju/admin.
83 '';
84 };
85
86 httpOrigins = mkOption {
87 type = types.listOf types.str;
88 default = [];
89 description = ''
90 List of allowed HTTP origins for WebSocket listeners. The parameters are
91 interpreted as shell patterns, see
92 {manpage}`glob(7)`.
93 '';
94 };
95
96 acceptProxyIP = mkOption {
97 type = types.listOf types.str;
98 default = [];
99 description = ''
100 Allow the specified IPs to act as a proxy. Proxys have the ability to
101 overwrite the remote and local connection addresses (via the X-Forwarded-\*
102 HTTP header fields). The special name "localhost" accepts the loopback
103 addresses 127.0.0.0/8 and ::1/128. By default, all IPs are rejected.
104 '';
105 };
106
107 extraConfig = mkOption {
108 type = types.lines;
109 default = "";
110 description = "Lines added verbatim to the configuration file.";
111 };
112 };
113
114 ###### implementation
115
116 config = mkIf cfg.enable {
117 assertions = [
118 {
119 assertion = (cfg.tlsCertificate != null) == (cfg.tlsCertificateKey != null);
120 message = ''
121 services.soju.tlsCertificate and services.soju.tlsCertificateKey
122 must both be specified to enable TLS.
123 '';
124 }
125 ];
126
127 environment.systemPackages = [ sojuctl ];
128
129 systemd.services.soju = {
130 description = "soju IRC bouncer";
131 wantedBy = [ "multi-user.target" ];
132 wants = [ "network-online.target" ];
133 after = [ "network-online.target" ];
134 serviceConfig = {
135 DynamicUser = true;
136 Restart = "always";
137 ExecStart = "${cfg.package}/bin/soju -config ${configFile}";
138 StateDirectory = "soju";
139 RuntimeDirectory = "soju";
140 };
141 };
142 };
143
144 meta.maintainers = with maintainers; [ malte-v ];
145}