9 cfg = config.services.cryptpad;
19 # The Cryptpad configuration file isn't JSON, but a JavaScript source file that assigns a JSON value
21 cryptpadConfigFile = builtins.toFile "cryptpad_config.js" ''
22 module.exports = ${builtins.toJSON cfg.settings}
25 # Derive domain names for Nginx configuration from Cryptpad configuration
26 mainDomain = strings.removePrefix "https://" cfg.settings.httpUnsafeOrigin;
28 if cfg.settings.httpSafeOrigin == null then
31 strings.removePrefix "https://" cfg.settings.httpSafeOrigin;
35 options.services.cryptpad = {
36 enable = lib.mkEnableOption "cryptpad";
38 package = lib.mkPackageOption pkgs "cryptpad" { };
40 configureNginx = mkOption {
42 Configure Nginx as a reverse proxy for Cryptpad.
43 Note that this makes some assumptions on your setup, and sets settings that will
44 affect other virtualHosts running on your Nginx instance, if any.
45 Alternatively you can configure a reverse-proxy of your choice.
53 Cryptpad configuration settings.
54 See https://github.com/cryptpad/cryptpad/blob/main/config/config.example.js for a more extensive
55 reference documentation.
56 Test your deployed instance through `https://<domain>/checkup/`.
58 type = types.submodule {
59 freeformType = (pkgs.formats.json { }).type;
61 httpUnsafeOrigin = mkOption {
63 example = "https://cryptpad.example.com";
65 description = "This is the URL that users will enter to load your instance";
67 httpSafeOrigin = mkOption {
68 type = types.nullOr types.str;
69 example = "https://cryptpad-ui.example.com. Apparently optional but recommended.";
70 description = "Cryptpad sandbox URL";
72 httpAddress = mkOption {
74 default = "127.0.0.1";
75 description = "Address on which the Node.js server should listen";
80 description = "Port on which the Node.js server should listen";
82 websocketPort = mkOption {
85 description = "Port for the websocket that needs to be separate";
87 maxWorkers = mkOption {
88 type = types.nullOr types.int;
90 description = "Number of child processes, defaults to number of cores available";
92 adminKeys = mkOption {
93 type = types.listOf types.str;
95 description = "List of public signing keys of users that can access the admin panel";
96 example = [ "[cryptpad-user1@my.awesome.website/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]" ];
98 logToStdout = mkOption {
101 description = "Controls whether log output should go to stdout of the systemd service";
103 logLevel = mkOption {
106 description = "Controls log level";
108 blockDailyCheck = mkOption {
112 Disable telemetry. This setting is only effective if the 'Disable server telemetry'
113 setting in the admin menu has been untouched, and will be ignored by cryptpad once
114 that option is set either way.
115 Note that due to the service confinement, just enabling the option in the admin
116 menu will not be able to resolve DNS and fail; this setting must be set as well.
119 installMethod = mkOption {
123 Install method is listed in telemetry if you agree to it through the consentToContact
124 setting in the admin panel.
132 config = mkIf cfg.enable (mkMerge [
134 systemd.services.cryptpad = {
135 description = "Cryptpad service";
136 wantedBy = [ "multi-user.target" ];
137 after = [ "networking.target" ];
139 BindReadOnlyPaths = [
141 # apparently needs proc for workers management
147 "CRYPTPAD_CONFIG=${cryptpadConfigFile}"
150 ExecStart = lib.getExe cfg.package;
152 StateDirectory = "cryptpad";
153 WorkingDirectory = "%S/cryptpad";
154 # security way too many numerous options, from the systemd-analyze security output
155 # at end of test: block everything except
156 # - SystemCallFiters=@resources is required for node
157 # - MemoryDenyWriteExecute for node JIT
158 # - RestrictAddressFamilies=~AF_(INET|INET6) / PrivateNetwork to bind to sockets
159 # - IPAddressDeny likewise allow localhost if binding to localhost or any otherwise
160 # - PrivateUsers somehow service doesn't start with that
161 # - DeviceAllow (char-rtc r added by ProtectClock)
162 AmbientCapabilities = "";
163 CapabilityBoundingSet = "";
165 LockPersonality = true;
166 NoNewPrivileges = true;
167 PrivateDevices = true;
171 ProtectControlGroups = true;
173 ProtectHostname = true;
174 ProtectKernelLogs = true;
175 ProtectKernelModules = true;
176 ProtectKernelTunables = true;
177 ProtectProc = "invisible";
178 ProtectSystem = "strict";
180 RestrictAddressFamilies = [
184 RestrictNamespaces = true;
185 RestrictRealtime = true;
186 RestrictSUIDSGID = true;
187 RuntimeDirectoryMode = "700";
189 "tcp:${builtins.toString cfg.settings.httpPort}"
190 "tcp:${builtins.toString cfg.settings.websocketPort}"
192 SocketBindDeny = [ "any" ];
193 StateDirectoryMode = "0700";
194 SystemCallArchitectures = "native";
211 mode = "chroot-only";
215 # block external network access if not phoning home and
216 # binding to localhost (default)
219 cfg.settings.blockDailyCheck
220 && (builtins.elem cfg.settings.httpAddress [
226 systemd.services.cryptpad = {
228 IPAddressAllow = [ "localhost" ];
229 IPAddressDeny = [ "any" ];
234 # .. conversely allow DNS & TLS if telemetry is explicitly enabled
235 (mkIf (!cfg.settings.blockDailyCheck) {
236 systemd.services.cryptpad = {
238 BindReadOnlyPaths = [
242 "/etc/ssl/certs/ca-certificates.crt"
248 (mkIf cfg.configureNginx {
251 assertion = cfg.settings.httpUnsafeOrigin != "";
252 message = "services.cryptpad.settings.httpUnsafeOrigin is required";
255 assertion = strings.hasPrefix "https://" cfg.settings.httpUnsafeOrigin;
256 message = "services.cryptpad.settings.httpUnsafeOrigin must start with https://";
260 cfg.settings.httpSafeOrigin == null || strings.hasPrefix "https://" cfg.settings.httpSafeOrigin;
261 message = "services.cryptpad.settings.httpSafeOrigin must start with https:// (or be unset)";
266 recommendedTlsSettings = true;
267 recommendedProxySettings = true;
268 recommendedOptimisation = true;
269 recommendedGzipSettings = true;
271 virtualHosts = mkMerge [
274 serverAliases = lib.optionals (cfg.settings.httpSafeOrigin != null) [ sandboxDomain ];
275 enableACME = lib.mkDefault true;
278 proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.httpPort}";
280 client_max_body_size 150m;
283 locations."/cryptpad_websocket" = {
284 proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.websocketPort}";
285 proxyWebsockets = true;