{ pkgs, config, lib, ... }: let cfg = config.myServices.tools.cryptpad.farm; toService = name: let inherit (cfg.hosts.${name}) package config; in { description = "Cryptpad ${name} Service"; wantedBy = [ "multi-user.target" ]; after = [ "networking.target" ]; serviceConfig = { User = "cryptpad"; Group = "cryptpad"; Environment = [ "CRYPTPAD_CONFIG=${config}" "HOME=%S/cryptpad/${name}" ]; ExecStart = "${package}/bin/cryptpad"; PrivateTmp = true; Restart = "always"; StateDirectory = "cryptpad/${name}"; WorkingDirectory = "%S/cryptpad/${name}"; }; }; toVhostRoot = name: "${cfg.hosts.${name}.package}/lib/node_modules/cryptpad"; toVhost = name: let inherit (cfg.hosts.${name}) package domain port; api_domain = domain; files_domain = domain; in '' RewriteEngine On Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" Header set X-XSS-Protection "1; mode=block" Header set X-Content-Type-Options "nosniff" Header set Access-Control-Allow-Origin "*" Header set Permissions-Policy "interest-cohort=()" Header set Cross-Origin-Resource-Policy "cross-origin" Header set Cross-Origin-Opener-Policy "same-origin" Header set Cross-Origin-Embedder-Policy "require-corp" ErrorDocument 404 /customize.dist/404.html Header set Cache-Control "max-age=31536000" Header set Cache-Control "no-cache" SetEnv styleSrc "'unsafe-inline' 'self' ${domain}" SetEnv connectSrc "'self' https://${domain} ${domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}" SetEnv fontSrc "'self' data: ${domain}" SetEnv imgSrc "'self' data: * blob: ${domain}" SetEnv frameSrc "'self' blob:" SetEnv mediaSrc "'self' data: * blob: ${domain}" SetEnv childSrc "https://${domain}" SetEnv workerSrc "https://${domain}" SetEnv scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ${domain}" Header set Content-Security-Policy "default-src 'none'; child-src %{childSrc}e; worker-src %{workerSrc}e; media-src %{mediaSrc}e; style-src %{styleSrc}e; script-src %{scriptSrc}e; connect-src %{connectSrc}e; font-src %{fontSrc}e; img-src %{imgSrc}e; frame-src %{frameSrc}e;" RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC] RewriteCond %{HTTP:CONNECTION} Upgrade$ [NC] RewriteRule .* ws://localhost:${toString port}%{REQUEST_URI} [P,NE,QSA,L] RewriteRule ^/customize/(.*)$ /customize.dist/$1 [L] ProxyPassMatch "^/(api/(config|broadcast).*)$" "http://localhost:${toString port}/$1" ProxyPassReverse /api http://localhost:${toString port}/api ProxyPreserveHost On RequestHeader set X-Real-IP %{REMOTE_ADDR}s Alias /blob /var/lib/cryptpad/${name}/blob Require all granted AllowOverride None Alias /block /var/lib/cryptpad/${name}/block Require all granted AllowOverride None Header set Cache-Control "max-age=31536000" Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods "GET, POST, OPTIONS" Header set Access-Control-Allow-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length" Header set Access-Control-Expose-Headers "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length" RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=204,L] Header set Cache-Control "max-age=0" RewriteRule ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams|calendar|presentation|doc)$ $1/ [R=302,L] RewriteCond %{DOCUMENT_ROOT}/www/%{REQUEST_URI} -f RewriteRule (.*) /www/$1 [L] RewriteCond %{DOCUMENT_ROOT}/www/%{REQUEST_URI}/index.html -f RewriteRule (.*) /www/$1/index.html [L] RewriteCond %{DOCUMENT_ROOT}/customize.dist/%{REQUEST_URI} -f RewriteRule (.*) /customize.dist/$1 [L] AllowOverride None Require all granted DirectoryIndex index.html AllowOverride None Require all granted DirectoryIndex index.html ''; in { options.myServices.tools.cryptpad.farm = { hosts = lib.mkOption { default = {}; description = "Hosts to install"; type = lib.types.attrsOf (lib.types.submodule { options = { port = lib.mkOption { type = lib.types.port; }; package = lib.mkOption { type = lib.types.package; description = "Cryptpad package to use"; default = pkgs.cryptpad; }; domain = lib.mkOption { type = lib.types.str; description = "Domain for main host"; }; config = lib.mkOption { type = lib.types.path; description = "Path to configuration"; }; }; }); }; vhosts = lib.mkOption { description = "Instance vhosts configs"; readOnly = true; type = lib.types.attrsOf lib.types.str; default = lib.genAttrs (builtins.attrNames cfg.hosts) toVhost; }; vhostRoots = lib.mkOption { description = "Instance vhosts document roots"; readOnly = true; type = lib.types.attrsOf lib.types.path; default = lib.genAttrs (builtins.attrNames cfg.hosts) toVhostRoot; }; }; config = { users.users = lib.optionalAttrs (cfg.hosts != {}) { cryptpad = { uid = config.ids.uids.cryptpad; group = "cryptpad"; description = "Cryptpad user"; }; }; users.groups = lib.optionalAttrs (cfg.hosts != {}) { cryptpad = { gid = config.ids.gids.cryptpad; }; }; systemd.services = lib.listToAttrs (map (n: lib.nameValuePair "cryptpad-${n}" (toService n)) (builtins.attrNames cfg.hosts)); }; }