2 { pkgs, lib, config, hostName, ... }:
 
   4   inherit (config) networking;
 
   5   inherit (config.services) nginx;
 
   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}";
 
  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 host at a later date
 
  23   # if you find that a single host 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}";
 
  29   # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain
 
  30   styleSrc = "'unsafe-inline' 'self' ${main_domain}";
 
  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}";
 
  35   # fonts can be loaded from data-URLs or the main domain
 
  36   fontSrc = "'self' data: ${main_domain}";
 
  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}";
 
  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:";
 
  45   # specifies valid sources for loading media using video or audio
 
  46   mediaSrc = "'self' data: * blob: ${main_domain}";
 
  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}";
 
  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}";
 
  56   # add_header clear all parent add_header
 
  57   # and thus must be repeated in sub-blocks
 
  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};";
 
  65   cryptpad_root = "/var/lib/nginx/cryptpad";
 
  70   # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js
 
  71   configFile = pkgs.writeText "config.js" ''
 
  73       httpUnsafeOrigin: 'https://${main_domain}/',
 
  74       httpSafeOrigin: "https://${sandbox_domain}/",
 
  80       /* =====================
 
  82        * ===================== */
 
  84         "https://cryptpad.sourcephile.fr/user/#/1/julm/s9y2aE8C5INMZSrR2yQbWBNcGpB8nSLZGFVhGHE8Nn8=",
 
  86       // supportMailboxPublicKey: "",
 
  88       removeDonateButton: true,
 
  89       disableAnonymousStore: true,
 
  90       disableCrowdfundingMessages: true,
 
  91       adminEmail: 'root+cryptpad@sourcephile.fr',
 
  92       blockDailyCheck: true,
 
  93       defaultStorageLimit: 1024 * 1024 * 1024,
 
  95       /* =====================
 
  97        * ===================== */
 
  98       //inactiveTime: 90, // days
 
  99       //archiveRetentionTime: 15,
 
 100       maxUploadSize: 256 * 1024 * 1024,
 
 104         "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
 
 105           limit: 20 * 1024 * 1024 * 1024,
 
 107           note: 'storage space donated by my.awesome.website'
 
 109         "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": {
 
 110           limit: 10 * 1024 * 1024 * 1024,
 
 112           note: 'storage space donated by my.awesome.website'
 
 117       //premiumUploadSize: 100 * 1024 * 1024,
 
 119       /* =====================
 
 121        * ===================== */
 
 122       filePath: './datastore/',
 
 123       archivePath: './data/archive',
 
 124       pinPath: './data/pins',
 
 125       taskPath: './data/tasks',
 
 126       blockPath: './block',
 
 128       blobStagingPath: './data/blobstage',
 
 129       logPath: './data/logs',
 
 131       /* =====================
 
 133        * ===================== */
 
 142 fileSystems."/var/lib/private/cryptpad" = {
 
 143   device = "${hostName}/var/cryptpad";
 
 147 services.sanoid.datasets = {
 
 148   "${hostName}/var/cryptpad" = {
 
 149     use_template = [ "local" ];
 
 154 services.syncoid.commands = {
 
 155   "${hostName}/var/cryptpad" = {
 
 157     target = "backup@mermet.${networking.domain}:rpool/backup/${hostName}/var/cryptpad";
 
 161 systemd.services.nginx = {
 
 162   #after = [ "cryptpad.service" ];
 
 163   #wants = [ "cryptpad.service" ];
 
 165     BindReadOnlyPaths = [
 
 166       "/var/lib/private/cryptpad:${cryptpad_root}"
 
 168     LogsDirectory = lib.mkForce [
 
 169       "nginx/${domain}/${srv}"
 
 174 services.nginx.virtualHosts.${main_domain} = {
 
 175   serverAliases = [ sandbox_domain ];
 
 177   useACMEHost = domain;
 
 179   # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/docs/example.nginx.conf
 
 180   root = "${pkgs.cryptpad}/lib/node_modules/cryptpad";
 
 182   # The nodejs process can handle all traffic whether accessed over websocket or as static assets
 
 183   # We prefer to serve static content from nginx directly and to leave the API server to handle
 
 184   # the dynamic content that only it can manage. This is primarily an optimization
 
 185   locations."^~ /cryptpad_websocket" = {
 
 186     proxyPass = "http://[::1]:3000";
 
 187     proxyWebsockets = true;
 
 190   locations."^~ /customize.dist/" = {
 
 191     # This is needed in order to prevent infinite recursion between /customize/ and the root
 
 193   # try to load customizeable content via /customize/ and fall back to the default content
 
 194   # located at /customize.dist/
 
 195   # This is what allows you to override behaviour.
 
 196   locations."^~ /customize/".extraConfig = ''
 
 197     rewrite ^/customize/(.*)$ $1 break;
 
 198     try_files /customize/$uri /customize.dist/$uri;
 
 201   # /api/config is loaded once per page load and is used to retrieve
 
 202   # the caching variable which is applied to every other resource
 
 203   # which is loaded during that session.
 
 204   locations."= /api/config" = {
 
 205     proxyPass = "http://[::1]:3000";
 
 208   # encrypted blobs are immutable and are thus cached for a year
 
 209   locations."^~ /blob/" = {
 
 210     root = cryptpad_root;
 
 213       if ($request_method = 'OPTIONS') {
 
 215         add_header 'Access-Control-Allow-Origin' '*';
 
 216         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
 
 217         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';
 
 218         add_header 'Access-Control-Max-Age' 1728000;
 
 219         add_header 'Content-Type' 'application/octet-stream; charset=utf-8';
 
 220         add_header 'Content-Length' 0;
 
 223       add_header Cache-Control max-age=31536000;
 
 224       add_header 'Access-Control-Allow-Origin' '*';
 
 225       add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
 
 226       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';
 
 227       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   # The "block-store" serves encrypted payloads containing users' drive keys
 
 233   # these payloads are unlocked via login credentials.
 
 234   # They are mutable and are thus never cached.
 
 235   # They're small enough that it doesn't matter, in any case.
 
 236   locations."^~ /block/" = {
 
 237     root = cryptpad_root;
 
 240       add_header Cache-Control max-age=0;
 
 245   # ugly hack: disable registration
 
 247   locations."/register" = {
 
 252   # The nodejs server has some built-in forwarding rules to prevent
 
 253   # URLs like /pad from resulting in a 404. This simply adds a trailing slash
 
 254   # to a variety of applications.
 
 255   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 = ''
 
 256     rewrite ^(.*)$ $1/ redirect;
 
 260     access_log /var/log/nginx/${domain}/${srv}/access.log json buffer=32k;
 
 261     error_log  /var/log/nginx/${domain}/${srv}/error.log warn;
 
 264     error_page 404 /customize.dist/404.html;
 
 266     # add_header will not set any header if it is emptystring
 
 267     set $cacheControl "";
 
 268     # any static assets loaded with "ver=" in their URL will be cached for a year
 
 270       set $cacheControl max-age=31536000;
 
 274     # the following assets are loaded via the sandbox domain
 
 275     # they unfortunately still require exceptions to the sandboxing to work correctly.
 
 276     if ($uri = "/pad/inner.html") { set $unsafe 1; }
 
 277     if ($uri = "/sheet/inner.html") { set $unsafe 1; }
 
 278     if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; }
 
 280     # Everything except the sandbox domain is a privileged scope,
 
 281     # as they might be used to handle keys
 
 282     if ($host != "${sandbox_domain}") { set $unsafe 0; }
 
 284     # script-src specifies valid sources for javascript, including inline handlers
 
 285     set $scriptSrc "'self' resource: ${main_domain}";
 
 287     # privileged contexts allow a few more rights than unprivileged contexts,
 
 288     # though limits are still applied
 
 290       set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ${main_domain}";
 
 295     # Finally, serve anything the above exceptions don't govern.
 
 296     try_files /www/$uri /www/$uri/index.html /customize/$uri;