{ domain, ... }:
{ pkgs, lib, config, hostName, ... }:
let
  inherit (config) networking;
  inherit (config.services) nginx;
  srv = "cryptpad";

  # CryptPad serves static assets over these two domains.
  # `main_domain` is what users will enter in their address bar.
  # Privileged computation such as key management is handled in this scope
  # UI content is loaded via the `sandbox_domain`.
  # "Content Security Policy" headers prevent content loaded via the sandbox
  # from accessing privileged information.
  # These variables must be different to take advantage of CryptPad's sandboxing techniques.
  # In the event of an XSS vulnerability in CryptPad's front-end code
  # this will limit the amount of information accessible to attackers.
  main_domain = "${srv}.${domain}";
  sandbox_domain = "${srv}-sandbox.${domain}";

  # CryptPad's dynamic content (websocket traffic and encrypted blobs)
  # can be served over separate domains. Using dedicated domains (or subdomains)
  # for these purposes allows you to move them to a separate host at a later date
  # if you find that a single host cannot handle all of your users.
  # If you don't use dedicated domains, this can be the same as $main_domain
  # If you do, they'll be added as exceptions to any rules which block connections to remote domains.
  api_domain = "${srv}-api.${domain}";
  files_domain = "${srv}-files.${domain}";

  # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain
  styleSrc = "'unsafe-inline' 'self' ${main_domain}";

  # connect-src restricts URLs which can be loaded using script interfaces
  connectSrc = "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}";

  # fonts can be loaded from data-URLs or the main domain
  fontSrc = "'self' data: ${main_domain}";

  # images can be loaded from anywhere, though we'd like to deprecate this as it allows the use of images for tracking
  imgSrc = "'self' data: * blob: ${main_domain}";

  # frame-src specifies valid sources for nested browsing contexts.
  # this prevents loading any iframes from anywhere other than the sandbox domain
  frameSrc = "'self' ${sandbox_domain} blob:";

  # specifies valid sources for loading media using video or audio
  mediaSrc = "'self' data: * blob: ${main_domain}";

  # defines valid sources for webworkers and nested browser contexts
  # deprecated in favour of worker-src and frame-src
  childSrc = "https://${main_domain}";

  # specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts.
  # supercedes child-src but is unfortunately not yet universally supported.
  workerSrc = "https://${main_domain}";

  # add_header clear all parent add_header
  # and thus must be repeated in sub-blocks
  add_headers = ''
    ${nginx.configs.https_add_headers}
    add_header Access-Control-Allow-Origin "*";
    add_header Cache-Control $cacheControl;
    add_header X-Frame-Options "";
    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};";
  '';
  cryptpad_root = "/var/lib/nginx/cryptpad";
in
{
  services.cryptpad = {
    enable = true;
    # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js
    configFile = pkgs.writeText "config.js" ''
      module.exports = {
        httpUnsafeOrigin: 'https://${main_domain}/',
        httpSafeOrigin: "https://${sandbox_domain}/",
        httpAddress: '::1',
        httpPort: 3100,
        httpSafePort: 3101,
        maxWorkers: 1,

        /* =====================
         *         Admin
         * ===================== */
        adminKeys: [
          "https://cryptpad.sourcephile.fr/user/#/1/julm/s9y2aE8C5INMZSrR2yQbWBNcGpB8nSLZGFVhGHE8Nn8=",
        ],
        // supportMailboxPublicKey: "",

        removeDonateButton: true,
        disableAnonymousStore: true,
        disableCrowdfundingMessages: true,
        adminEmail: 'root+cryptpad@sourcephile.fr',
        blockDailyCheck: true,
        defaultStorageLimit: 1024 * 1024 * 1024,

        /* =====================
         *        STORAGE
         * ===================== */
        //inactiveTime: 90, // days
        //archiveRetentionTime: 15,
        maxUploadSize: 256 * 1024 * 1024,

        /*
        customLimits: {
          "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
            limit: 20 * 1024 * 1024 * 1024,
            plan: 'insider',
            note: 'storage space donated by my.awesome.website'
          },
          "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": {
            limit: 10 * 1024 * 1024 * 1024,
            plan: 'insider',
            note: 'storage space donated by my.awesome.website'
          }
        },
        */

        //premiumUploadSize: 100 * 1024 * 1024,

        /* =====================
         *   DATABASE VOLUMES
         * ===================== */
        filePath: './datastore/',
        archivePath: './data/archive',
        pinPath: './data/pins',
        taskPath: './data/tasks',
        blockPath: './block',
        blobPath: './blob',
        blobStagingPath: './data/blobstage',
        logPath: './data/logs',

        /* =====================
         *       Debugging
         * ===================== */
        logToStdout: false,
        logLevel: 'info',
        logFeedback: false,
        verbose: false,
      };
    '';
  };

  fileSystems."/var/lib/private/cryptpad" = {
    device = "${hostName}/var/cryptpad";
    fsType = "zfs";
  };

  services.sanoid.datasets = {
    "${hostName}/var/cryptpad" = {
      use_template = [ "snap" ];
      daily = 31;
    };
  };

  services.syncoid.commands = {
    "${hostName}/var/cryptpad" = {
      sendOptions = "raw";
      target = "backup@mermet.${networking.domain}:rpool/backup/${hostName}/var/cryptpad";
    };
  };

  systemd.services.nginx = {
    #after = [ "cryptpad.service" ];
    #wants = [ "cryptpad.service" ];
    serviceConfig = {
      BindReadOnlyPaths = [
        "/var/lib/private/cryptpad:${cryptpad_root}"
      ];
      LogsDirectory = lib.mkForce [
        "nginx/${domain}/${srv}"
      ];
    };
  };

  services.nginx.virtualHosts.${main_domain} = {
    serverAliases = [ sandbox_domain ];
    forceSSL = true;
    useACMEHost = domain;

    # DOC: https://github.com/xwiki-labs/cryptpad/blob/master/docs/example.nginx.conf
    root = "${pkgs.cryptpad}/lib/node_modules/cryptpad";

    # The nodejs process can handle all traffic whether accessed over websocket or as static assets
    # We prefer to serve static content from nginx directly and to leave the API server to handle
    # the dynamic content that only it can manage. This is primarily an optimization
    locations."^~ /cryptpad_websocket" = {
      proxyPass = "http://[::1]:3100";
      proxyWebsockets = true;
    };

    locations."^~ /customize.dist/" = {
      # This is needed in order to prevent infinite recursion between /customize/ and the root
    };
    # try to load customizeable content via /customize/ and fall back to the default content
    # located at /customize.dist/
    # This is what allows you to override behaviour.
    locations."^~ /customize/".extraConfig = ''
      rewrite ^/customize/(.*)$ $1 break;
      try_files /customize/$uri /customize.dist/$uri;
    '';

    # /api/config is loaded once per page load and is used to retrieve
    # the caching variable which is applied to every other resource
    # which is loaded during that session.
    locations."= /api/config" = {
      proxyPass = "http://[::1]:3100";
    };

    # encrypted blobs are immutable and are thus cached for a year
    locations."^~ /blob/" = {
      root = cryptpad_root;
      extraConfig = ''
        ${add_headers}
        if ($request_method = 'OPTIONS') {
          ${add_headers}
          add_header 'Access-Control-Allow-Origin' '*';
          add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
          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';
          add_header 'Access-Control-Max-Age' 1728000;
          add_header 'Content-Type' 'application/octet-stream; charset=utf-8';
          add_header 'Content-Length' 0;
          return 204;
        }
        add_header Cache-Control max-age=31536000;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        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';
        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';
        try_files $uri =404;
      '';
    };

    # The "block-store" serves encrypted payloads containing users' drive keys
    # these payloads are unlocked via login credentials.
    # They are mutable and are thus never cached.
    # They're small enough that it doesn't matter, in any case.
    locations."^~ /block/" = {
      root = cryptpad_root;
      extraConfig = ''
        ${add_headers}
        add_header Cache-Control max-age=0;
        try_files $uri =404;
      '';
    };

    # ugly hack: disable registration
    /*
      locations."/register" = {
      deny all;
      }
    */

    # The nodejs server has some built-in forwarding rules to prevent
    # URLs like /pad from resulting in a 404. This simply adds a trailing slash
    # to a variety of applications.
    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 = ''
      rewrite ^(.*)$ $1/ redirect;
    '';

    extraConfig = ''
      access_log /var/log/nginx/${domain}/${srv}/access.json json buffer=32k;
      error_log  /var/log/nginx/${domain}/${srv}/error.log warn;

      index index.html;
      error_page 404 /customize.dist/404.html;

      # add_header will not set any header if it is emptystring
      set $cacheControl "";
      # any static assets loaded with "ver=" in their URL will be cached for a year
      if ($args ~ ver=) {
        set $cacheControl max-age=31536000;
      }

      set $unsafe 0;
      # the following assets are loaded via the sandbox domain
      # they unfortunately still require exceptions to the sandboxing to work correctly.
      if ($uri = "/pad/inner.html") { set $unsafe 1; }
      if ($uri = "/sheet/inner.html") { set $unsafe 1; }
      if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; }

      # Everything except the sandbox domain is a privileged scope,
      # as they might be used to handle keys
      if ($host != "${sandbox_domain}") { set $unsafe 0; }

      # script-src specifies valid sources for javascript, including inline handlers
      set $scriptSrc "'self' resource: ${main_domain}";

      # privileged contexts allow a few more rights than unprivileged contexts,
      # though limits are still applied
      if ($unsafe) {
        set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ${main_domain}";
      }

      ${add_headers}

      # Finally, serve anything the above exceptions don't govern.
      try_files /www/$uri /www/$uri/index.html /customize/$uri;
    '';
  };
}