1{ config, pkgs, lib, ... }: with lib; let
2 cfg = config.services.sogo;
3
4 preStart = pkgs.writeShellScriptBin "sogo-prestart" ''
5 touch /etc/sogo/sogo.conf
6 chown sogo:sogo /etc/sogo/sogo.conf
7 chmod 640 /etc/sogo/sogo.conf
8
9 ${if (cfg.configReplaces != {}) then ''
10 # Insert secrets
11 ${concatStringsSep "\n" (mapAttrsToList (k: v: ''export ${k}="$(cat "${v}" | tr -d '\n')"'') cfg.configReplaces)}
12
13 ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw > /etc/sogo/sogo.conf
14 '' else ''
15 cp /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
16 ''}
17 '';
18
19in {
20 options.services.sogo = with types; {
21 enable = mkEnableOption (lib.mdDoc "SOGo groupware");
22
23 vhostName = mkOption {
24 description = lib.mdDoc "Name of the nginx vhost";
25 type = str;
26 default = "sogo";
27 };
28
29 timezone = mkOption {
30 description = lib.mdDoc "Timezone of your SOGo instance";
31 type = str;
32 example = "America/Montreal";
33 };
34
35 language = mkOption {
36 description = lib.mdDoc "Language of SOGo";
37 type = str;
38 default = "English";
39 };
40
41 ealarmsCredFile = mkOption {
42 description = lib.mdDoc "Optional path to a credentials file for email alarms";
43 type = nullOr str;
44 default = null;
45 };
46
47 configReplaces = mkOption {
48 description = lib.mdDoc ''
49 Replacement-filepath mapping for sogo.conf.
50 Every key is replaced with the contents of the file specified as value.
51
52 In the example, every occurrence of LDAP_BINDPW will be replaced with the text of the
53 specified file.
54 '';
55 type = attrsOf str;
56 default = {};
57 example = {
58 LDAP_BINDPW = "/var/lib/secrets/sogo/ldappw";
59 };
60 };
61
62 extraConfig = mkOption {
63 description = lib.mdDoc "Extra sogo.conf configuration lines";
64 type = lines;
65 default = "";
66 };
67 };
68
69 config = mkIf cfg.enable {
70 environment.systemPackages = [ pkgs.sogo ];
71
72 environment.etc."sogo/sogo.conf.raw".text = ''
73 {
74 // Mandatory parameters
75 SOGoTimeZone = "${cfg.timezone}";
76 SOGoLanguage = "${cfg.language}";
77 // Paths
78 WOSendMail = "/run/wrappers/bin/sendmail";
79 SOGoMailSpoolPath = "/var/lib/sogo/spool";
80 // Enable CSRF protection
81 SOGoXSRFValidationEnabled = YES;
82 // Remove dates from log (jornald does that)
83 NGLogDefaultLogEventFormatterClass = "NGLogEventFormatter";
84 // Extra config
85 ${cfg.extraConfig}
86 }
87 '';
88
89 systemd.services.sogo = {
90 description = "SOGo groupware";
91 after = [ "postgresql.service" "mysql.service" "memcached.service" "openldap.service" "dovecot2.service" ];
92 wantedBy = [ "multi-user.target" ];
93 restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
94
95 environment.LDAPTLS_CACERT = "/etc/ssl/certs/ca-certificates.crt";
96
97 serviceConfig = {
98 Type = "forking";
99 ExecStartPre = "+" + preStart + "/bin/sogo-prestart";
100 ExecStart = "${pkgs.sogo}/bin/sogod -WOLogFile - -WOPidFile /run/sogo/sogo.pid";
101
102 ProtectSystem = "strict";
103 ProtectHome = true;
104 PrivateTmp = true;
105 PrivateDevices = true;
106 ProtectKernelTunables = true;
107 ProtectKernelModules = true;
108 ProtectControlGroups = true;
109 RuntimeDirectory = "sogo";
110 StateDirectory = "sogo/spool";
111
112 User = "sogo";
113 Group = "sogo";
114
115 CapabilityBoundingSet = "";
116 NoNewPrivileges = true;
117
118 LockPersonality = true;
119 RestrictRealtime = true;
120 PrivateMounts = true;
121 PrivateUsers = true;
122 MemoryDenyWriteExecute = true;
123 SystemCallFilter = "@basic-io @file-system @network-io @system-service @timer";
124 SystemCallArchitectures = "native";
125 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
126 };
127 };
128
129 systemd.services.sogo-tmpwatch = {
130 description = "SOGo tmpwatch";
131
132 startAt = [ "hourly" ];
133 script = ''
134 SOGOSPOOL=/var/lib/sogo/spool
135
136 find "$SOGOSPOOL" -type f -user sogo -atime +23 -delete > /dev/null
137 find "$SOGOSPOOL" -mindepth 1 -type d -user sogo -empty -delete > /dev/null
138 '';
139
140 serviceConfig = {
141 Type = "oneshot";
142
143 ProtectSystem = "strict";
144 ProtectHome = true;
145 PrivateTmp = true;
146 PrivateDevices = true;
147 ProtectKernelTunables = true;
148 ProtectKernelModules = true;
149 ProtectControlGroups = true;
150 StateDirectory = "sogo/spool";
151
152 User = "sogo";
153 Group = "sogo";
154
155 CapabilityBoundingSet = "";
156 NoNewPrivileges = true;
157
158 LockPersonality = true;
159 RestrictRealtime = true;
160 PrivateMounts = true;
161 PrivateUsers = true;
162 PrivateNetwork = true;
163 SystemCallFilter = "@basic-io @file-system @system-service";
164 SystemCallArchitectures = "native";
165 RestrictAddressFamilies = "";
166 };
167 };
168
169 systemd.services.sogo-ealarms = {
170 description = "SOGo email alarms";
171
172 after = [ "postgresql.service" "mysqld.service" "memcached.service" "openldap.service" "dovecot2.service" "sogo.service" ];
173 restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
174
175 startAt = [ "minutely" ];
176
177 serviceConfig = {
178 Type = "oneshot";
179 ExecStart = "${pkgs.sogo}/bin/sogo-ealarms-notify${optionalString (cfg.ealarmsCredFile != null) " -p ${cfg.ealarmsCredFile}"}";
180
181 ProtectSystem = "strict";
182 ProtectHome = true;
183 PrivateTmp = true;
184 PrivateDevices = true;
185 ProtectKernelTunables = true;
186 ProtectKernelModules = true;
187 ProtectControlGroups = true;
188 StateDirectory = "sogo/spool";
189
190 User = "sogo";
191 Group = "sogo";
192
193 CapabilityBoundingSet = "";
194 NoNewPrivileges = true;
195
196 LockPersonality = true;
197 RestrictRealtime = true;
198 PrivateMounts = true;
199 PrivateUsers = true;
200 MemoryDenyWriteExecute = true;
201 SystemCallFilter = "@basic-io @file-system @network-io @system-service";
202 SystemCallArchitectures = "native";
203 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
204 };
205 };
206
207 # nginx vhost
208 services.nginx.virtualHosts."${cfg.vhostName}" = {
209 locations."/".extraConfig = ''
210 rewrite ^ https://$server_name/SOGo;
211 allow all;
212 '';
213
214 # For iOS 7
215 locations."/principals/".extraConfig = ''
216 rewrite ^ https://$server_name/SOGo/dav;
217 allow all;
218 '';
219
220 locations."^~/SOGo".extraConfig = ''
221 proxy_pass http://127.0.0.1:20000;
222 proxy_redirect http://127.0.0.1:20000 default;
223
224 proxy_set_header X-Real-IP $remote_addr;
225 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
226 proxy_set_header Host $host;
227 proxy_set_header x-webobjects-server-protocol HTTP/1.0;
228 proxy_set_header x-webobjects-remote-host 127.0.0.1;
229 proxy_set_header x-webobjects-server-port $server_port;
230 proxy_set_header x-webobjects-server-name $server_name;
231 proxy_set_header x-webobjects-server-url $scheme://$host;
232 proxy_connect_timeout 90;
233 proxy_send_timeout 90;
234 proxy_read_timeout 90;
235 proxy_buffer_size 4k;
236 proxy_buffers 4 32k;
237 proxy_busy_buffers_size 64k;
238 proxy_temp_file_write_size 64k;
239 client_max_body_size 50m;
240 client_body_buffer_size 128k;
241 break;
242 '';
243
244 locations."/SOGo.woa/WebServerResources/".extraConfig = ''
245 alias ${pkgs.sogo}/lib/GNUstep/SOGo/WebServerResources/;
246 allow all;
247 '';
248
249 locations."/SOGo/WebServerResources/".extraConfig = ''
250 alias ${pkgs.sogo}/lib/GNUstep/SOGo/WebServerResources/;
251 allow all;
252 '';
253
254 locations."~ ^/SOGo/so/ControlPanel/Products/([^/]*)/Resources/(.*)$".extraConfig = ''
255 alias ${pkgs.sogo}/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
256 '';
257
258 locations."~ ^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\\.(jpg|png|gif|css|js)$".extraConfig = ''
259 alias ${pkgs.sogo}/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
260 '';
261 };
262
263 # User and group
264 users.groups.sogo = {};
265 users.users.sogo = {
266 group = "sogo";
267 isSystemUser = true;
268 description = "SOGo service user";
269 };
270 };
271}