]> Git — Sourcephile - sourcephile-nix.git/blob - machines/losurdo/nginx/sourcephile.fr/cryptpad.nix
neomutt: fix neomuch
[sourcephile-nix.git] / machines / losurdo / nginx / sourcephile.fr / cryptpad.nix
1 { domain, ... }:
2 { pkgs, lib, config, machineName, ... }:
3 let
4 inherit (config) networking;
5 inherit (config.services) nginx;
6 srv = "cryptpad";
7
8 # CryptPad serves static assets over these two domains.
9 # `main_domain` is what users will enter in their address bar.
10 # Privileged computation such as key management is handled in this scope
11 # UI content is loaded via the `sandbox_domain`.
12 # "Content Security Policy" headers prevent content loaded via the sandbox
13 # from accessing privileged information.
14 # These variables must be different to take advantage of CryptPad's sandboxing techniques.
15 # In the event of an XSS vulnerability in CryptPad's front-end code
16 # this will limit the amount of information accessible to attackers.
17 main_domain = "${srv}.${domain}";
18 sandbox_domain = "${srv}-sandbox.${domain}";
19
20 # CryptPad's dynamic content (websocket traffic and encrypted blobs)
21 # can be served over separate domains. Using dedicated domains (or subdomains)
22 # for these purposes allows you to move them to a separate machine at a later date
23 # if you find that a single machine cannot handle all of your users.
24 # If you don't use dedicated domains, this can be the same as $main_domain
25 # If you do, they'll be added as exceptions to any rules which block connections to remote domains.
26 api_domain = "${srv}-api.${domain}";
27 files_domain = "${srv}-files.${domain}";
28
29 # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain
30 styleSrc = "'unsafe-inline' 'self' ${main_domain}";
31
32 # connect-src restricts URLs which can be loaded using script interfaces
33 connectSrc = "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}";
34
35 # fonts can be loaded from data-URLs or the main domain
36 fontSrc = "'self' data: ${main_domain}";
37
38 # images can be loaded from anywhere, though we'd like to deprecate this as it allows the use of images for tracking
39 imgSrc = "'self' data: * blob: ${main_domain}";
40
41 # frame-src specifies valid sources for nested browsing contexts.
42 # this prevents loading any iframes from anywhere other than the sandbox domain
43 frameSrc = "'self' ${sandbox_domain} blob:";
44
45 # specifies valid sources for loading media using video or audio
46 mediaSrc = "'self' data: * blob: ${main_domain}";
47
48 # defines valid sources for webworkers and nested browser contexts
49 # deprecated in favour of worker-src and frame-src
50 childSrc = "https://${main_domain}";
51
52 # specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts.
53 # supercedes child-src but is unfortunately not yet universally supported.
54 workerSrc = "https://${main_domain}";
55
56 # add_header clear all parent add_header
57 # and thus must be repeated in sub-blocks
58 add_headers = ''
59 ${nginx.configs.https_add_headers}
60 add_header Access-Control-Allow-Origin "*";
61 add_header Cache-Control $cacheControl;
62 add_header X-Frame-Options "";
63 add_header Content-Security-Policy "default-src 'none'; child-src ${childSrc}; worker-src ${workerSrc}; media-src ${mediaSrc}; style-src ${styleSrc}; script-src $scriptSrc; connect-src ${connectSrc}; font-src ${fontSrc}; img-src ${imgSrc}; frame-src ${frameSrc};";
64 '';
65 cryptpad_root = "/var/lib/nginx/cryptpad";
66 in
67 {
68 services.cryptpad = {
69 enable = true;
70 # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js
71 configFile = pkgs.writeText "config.js" ''
72 module.exports = {
73 httpUnsafeOrigin: 'https://${main_domain}/',
74 httpSafeOrigin: "https://${sandbox_domain}/",
75 httpAddress: '::1',
76 httpPort: 3000,
77 httpSafePort: 3001,
78 maxWorkers: 1,
79
80 /* =====================
81 * Admin
82 * ===================== */
83 adminKeys: [
84 "https://cryptpad.sourcephile.fr/user/#/1/julm/s9y2aE8C5INMZSrR2yQbWBNcGpB8nSLZGFVhGHE8Nn8=",
85 ],
86 // supportMailboxPublicKey: "",
87
88 removeDonateButton: true,
89 disableAnonymousStore: true,
90 disableCrowdfundingMessages: true,
91 adminEmail: 'root+cryptpad@sourcephile.fr',
92 blockDailyCheck: true,
93 defaultStorageLimit: 1024 * 1024 * 1024,
94
95 /* =====================
96 * STORAGE
97 * ===================== */
98 //inactiveTime: 90, // days
99 //archiveRetentionTime: 15,
100 maxUploadSize: 256 * 1024 * 1024,
101
102 /*
103 customLimits: {
104 "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
105 limit: 20 * 1024 * 1024 * 1024,
106 plan: 'insider',
107 note: 'storage space donated by my.awesome.website'
108 },
109 "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": {
110 limit: 10 * 1024 * 1024 * 1024,
111 plan: 'insider',
112 note: 'storage space donated by my.awesome.website'
113 }
114 },
115 */
116
117 //premiumUploadSize: 100 * 1024 * 1024,
118
119 /* =====================
120 * DATABASE VOLUMES
121 * ===================== */
122 filePath: './datastore/',
123 archivePath: './data/archive',
124 pinPath: './data/pins',
125 taskPath: './data/tasks',
126 blockPath: './block',
127 blobPath: './blob',
128 blobStagingPath: './data/blobstage',
129 logPath: './data/logs',
130
131 /* =====================
132 * Debugging
133 * ===================== */
134 logToStdout: false,
135 logLevel: 'info',
136 logFeedback: false,
137 verbose: false,
138 };
139 '';
140 };
141
142 fileSystems."/var/lib/private/cryptpad" = {
143 device = "${machineName}/var/cryptpad";
144 fsType = "zfs";
145 };
146
147 services.sanoid.datasets = {
148 "${machineName}/var/cryptpad" = {
149 use_template = [ "local" ];
150 daily = 31;
151 };
152 };
153
154 services.syncoid.commands = {
155 "${machineName}/var/cryptpad" = {
156 sendOptions = "raw";
157 target = "backup@mermet.${networking.domain}:rpool/backup/${machineName}/var/cryptpad";
158 };
159 };
160
161 systemd.services.nginx = {
162 #after = [ "cryptpad.service" ];
163 #wants = [ "cryptpad.service" ];
164 serviceConfig = {
165 BindReadOnlyPaths = [
166 "/var/lib/private/cryptpad:${cryptpad_root}"
167 ];
168 LogsDirectory = lib.mkForce [
169 "nginx/${domain}/${srv}"
170 ];
171 };
172 };
173
174 services.nginx.virtualHosts.${main_domain} = {
175 serverAliases = [ sandbox_domain ];
176 listen = [
177 { addr="0.0.0.0"; port = 443; ssl = true; }
178 { addr="[::]"; port = 443; ssl = true; }
179 ];
180 useACMEHost = domain;
181 forceSSL = true;
182
183 # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/docs/example.nginx.conf
184 root = "${pkgs.cryptpad}/lib/node_modules/cryptpad";
185
186 # The nodejs process can handle all traffic whether accessed over websocket or as static assets
187 # We prefer to serve static content from nginx directly and to leave the API server to handle
188 # the dynamic content that only it can manage. This is primarily an optimization
189 locations."^~ /cryptpad_websocket" = {
190 proxyPass = "http://[::1]:3000";
191 proxyWebsockets = true;
192 };
193
194 locations."^~ /customize.dist/" = {
195 # This is needed in order to prevent infinite recursion between /customize/ and the root
196 };
197 # try to load customizeable content via /customize/ and fall back to the default content
198 # located at /customize.dist/
199 # This is what allows you to override behaviour.
200 locations."^~ /customize/".extraConfig = ''
201 rewrite ^/customize/(.*)$ $1 break;
202 try_files /customize/$uri /customize.dist/$uri;
203 '';
204
205 # /api/config is loaded once per page load and is used to retrieve
206 # the caching variable which is applied to every other resource
207 # which is loaded during that session.
208 locations."= /api/config" = {
209 proxyPass = "http://[::1]:3000";
210 };
211
212 # encrypted blobs are immutable and are thus cached for a year
213 locations."^~ /blob/" = {
214 root = cryptpad_root;
215 extraConfig = ''
216 ${add_headers}
217 if ($request_method = 'OPTIONS') {
218 ${add_headers}
219 add_header 'Access-Control-Allow-Origin' '*';
220 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
221 add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
222 add_header 'Access-Control-Max-Age' 1728000;
223 add_header 'Content-Type' 'application/octet-stream; charset=utf-8';
224 add_header 'Content-Length' 0;
225 return 204;
226 }
227 add_header Cache-Control max-age=31536000;
228 add_header 'Access-Control-Allow-Origin' '*';
229 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
230 add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
231 add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
232 try_files $uri =404;
233 '';
234 };
235
236 # The "block-store" serves encrypted payloads containing users' drive keys
237 # these payloads are unlocked via login credentials.
238 # They are mutable and are thus never cached.
239 # They're small enough that it doesn't matter, in any case.
240 locations."^~ /block/" = {
241 root = cryptpad_root;
242 extraConfig = ''
243 ${add_headers}
244 add_header Cache-Control max-age=0;
245 try_files $uri =404;
246 '';
247 };
248
249 # ugly hack: disable registration
250 /*
251 locations."/register" = {
252 deny all;
253 }
254 */
255
256 # The nodejs server has some built-in forwarding rules to prevent
257 # URLs like /pad from resulting in a 404. This simply adds a trailing slash
258 # to a variety of applications.
259 locations."~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams)$".extraConfig = ''
260 rewrite ^(.*)$ $1/ redirect;
261 '';
262
263 extraConfig = ''
264 access_log /var/log/nginx/${domain}/${srv}/access.log json buffer=32k;
265 error_log /var/log/nginx/${domain}/${srv}/error.log warn;
266
267 index index.html;
268 error_page 404 /customize.dist/404.html;
269
270 # add_header will not set any header if it is emptystring
271 set $cacheControl "";
272 # any static assets loaded with "ver=" in their URL will be cached for a year
273 if ($args ~ ver=) {
274 set $cacheControl max-age=31536000;
275 }
276
277 set $unsafe 0;
278 # the following assets are loaded via the sandbox domain
279 # they unfortunately still require exceptions to the sandboxing to work correctly.
280 if ($uri = "/pad/inner.html") { set $unsafe 1; }
281 if ($uri = "/sheet/inner.html") { set $unsafe 1; }
282 if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; }
283
284 # Everything except the sandbox domain is a privileged scope,
285 # as they might be used to handle keys
286 if ($host != "${sandbox_domain}") { set $unsafe 0; }
287
288 # script-src specifies valid sources for javascript, including inline handlers
289 set $scriptSrc "'self' resource: ${main_domain}";
290
291 # privileged contexts allow a few more rights than unprivileged contexts,
292 # though limits are still applied
293 if ($unsafe) {
294 set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ${main_domain}";
295 }
296
297 ${add_headers}
298
299 # Finally, serve anything the above exceptions don't govern.
300 try_files /www/$uri /www/$uri/index.html /customize/$uri;
301 '';
302 };
303 }