aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2019-07-01 22:07:52 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2019-07-01 22:07:52 +0200
commitafcc5de071dfffdc507995d1845372ba40dc1dc2 (patch)
treec96fe6b4d915e7382316a57d0d626760a7fd2876
parent2f16a987d306cdb7bf9b4e80fa4af173373719bd (diff)
downloadNix-afcc5de071dfffdc507995d1845372ba40dc1dc2.tar.gz
Nix-afcc5de071dfffdc507995d1845372ba40dc1dc2.tar.zst
Nix-afcc5de071dfffdc507995d1845372ba40dc1dc2.zip
Implement mta-sts and move mail services to specific domain
-rw-r--r--modules/private/default.nix1
-rw-r--r--modules/private/dns.nix16
-rw-r--r--modules/private/mail/postfix.nix7
-rw-r--r--modules/private/websites/default.nix1
-rw-r--r--modules/private/websites/tools/mail/default.nix75
-rw-r--r--modules/private/websites/tools/mail/mta-sts.nix55
-rw-r--r--modules/private/websites/tools/mail/rainloop.nix (renamed from modules/private/websites/tools/tools/rainloop.nix)0
-rw-r--r--modules/private/websites/tools/mail/roundcubemail.nix (renamed from modules/private/websites/tools/tools/roundcubemail.nix)0
-rw-r--r--modules/private/websites/tools/mail/www/index.html73
-rw-r--r--modules/private/websites/tools/tools/default.nix35
10 files changed, 228 insertions, 35 deletions
diff --git a/modules/private/default.nix b/modules/private/default.nix
index 026e69d..552ee8c 100644
--- a/modules/private/default.nix
+++ b/modules/private/default.nix
@@ -46,6 +46,7 @@ set = {
46 mgoblinTool = ./websites/tools/mgoblin; 46 mgoblinTool = ./websites/tools/mgoblin;
47 peertubeTool = ./websites/tools/peertube; 47 peertubeTool = ./websites/tools/peertube;
48 toolsTool = ./websites/tools/tools; 48 toolsTool = ./websites/tools/tools;
49 mailTool = ./websites/tools/mail;
49 50
50 mail = ./mail; 51 mail = ./mail;
51 mailMilters = ./mail/milters.nix; 52 mailMilters = ./mail/milters.nix;
diff --git a/modules/private/dns.nix b/modules/private/dns.nix
index 6647c14..01a3cbb 100644
--- a/modules/private/dns.nix
+++ b/modules/private/dns.nix
@@ -94,10 +94,10 @@
94 ${conf.entries} 94 ${conf.entries}
95 95
96 ${if lib.attrsets.hasAttr "withEmail" conf && lib.lists.length conf.withEmail > 0 then '' 96 ${if lib.attrsets.hasAttr "withEmail" conf && lib.lists.length conf.withEmail > 0 then ''
97 mail IN A ${myconfig.env.servers.immaeEu.ips.main.ip4}
98 mx-1 IN A ${myconfig.env.servers.eldiron.ips.main.ip4} 97 mx-1 IN A ${myconfig.env.servers.eldiron.ips.main.ip4}
99 ${builtins.concatStringsSep "\n" (map (i: "mail IN AAAA ${i}") myconfig.env.servers.immaeEu.ips.main.ip6)} 98 mx-2 IN A ${myconfig.env.servers.immaeEu.ips.main.ip4}
100 ${builtins.concatStringsSep "\n" (map (i: "mx-1 IN AAAA ${i}") myconfig.env.servers.eldiron.ips.main.ip6)} 99 ${builtins.concatStringsSep "\n" (map (i: "mx-1 IN AAAA ${i}") myconfig.env.servers.eldiron.ips.main.ip6)}
100 ${builtins.concatStringsSep "\n" (map (i: "mx-2 IN AAAA ${i}") myconfig.env.servers.immaeEu.ips.main.ip6)}
101 ${lib.concatStringsSep "\n\n" (map (e: 101 ${lib.concatStringsSep "\n\n" (map (e:
102 let 102 let
103 n = if e.domain == "" then "@" else "${e.domain} "; 103 n = if e.domain == "" then "@" else "${e.domain} ";
@@ -105,8 +105,8 @@
105 in 105 in
106 '' 106 ''
107 ; ------------------ mail: ${n} --------------------------- 107 ; ------------------ mail: ${n} ---------------------------
108 ${n} IN MX 10 mail.${conf.name}. 108 ${n} IN MX 10 mx-1.${conf.name}.
109 ${n} IN MX 50 mx-1.${conf.name}. 109 ${n} IN MX 20 mx-2.${conf.name}.
110 110
111 ; https://tools.ietf.org/html/rfc6186 111 ; https://tools.ietf.org/html/rfc6186
112 _submission._tcp${suffix} SRV 0 1 587 smtp.immae.eu. 112 _submission._tcp${suffix} SRV 0 1 587 smtp.immae.eu.
@@ -116,6 +116,14 @@
116 _pop3s._tcp${suffix} SRV 10 1 995 pop3.immae.eu. 116 _pop3s._tcp${suffix} SRV 10 1 995 pop3.immae.eu.
117 _sieve._tcp${suffix} SRV 0 1 4190 imap.immae.eu. 117 _sieve._tcp${suffix} SRV 0 1 4190 imap.immae.eu.
118 118
119 ; MTA-STS
120 ; https://blog.delouw.ch/2018/12/16/using-mta-sts-to-enhance-email-transport-security-and-privacy/
121 ; https://support.google.com/a/answer/9261504
122 _mta-sts${suffix} IN TXT "v=STSv1;id=20190630054629Z"
123 _smtp._tls${suffix} IN TXT "v=TLSRPTv1;rua=mailto:postmaster+mta-sts@immae.eu"
124 mta-sts${suffix} IN A ${myconfig.env.servers.eldiron.ips.main.ip4}
125 ${builtins.concatStringsSep "\n" (map (i: "mta-sts${suffix} IN AAAA ${i}") myconfig.env.servers.eldiron.ips.main.ip6)}
126
119 ; Mail sender authentications 127 ; Mail sender authentications
120 ${n} IN TXT "v=spf1 mx ~all" 128 ${n} IN TXT "v=spf1 mx ~all"
121 _dmarc${suffix} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; fo=1; rua=mailto:postmaster+rua@immae.eu; ruf=mailto:postmaster+ruf@immae.eu;" 129 _dmarc${suffix} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; fo=1; rua=mailto:postmaster+rua@immae.eu; ruf=mailto:postmaster+ruf@immae.eu;"
diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix
index 53bf650..dfe6129 100644
--- a/modules/private/mail/postfix.nix
+++ b/modules/private/mail/postfix.nix
@@ -190,6 +190,13 @@
190 milter_macro_daemon_name = "ORIGINATING"; 190 milter_macro_daemon_name = "ORIGINATING";
191 smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; 191 smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}";
192 }; 192 };
193 # FIXME: Mail adressed to localhost.immae.eu will still have mx-1 as
194 # prioritized MX, which provokes "mail for localhost.immae.eu loops
195 # back to myself" errors. This transport entry forces to push
196 # e-mails to its right destination.
197 transport = ''
198 localhost.immae.eu smtp:[immae.eu]:25
199 '';
193 destination = ["localhost"]; 200 destination = ["localhost"];
194 # This needs to reverse DNS 201 # This needs to reverse DNS
195 hostname = "eldiron.immae.eu"; 202 hostname = "eldiron.immae.eu";
diff --git a/modules/private/websites/default.nix b/modules/private/websites/default.nix
index add4e42..63dd53d 100644
--- a/modules/private/websites/default.nix
+++ b/modules/private/websites/default.nix
@@ -264,6 +264,7 @@ in
264 tools.mediagoblin.enable = true; 264 tools.mediagoblin.enable = true;
265 tools.peertube.enable = true; 265 tools.peertube.enable = true;
266 tools.tools.enable = true; 266 tools.tools.enable = true;
267 tools.email.enable = true;
267 }; 268 };
268 }; 269 };
269} 270}
diff --git a/modules/private/websites/tools/mail/default.nix b/modules/private/websites/tools/mail/default.nix
new file mode 100644
index 0000000..ea0a27f
--- /dev/null
+++ b/modules/private/websites/tools/mail/default.nix
@@ -0,0 +1,75 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 roundcubemail = pkgs.callPackage ./roundcubemail.nix {
4 inherit (pkgs.webapps) roundcubemail roundcubemail-plugins roundcubemail-skins;
5 env = myconfig.env.tools.roundcubemail;
6 };
7 rainloop = pkgs.callPackage ./rainloop.nix {};
8 cfg = config.myServices.websites.tools.email;
9in
10{
11 options.myServices.websites.tools.email = {
12 enable = lib.mkEnableOption "enable email website";
13 };
14
15 imports = [
16 ./mta-sts.nix
17 ];
18
19 config = lib.mkIf cfg.enable {
20 secrets.keys = roundcubemail.keys;
21
22 services.websites.env.tools.modules =
23 [ "proxy_fcgi" ]
24 ++ rainloop.apache.modules
25 ++ roundcubemail.apache.modules;
26
27 services.websites.env.tools.vhostConfs.mail = {
28 certName = "mail";
29 addToCerts = true;
30 hosts = ["mail.immae.eu"];
31 root = "/run/current-system/webapps/_mail";
32 extraConfig = [
33 rainloop.apache.vhostConf
34 roundcubemail.apache.vhostConf
35 ''
36 <Directory /run/current-system/webapps/_mail>
37 Require all granted
38 Options -Indexes
39 </Directory>
40 ''
41 ];
42 };
43 systemd.services = {
44 phpfpm-rainloop = {
45 after = lib.mkAfter rainloop.phpFpm.serviceDeps;
46 wants = rainloop.phpFpm.serviceDeps;
47 };
48 phpfpm-roundcubemail = {
49 after = lib.mkAfter roundcubemail.phpFpm.serviceDeps;
50 wants = roundcubemail.phpFpm.serviceDeps;
51 };
52 };
53
54 services.phpfpm.pools.roundcubemail = {
55 listen = roundcubemail.phpFpm.socket;
56 extraConfig = roundcubemail.phpFpm.pool;
57 phpOptions = config.services.phpfpm.phpOptions + roundcubemail.phpFpm.phpConfig;
58 };
59 services.phpfpm.poolConfigs = {
60 rainloop = rainloop.phpFpm.pool;
61 };
62 system.activationScripts = {
63 roundcubemail = roundcubemail.activationScript;
64 rainloop = rainloop.activationScript;
65 };
66
67 myServices.websites.webappDirs = {
68 _mail = ./www;
69 "${roundcubemail.apache.webappName}" = roundcubemail.webRoot;
70 "${rainloop.apache.webappName}" = rainloop.webRoot;
71 };
72
73 };
74
75}
diff --git a/modules/private/websites/tools/mail/mta-sts.nix b/modules/private/websites/tools/mail/mta-sts.nix
new file mode 100644
index 0000000..bedefda
--- /dev/null
+++ b/modules/private/websites/tools/mail/mta-sts.nix
@@ -0,0 +1,55 @@
1{ lib, pkgs, config, myconfig, ... }:
2let
3 domains = (lib.remove null (lib.flatten (map
4 (zone: map
5 (e: if e.receive
6 then {
7 domain = "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}";
8 mail = zone.name;
9 }
10 else null
11 )
12 (zone.withEmail or [])
13 )
14 myconfig.env.dns.masterZones
15 )));
16 # FIXME: increase the id number in modules/private/dns.nix when this
17 # file change (date -u +'%Y%m%d%H%M%S'Z)
18 file = domain: pkgs.writeText "mta-sts-${domain.domain}.txt" ''
19 version: STSv1
20 mode: testing
21 mx: mx-1.${domain.mail}
22 mx: mx-2.${domain.mail}
23 max_age: 604800
24 '';
25 root = pkgs.runCommand "mta-sts_root" {} ''
26 mkdir -p $out
27 ${builtins.concatStringsSep "\n" (map (d:
28 "cp ${file d} $out/${d.domain}.txt"
29 ) domains)}
30 '';
31in
32{
33 config.myServices.websites.webappDirs = {
34 _mta-sts = root;
35 };
36
37 config.services.websites.env.tools.vhostConfs.mta_sts = {
38 certName = "mail";
39 addToCerts = true;
40 hosts = ["mta-sts.mail.immae.eu"] ++ map (v: "mta-sts.${v.domain}") domains;
41 root = "/run/current-system/webapps/_mta-sts";
42 extraConfig = [
43 ''
44 RewriteEngine on
45 RewriteCond %{HTTP_HOST} ^mta-sts.(.*)$
46 RewriteRule ^/.well-known/mta-sts.txt$ %{DOCUMENT_ROOT}/%1.txt [L]
47 <Directory /run/current-system/webapps/_mta-sts>
48 Require all granted
49 Options -Indexes
50 </Directory>
51 ''
52 ];
53 };
54
55}
diff --git a/modules/private/websites/tools/tools/rainloop.nix b/modules/private/websites/tools/mail/rainloop.nix
index dbf0f24..dbf0f24 100644
--- a/modules/private/websites/tools/tools/rainloop.nix
+++ b/modules/private/websites/tools/mail/rainloop.nix
diff --git a/modules/private/websites/tools/tools/roundcubemail.nix b/modules/private/websites/tools/mail/roundcubemail.nix
index 8bb60d6..8bb60d6 100644
--- a/modules/private/websites/tools/tools/roundcubemail.nix
+++ b/modules/private/websites/tools/mail/roundcubemail.nix
diff --git a/modules/private/websites/tools/mail/www/index.html b/modules/private/websites/tools/mail/www/index.html
new file mode 100644
index 0000000..3727c42
--- /dev/null
+++ b/modules/private/websites/tools/mail/www/index.html
@@ -0,0 +1,73 @@
1<!doctype html>
2<html lang="fr">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>E-mail configuration</title>
7 <style type="text/css">
8 body {
9 padding-top: 1em;
10 padding-left: 5px;
11 padding-right: 5px;
12 text-align: left;
13 margin: auto;
14 font: 20px Helvetica, sans-serif;
15 color: #333;
16 height: 100%;
17 min-height: 100%;
18 }
19 article {
20 text-align: justify;
21 display: block;
22 max-width: 850px;
23 margin: 0 auto;
24 padding-top: 30px;
25 }
26 span.code {
27 font-family: monospace;
28 }
29 </style>
30 </head>
31 <body>
32 <p>
33 Email configuration. For automatic configuration in your smart e-mail
34 client, use <span class="code">login@mail.immae.eu</span>. If it
35 doesn’t work, the details are there:
36 <ul>
37 <li>IMAP: <span class="code">imap.immae.eu</span>
38 <ul>
39 <li>No unencrypted access</li>
40 <li>STARTTLS: 143 (recommended)</li>
41 <li>SSL: 993</li>
42 </ul>
43 </li>
44 <li>POP3: <span class="code">pop3.immae.eu</span>
45 <ul>
46 <li>No unencrypted access</li>
47 <li>STARTTLS: 110 (recommended)</li>
48 <li>SSL: 995</li>
49 </ul>
50 </li>
51 <li>SMTP: <span class="code">smtp.immae.eu</span>
52 <ul>
53 <li>No unencrypted access</li>
54 <li>STARTTLS: 587</li>
55 </ul>
56 </li>
57 <li>Sieve: <span class="code">imap.immae.eu</span>
58 <ul>
59 <li>No unencrypted access</li>
60 <li>STARTTLS: 4190</li>
61 </ul>
62 </li>
63 </ul>
64 </p>
65 <p>Webmails:
66 <ul>
67 <li><a href="/roundcube">Roundcube</a></li>
68 <li><a href="/rainloop">Rainloop</a> (experimental)</li>
69 </ul>
70 </p>
71 </body>
72</html>
73
diff --git a/modules/private/websites/tools/tools/default.nix b/modules/private/websites/tools/tools/default.nix
index 9908d99..5b368e0 100644
--- a/modules/private/websites/tools/tools/default.nix
+++ b/modules/private/websites/tools/tools/default.nix
@@ -10,11 +10,6 @@ let
10 inherit (pkgs.webapps) ttrss ttrss-plugins; 10 inherit (pkgs.webapps) ttrss ttrss-plugins;
11 env = myconfig.env.tools.ttrss; 11 env = myconfig.env.tools.ttrss;
12 }; 12 };
13 roundcubemail = pkgs.callPackage ./roundcubemail.nix {
14 inherit (pkgs.webapps) roundcubemail roundcubemail-plugins roundcubemail-skins;
15 env = myconfig.env.tools.roundcubemail;
16 };
17 rainloop = pkgs.callPackage ./rainloop.nix {};
18 kanboard = pkgs.callPackage ./kanboard.nix { 13 kanboard = pkgs.callPackage ./kanboard.nix {
19 env = myconfig.env.tools.kanboard; 14 env = myconfig.env.tools.kanboard;
20 }; 15 };
@@ -51,21 +46,16 @@ in {
51 secrets.keys = 46 secrets.keys =
52 kanboard.keys 47 kanboard.keys
53 ++ ldap.keys 48 ++ ldap.keys
54 ++ roundcubemail.keys
55 ++ shaarli.keys 49 ++ shaarli.keys
56 ++ ttrss.keys 50 ++ ttrss.keys
57 ++ wallabag.keys 51 ++ wallabag.keys
58 ++ yourls.keys; 52 ++ yourls.keys;
59 53
60 services.websites.env.integration.modules =
61 rainloop.apache.modules;
62
63 services.websites.env.tools.modules = 54 services.websites.env.tools.modules =
64 [ "proxy_fcgi" ] 55 [ "proxy_fcgi" ]
65 ++ adminer.apache.modules 56 ++ adminer.apache.modules
66 ++ ympd.apache.modules 57 ++ ympd.apache.modules
67 ++ ttrss.apache.modules 58 ++ ttrss.apache.modules
68 ++ roundcubemail.apache.modules
69 ++ wallabag.apache.modules 59 ++ wallabag.apache.modules
70 ++ yourls.apache.modules 60 ++ yourls.apache.modules
71 ++ rompr.apache.modules 61 ++ rompr.apache.modules
@@ -90,7 +80,6 @@ in {
90 </FilesMatch> 80 </FilesMatch>
91 </Directory> 81 </Directory>
92 '' 82 ''
93 rainloop.apache.vhostConf
94 ]; 83 ];
95 }; 84 };
96 85
@@ -101,6 +90,8 @@ in {
101 root = "/var/lib/ftp/tools.immae.eu"; 90 root = "/var/lib/ftp/tools.immae.eu";
102 extraConfig = [ 91 extraConfig = [
103 '' 92 ''
93 RedirectMatch 301 ^/roundcube(.*)$ https://mail.immae.eu/roundcube$1
94
104 <Directory "/var/lib/ftp/tools.immae.eu"> 95 <Directory "/var/lib/ftp/tools.immae.eu">
105 DirectoryIndex index.php index.htm index.html 96 DirectoryIndex index.php index.htm index.html
106 AllowOverride all 97 AllowOverride all
@@ -113,7 +104,6 @@ in {
113 adminer.apache.vhostConf 104 adminer.apache.vhostConf
114 ympd.apache.vhostConf 105 ympd.apache.vhostConf
115 ttrss.apache.vhostConf 106 ttrss.apache.vhostConf
116 roundcubemail.apache.vhostConf
117 wallabag.apache.vhostConf 107 wallabag.apache.vhostConf
118 yourls.apache.vhostConf 108 yourls.apache.vhostConf
119 rompr.apache.vhostConf 109 rompr.apache.vhostConf
@@ -145,6 +135,8 @@ in {
145 135
146 RedirectMatch 301 ^/taskweb(.*)$ https://task.immae.eu/taskweb$1 136 RedirectMatch 301 ^/taskweb(.*)$ https://task.immae.eu/taskweb$1
147 137
138 RedirectMatch 301 ^/roundcube(.*)$ https://mail.immae.eu/roundcube$1
139
148 RedirectMatch 301 ^/(.*)$ https://tools.immae.eu/$1 140 RedirectMatch 301 ^/(.*)$ https://tools.immae.eu/$1
149 '' 141 ''
150 ]; 142 ];
@@ -163,14 +155,6 @@ in {
163 after = lib.mkAfter ldap.phpFpm.serviceDeps; 155 after = lib.mkAfter ldap.phpFpm.serviceDeps;
164 wants = ldap.phpFpm.serviceDeps; 156 wants = ldap.phpFpm.serviceDeps;
165 }; 157 };
166 phpfpm-rainloop = {
167 after = lib.mkAfter rainloop.phpFpm.serviceDeps;
168 wants = rainloop.phpFpm.serviceDeps;
169 };
170 phpfpm-roundcubemail = {
171 after = lib.mkAfter roundcubemail.phpFpm.serviceDeps;
172 wants = roundcubemail.phpFpm.serviceDeps;
173 };
174 phpfpm-shaarli = { 158 phpfpm-shaarli = {
175 after = lib.mkAfter shaarli.phpFpm.serviceDeps; 159 after = lib.mkAfter shaarli.phpFpm.serviceDeps;
176 wants = shaarli.phpFpm.serviceDeps; 160 wants = shaarli.phpFpm.serviceDeps;
@@ -217,12 +201,6 @@ in {
217 paths = [ "/var/secrets/mpd" ]; 201 paths = [ "/var/secrets/mpd" ];
218 }; 202 };
219 203
220 services.phpfpm.pools.roundcubemail = {
221 listen = roundcubemail.phpFpm.socket;
222 extraConfig = roundcubemail.phpFpm.pool;
223 phpOptions = config.services.phpfpm.phpOptions + roundcubemail.phpFpm.phpConfig;
224 };
225
226 services.phpfpm.pools.devtools = { 204 services.phpfpm.pools.devtools = {
227 listen = "/var/run/phpfpm/devtools.sock"; 205 listen = "/var/run/phpfpm/devtools.sock";
228 extraConfig = '' 206 extraConfig = ''
@@ -254,7 +232,6 @@ in {
254 shaarli = shaarli.phpFpm.pool; 232 shaarli = shaarli.phpFpm.pool;
255 dokuwiki = dokuwiki.phpFpm.pool; 233 dokuwiki = dokuwiki.phpFpm.pool;
256 ldap = ldap.phpFpm.pool; 234 ldap = ldap.phpFpm.pool;
257 rainloop = rainloop.phpFpm.pool;
258 kanboard = kanboard.phpFpm.pool; 235 kanboard = kanboard.phpFpm.pool;
259 tools = '' 236 tools = ''
260 listen = /var/run/phpfpm/tools.sock 237 listen = /var/run/phpfpm/tools.sock
@@ -277,13 +254,11 @@ in {
277 system.activationScripts = { 254 system.activationScripts = {
278 adminer = adminer.activationScript; 255 adminer = adminer.activationScript;
279 ttrss = ttrss.activationScript; 256 ttrss = ttrss.activationScript;
280 roundcubemail = roundcubemail.activationScript;
281 wallabag = wallabag.activationScript; 257 wallabag = wallabag.activationScript;
282 yourls = yourls.activationScript; 258 yourls = yourls.activationScript;
283 rompr = rompr.activationScript; 259 rompr = rompr.activationScript;
284 shaarli = shaarli.activationScript; 260 shaarli = shaarli.activationScript;
285 dokuwiki = dokuwiki.activationScript; 261 dokuwiki = dokuwiki.activationScript;
286 rainloop = rainloop.activationScript;
287 kanboard = kanboard.activationScript; 262 kanboard = kanboard.activationScript;
288 ldap = ldap.activationScript; 263 ldap = ldap.activationScript;
289 }; 264 };
@@ -293,12 +268,10 @@ in {
293 "${dokuwiki.apache.webappName}" = dokuwiki.webRoot; 268 "${dokuwiki.apache.webappName}" = dokuwiki.webRoot;
294 "${ldap.apache.webappName}" = "${ldap.webRoot}/htdocs"; 269 "${ldap.apache.webappName}" = "${ldap.webRoot}/htdocs";
295 "${rompr.apache.webappName}" = rompr.webRoot; 270 "${rompr.apache.webappName}" = rompr.webRoot;
296 "${roundcubemail.apache.webappName}" = roundcubemail.webRoot;
297 "${shaarli.apache.webappName}" = shaarli.webRoot; 271 "${shaarli.apache.webappName}" = shaarli.webRoot;
298 "${ttrss.apache.webappName}" = ttrss.webRoot; 272 "${ttrss.apache.webappName}" = ttrss.webRoot;
299 "${wallabag.apache.webappName}" = wallabag.webRoot; 273 "${wallabag.apache.webappName}" = wallabag.webRoot;
300 "${yourls.apache.webappName}" = yourls.webRoot; 274 "${yourls.apache.webappName}" = yourls.webRoot;
301 "${rainloop.apache.webappName}" = rainloop.webRoot;
302 "${kanboard.apache.webappName}" = kanboard.webRoot; 275 "${kanboard.apache.webappName}" = kanboard.webRoot;
303 }; 276 };
304 277