]>
Commit | Line | Data |
---|---|---|
1 | { lib, pkgs, config, myconfig, mylibs, ... }: | |
2 | let | |
3 | cfg = config.services.myTasks; | |
4 | vardir = config.services.taskserver.dataDir; | |
5 | fqdn = "task.immae.eu"; | |
6 | user = config.services.taskserver.user; | |
7 | env = myconfig.env.tools.task; | |
8 | group = config.services.taskserver.group; | |
9 | taskserver-user-certs = pkgs.runCommand "taskserver-user-certs" {} '' | |
10 | mkdir -p $out/bin | |
11 | cat > $out/bin/taskserver-user-certs <<"EOF" | |
12 | #!/usr/bin/env bash | |
13 | ||
14 | user=$1 | |
15 | ||
16 | silent_certtool() { | |
17 | if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then | |
18 | echo "GNUTLS certtool invocation failed with output:" >&2 | |
19 | echo "$output" >&2 | |
20 | fi | |
21 | } | |
22 | ||
23 | silent_certtool -p \ | |
24 | --bits 4096 \ | |
25 | --outfile "${vardir}/userkeys/$user.key.pem" | |
26 | ${pkgs.gnused}/bin/sed -i -n -e '/^-----BEGIN RSA PRIVATE KEY-----$/,$p' "${vardir}/userkeys/$user.key.pem" | |
27 | ||
28 | silent_certtool -c \ | |
29 | --template "${pkgs.writeText "taskserver-ca.template" '' | |
30 | tls_www_client | |
31 | encryption_key | |
32 | signing_key | |
33 | expiration_days = 3650 | |
34 | ''}" \ | |
35 | --load-ca-certificate "${vardir}/keys/ca.cert" \ | |
36 | --load-ca-privkey "${vardir}/keys/ca.key" \ | |
37 | --load-privkey "${vardir}/userkeys/$user.key.pem" \ | |
38 | --outfile "${vardir}/userkeys/$user.cert.pem" | |
39 | EOF | |
40 | chmod a+x $out/bin/taskserver-user-certs | |
41 | patchShebangs $out/bin/taskserver-user-certs | |
42 | ''; | |
43 | taskwarrior-web = pkgs.callPackage ./taskwarrior-web.nix { | |
44 | inherit (mylibs) fetchedGithub; | |
45 | inherit env; | |
46 | }; | |
47 | taskwebPages = let | |
48 | uidPages = lib.attrsets.zipAttrs ( | |
49 | lib.lists.flatten | |
50 | (lib.attrsets.mapAttrsToList (k: c: map (v: { "${v}" = k; }) c.uid) env.taskwarrior-web) | |
51 | ); | |
52 | pages = lib.attrsets.mapAttrs (uid: items: | |
53 | if lib.lists.length items == 1 then | |
54 | '' | |
55 | <html> | |
56 | <head> | |
57 | <meta http-equiv="refresh" content="0; url=/taskweb/${lib.lists.head items}/" /> | |
58 | </head> | |
59 | <body></body> | |
60 | </html> | |
61 | '' | |
62 | else | |
63 | '' | |
64 | <html> | |
65 | <head> | |
66 | <title>To-do list disponibles</title> | |
67 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
68 | <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
69 | </head> | |
70 | <body> | |
71 | <ul> | |
72 | ${builtins.concatStringsSep "\n" (map (item: "<li><a href='/taskweb/${item}'>${item}</a></li>") items)} | |
73 | </ul> | |
74 | </body> | |
75 | </html> | |
76 | '' | |
77 | ) uidPages; | |
78 | in | |
79 | pkgs.runCommand "taskwerver-pages" {} '' | |
80 | mkdir -p $out/ | |
81 | ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (k: v: "cp ${pkgs.writeText k v} $out/${k}.html") pages)} | |
82 | echo "Please login" > $out/index.html | |
83 | ''; | |
84 | in { | |
85 | options.services.myTasks = { | |
86 | enable = lib.mkEnableOption "my tasks service"; | |
87 | }; | |
88 | ||
89 | config = lib.mkIf cfg.enable { | |
90 | mySecrets.keys = [{ | |
91 | dest = "webapps/tools-taskwarrior-web"; | |
92 | user = "wwwrun"; | |
93 | group = "wwwrun"; | |
94 | permissions = "0400"; | |
95 | text = '' | |
96 | SetEnv TASKD_HOST "${fqdn}:${toString config.services.taskserver.listenPort}" | |
97 | SetEnv TASKD_VARDIR "${vardir}" | |
98 | SetEnv TASKD_LDAP_HOST "ldaps://${env.ldap.host}" | |
99 | SetEnv TASKD_LDAP_DN "${env.ldap.dn}" | |
100 | SetEnv TASKD_LDAP_PASSWORD "${env.ldap.password}" | |
101 | SetEnv TASKD_LDAP_BASE "${env.ldap.base}" | |
102 | SetEnv TASKD_LDAP_FILTER "${env.ldap.search}" | |
103 | ''; | |
104 | }]; | |
105 | security.acme.certs."eldiron".extraDomains.${fqdn} = null; | |
106 | services.myWebsites.tools.modules = [ "proxy_fcgi" "sed" ]; | |
107 | services.myWebsites.tools.vhostConfs.task = { | |
108 | certName = "eldiron"; | |
109 | hosts = [ "task.immae.eu" ]; | |
110 | root = "/run/current-system/webapps/_task"; | |
111 | extraConfig = [ '' | |
112 | <Directory /run/current-system/webapps/_task> | |
113 | DirectoryIndex index.php | |
114 | Use LDAPConnect | |
115 | Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu | |
116 | <FilesMatch "\.php$"> | |
117 | SetHandler "proxy:unix:/var/run/phpfpm/task.sock|fcgi://localhost" | |
118 | </FilesMatch> | |
119 | Include /var/secrets/webapps/tools-taskwarrior-web | |
120 | </Directory> | |
121 | '' | |
122 | '' | |
123 | <Macro Taskwarrior %{folderName}> | |
124 | ProxyPass "unix://${taskwarrior-web.socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/" | |
125 | ProxyPassReverse "unix://${taskwarrior-web.socketsDir}/%{folderName}.sock|http://localhost-%{folderName}/" | |
126 | ProxyPassReverse http://${fqdn}/ | |
127 | ||
128 | SetOutputFilter Sed | |
129 | OutputSed "s|/ajax|/taskweb/%{folderName}/ajax|g" | |
130 | OutputSed "s|\([^x]\)/tasks|\1/taskweb/%{folderName}/tasks|g" | |
131 | OutputSed "s|\([^x]\)/projects|\1/taskweb/%{folderName}/projects|g" | |
132 | OutputSed "s|http://${fqdn}/|/taskweb/%{folderName}/|g" | |
133 | OutputSed "s|/img/relax.jpg|/taskweb/%{folderName}/img/relax.jpg|g" | |
134 | </Macro> | |
135 | '' | |
136 | '' | |
137 | Alias /taskweb ${taskwebPages} | |
138 | <Directory "${taskwebPages}"> | |
139 | DirectoryIndex index.html | |
140 | Require all granted | |
141 | </Directory> | |
142 | ||
143 | RewriteEngine on | |
144 | RewriteRule ^/taskweb$ /taskweb/ [R=301,L] | |
145 | RedirectMatch permanent ^/taskweb/([^/]+)$ /taskweb/$1/ | |
146 | ||
147 | RewriteCond %{LA-U:REMOTE_USER} !="" | |
148 | RewriteCond ${taskwebPages}/%{LA-U:REMOTE_USER}.html -f | |
149 | RewriteRule ^/taskweb/?$ ${taskwebPages}/%{LA-U:REMOTE_USER}.html [L] | |
150 | ||
151 | <Location /taskweb/> | |
152 | Use LDAPConnect | |
153 | Require ldap-group cn=users,cn=taskwarrior,ou=services,dc=immae,dc=eu | |
154 | </Location> | |
155 | '' | |
156 | ] ++ (lib.attrsets.mapAttrsToList (k: v: '' | |
157 | <Location /taskweb/${k}/> | |
158 | ${builtins.concatStringsSep "\n" (map (uid: "Require ldap-attribute uid=${uid}") v.uid)} | |
159 | ||
160 | Use Taskwarrior ${k} | |
161 | </Location> | |
162 | '') env.taskwarrior-web); | |
163 | }; | |
164 | services.myPhpfpm.poolConfigs = { | |
165 | tasks = '' | |
166 | listen = /var/run/phpfpm/task.sock | |
167 | user = ${user} | |
168 | group = ${group} | |
169 | listen.owner = wwwrun | |
170 | listen.group = wwwrun | |
171 | pm = dynamic | |
172 | pm.max_children = 60 | |
173 | pm.start_servers = 2 | |
174 | pm.min_spare_servers = 1 | |
175 | pm.max_spare_servers = 10 | |
176 | ||
177 | ; Needed to avoid clashes in browser cookies (same domain) | |
178 | env[PATH] = "/etc/profiles/per-user/${user}/bin" | |
179 | php_value[session.name] = TaskPHPSESSID | |
180 | php_admin_value[open_basedir] = "${./www}:/tmp:${vardir}:/etc/profiles/per-user/${user}/bin/" | |
181 | ''; | |
182 | }; | |
183 | ||
184 | system.extraSystemBuilderCmds = '' | |
185 | ln -s ${./www} $out/webapps/_task | |
186 | ''; | |
187 | ||
188 | security.acme.certs."task" = config.services.myCertificates.certConfig // { | |
189 | inherit user group; | |
190 | plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; | |
191 | domain = fqdn; | |
192 | postRun = '' | |
193 | systemctl restart taskserver.service | |
194 | ''; | |
195 | }; | |
196 | ||
197 | users.users.${user}.packages = [ taskserver-user-certs ]; | |
198 | ||
199 | system.activationScripts.taskserver = { | |
200 | deps = [ "users" ]; | |
201 | text = '' | |
202 | install -m 0750 -o ${user} -g ${group} -d ${vardir} | |
203 | install -m 0750 -o ${user} -g ${group} -d ${vardir}/userkeys | |
204 | install -m 0750 -o ${user} -g ${group} -d ${vardir}/keys | |
205 | ||
206 | if [ ! -e "${vardir}/keys/ca.key" ]; then | |
207 | silent_certtool() { | |
208 | if ! output="$("${pkgs.gnutls.bin}/bin/certtool" "$@" 2>&1)"; then | |
209 | echo "GNUTLS certtool invocation failed with output:" >&2 | |
210 | echo "$output" >&2 | |
211 | fi | |
212 | } | |
213 | ||
214 | silent_certtool -p \ | |
215 | --bits 4096 \ | |
216 | --outfile "${vardir}/keys/ca.key" | |
217 | ||
218 | silent_certtool -s \ | |
219 | --template "${pkgs.writeText "taskserver-ca.template" '' | |
220 | cn = ${fqdn} | |
221 | expiration_days = -1 | |
222 | cert_signing_key | |
223 | ca | |
224 | ''}" \ | |
225 | --load-privkey "${vardir}/keys/ca.key" \ | |
226 | --outfile "${vardir}/keys/ca.cert" | |
227 | ||
228 | chown :${group} "${vardir}/keys/ca.key" | |
229 | chmod g+r "${vardir}/keys/ca.key" | |
230 | fi | |
231 | ''; | |
232 | }; | |
233 | ||
234 | services.taskserver = { | |
235 | enable = true; | |
236 | allowedClientIDs = [ "^task [2-9]" "^Mirakel [1-9]" ]; | |
237 | inherit fqdn; | |
238 | listenHost = "::"; | |
239 | pki.manual.ca.cert = "${vardir}/keys/ca.cert"; | |
240 | pki.manual.server.cert = "/var/lib/acme/task/fullchain.pem"; | |
241 | pki.manual.server.crl = "/var/lib/acme/task/invalid.crl"; | |
242 | pki.manual.server.key = "/var/lib/acme/task/key.pem"; | |
243 | requestLimit = 104857600; | |
244 | }; | |
245 | ||
246 | system.activationScripts.taskwarrior-web = { | |
247 | deps = [ "users" ]; | |
248 | text = '' | |
249 | install -m 0755 -o ${user} -g ${group} -d ${taskwarrior-web.socketsDir} | |
250 | install -m 0750 -o ${user} -g ${group} -d ${taskwarrior-web.varDir} | |
251 | ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList | |
252 | (k: v: "install -m 0750 -o ${user} -g ${group} -d ${taskwarrior-web.varDir}/${k}") | |
253 | env.taskwarrior-web | |
254 | )} | |
255 | if [ ! -f ${vardir}/userkeys/taskwarrior-web.cert.pem ]; then | |
256 | ${taskserver-user-certs}/bin/taskserver-user-certs taskwarrior-web | |
257 | chown taskd:taskd ${vardir}/userkeys/taskwarrior-web.cert.pem ${vardir}/userkeys/taskwarrior-web.key.pem | |
258 | fi | |
259 | ''; | |
260 | }; | |
261 | ||
262 | systemd.services = (lib.attrsets.mapAttrs' (name: userConfig: | |
263 | let | |
264 | credentials = "${userConfig.org}/${name}/${userConfig.key}"; | |
265 | dateFormat = userConfig.date; | |
266 | taskrc = pkgs.writeText "taskrc" '' | |
267 | data.location=${taskwarrior-web.varDir}/${name} | |
268 | taskd.certificate=${vardir}/userkeys/taskwarrior-web.cert.pem | |
269 | taskd.key=${vardir}/userkeys/taskwarrior-web.key.pem | |
270 | # IdenTrust DST Root CA X3 | |
271 | # obtained here: https://letsencrypt.org/fr/certificates/ | |
272 | taskd.ca=${pkgs.writeText "ca.cert" '' | |
273 | -----BEGIN CERTIFICATE----- | |
274 | MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ | |
275 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT | |
276 | DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow | |
277 | PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD | |
278 | Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB | |
279 | AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O | |
280 | rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq | |
281 | OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b | |
282 | xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw | |
283 | 7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD | |
284 | aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV | |
285 | HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG | |
286 | SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 | |
287 | ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr | |
288 | AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz | |
289 | R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 | |
290 | JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo | |
291 | Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ | |
292 | -----END CERTIFICATE-----''} | |
293 | taskd.server=${fqdn}:${toString config.services.taskserver.listenPort} | |
294 | taskd.credentials=${credentials} | |
295 | dateformat=${dateFormat} | |
296 | ''; | |
297 | in lib.attrsets.nameValuePair "taskwarrior-web-${name}" { | |
298 | description = "Taskwarrior webapp for ${name}"; | |
299 | wantedBy = [ "multi-user.target" ]; | |
300 | after = [ "network.target" ]; | |
301 | path = [ pkgs.taskwarrior ]; | |
302 | ||
303 | environment.TASKRC = taskrc; | |
304 | environment.BUNDLE_PATH = "${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}"; | |
305 | environment.BUNDLE_GEMFILE = "${taskwarrior-web.gems.confFiles}/Gemfile"; | |
306 | environment.LC_ALL = "fr_FR.UTF-8"; | |
307 | ||
308 | script = '' | |
309 | exec ${taskwarrior-web.gems}/${taskwarrior-web.gems.ruby.gemPath}/bin/bundle exec thin start -R config.ru -S ${taskwarrior-web.socketsDir}/${name}.sock | |
310 | ''; | |
311 | ||
312 | serviceConfig = { | |
313 | User = user; | |
314 | PrivateTmp = true; | |
315 | Restart = "always"; | |
316 | TimeoutSec = 60; | |
317 | Type = "simple"; | |
318 | WorkingDirectory = taskwarrior-web.rubyRoot; | |
319 | }; | |
320 | ||
321 | unitConfig.RequiresMountsFor = taskwarrior-web.varDir; | |
322 | }) env.taskwarrior-web) // { | |
323 | taskserver-ca.postStart = '' | |
324 | chown :${group} "${vardir}/keys/ca.key" | |
325 | chmod g+r "${vardir}/keys/ca.key" | |
326 | ''; | |
327 | }; | |
328 | ||
329 | }; | |
330 | } |