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