mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-13 13:40:28 +03:00
nixos/cryptpad: init
This is a full rewrite independent of the previously removed cryptpad module, managing cryptpad's config in RFC0042 along with a shiny test. Upstream cryptpad provides two nginx configs, with many optimizations and complex settings; this uses the easier variant for now but improvements (e.g. serving blocks and js files directly through nginx) should be possible with a bit of work and care about http headers. the /checkup page of cryptpad passes all tests except HSTS, we don't seem to have any nginx config with HSTS enabled in nixpkgs so leave this as is for now. Co-authored-by: Pol Dellaiera <pol.dellaiera@protonmail.com> Co-authored-by: Michael Smith <shmitty@protonmail.com>
This commit is contained in:
parent
5f020a4166
commit
b846e8762f
6 changed files with 287 additions and 1 deletions
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
- [Localsend](https://localsend.org/), an open source cross-platform alternative to AirDrop. Available as [programs.localsend](#opt-programs.localsend.enable).
|
- [Localsend](https://localsend.org/), an open source cross-platform alternative to AirDrop. Available as [programs.localsend](#opt-programs.localsend.enable).
|
||||||
|
|
||||||
|
- [cryptpad](https://cryptpad.org/), a privacy-oriented collaborative platform (docs/drive/etc), has been added back. Available as [services.cryptpad](#opt-services.cryptpad.enable).
|
||||||
|
|
||||||
- [realm](https://github.com/zhboner/realm), a simple, high performance relay server written in rust. Available as [services.realm.enable](#opt-services.realm.enable).
|
- [realm](https://github.com/zhboner/realm), a simple, high performance relay server written in rust. Available as [services.realm.enable](#opt-services.realm.enable).
|
||||||
|
|
||||||
- [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld).
|
- [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld).
|
||||||
|
|
|
@ -1371,6 +1371,7 @@
|
||||||
./services/web-apps/convos.nix
|
./services/web-apps/convos.nix
|
||||||
./services/web-apps/crabfit.nix
|
./services/web-apps/crabfit.nix
|
||||||
./services/web-apps/davis.nix
|
./services/web-apps/davis.nix
|
||||||
|
./services/web-apps/cryptpad.nix
|
||||||
./services/web-apps/dex.nix
|
./services/web-apps/dex.nix
|
||||||
./services/web-apps/discourse.nix
|
./services/web-apps/discourse.nix
|
||||||
./services/web-apps/documize.nix
|
./services/web-apps/documize.nix
|
||||||
|
|
|
@ -116,7 +116,6 @@ in
|
||||||
(mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.")
|
(mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.")
|
||||||
(mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.")
|
(mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.")
|
||||||
(mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
|
(mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
|
||||||
(mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
|
|
||||||
(mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
|
(mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
|
||||||
(mkRemovedOptionModule [ "services" "prayer" ] "The corresponding package was removed from nixpkgs.")
|
(mkRemovedOptionModule [ "services" "prayer" ] "The corresponding package was removed from nixpkgs.")
|
||||||
(mkRemovedOptionModule [ "services" "restya-board" ] "The corresponding package was removed from nixpkgs.")
|
(mkRemovedOptionModule [ "services" "restya-board" ] "The corresponding package was removed from nixpkgs.")
|
||||||
|
|
215
nixos/modules/services/web-apps/cryptpad.nix
Normal file
215
nixos/modules/services/web-apps/cryptpad.nix
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.cryptpad;
|
||||||
|
|
||||||
|
inherit (lib)
|
||||||
|
mkIf
|
||||||
|
mkMerge
|
||||||
|
mkOption
|
||||||
|
strings
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
# The Cryptpad configuration file isn't JSON, but a JavaScript source file that assigns a JSON value
|
||||||
|
# to a variable.
|
||||||
|
cryptpadConfigFile = builtins.toFile "cryptpad_config.js" ''
|
||||||
|
module.exports = ${builtins.toJSON cfg.settings}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Derive domain names for Nginx configuration from Cryptpad configuration
|
||||||
|
mainDomain = strings.removePrefix "https://" cfg.settings.httpUnsafeOrigin;
|
||||||
|
sandboxDomain =
|
||||||
|
if cfg.settings.httpSafeOrigin == null then
|
||||||
|
mainDomain
|
||||||
|
else
|
||||||
|
strings.removePrefix "https://" cfg.settings.httpSafeOrigin;
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.cryptpad = {
|
||||||
|
enable = lib.mkEnableOption "cryptpad";
|
||||||
|
|
||||||
|
package = lib.mkPackageOption pkgs "cryptpad" { };
|
||||||
|
|
||||||
|
configureNginx = mkOption {
|
||||||
|
description = ''
|
||||||
|
Configure Nginx as a reverse proxy for Cryptpad.
|
||||||
|
Note that this makes some assumptions on your setup, and sets settings that will
|
||||||
|
affect other virtualHosts running on your Nginx instance, if any.
|
||||||
|
Alternatively you can configure a reverse-proxy of your choice.
|
||||||
|
'';
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
description = ''
|
||||||
|
Cryptpad configuration settings.
|
||||||
|
See https://github.com/cryptpad/cryptpad/blob/main/config/config.example.js for a more extensive
|
||||||
|
reference documentation.
|
||||||
|
Test your deployed instance through `https://<domain>/checkup/`.
|
||||||
|
'';
|
||||||
|
type = types.submodule {
|
||||||
|
freeformType = (pkgs.formats.json { }).type;
|
||||||
|
options = {
|
||||||
|
httpUnsafeOrigin = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "https://cryptpad.example.com";
|
||||||
|
default = "";
|
||||||
|
description = "This is the URL that users will enter to load your instance";
|
||||||
|
};
|
||||||
|
httpSafeOrigin = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
example = "https://cryptpad-ui.example.com. Apparently optional but recommended.";
|
||||||
|
description = "Cryptpad sandbox URL";
|
||||||
|
};
|
||||||
|
httpAddress = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Address on which the Node.js server should listen";
|
||||||
|
};
|
||||||
|
httpPort = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 3000;
|
||||||
|
description = "Port on which the Node.js server should listen";
|
||||||
|
};
|
||||||
|
websocketPort = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 3003;
|
||||||
|
description = "Port for the websocket that needs to be separate";
|
||||||
|
};
|
||||||
|
maxWorkers = mkOption {
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
description = "Number of child processes, defaults to number of cores available";
|
||||||
|
};
|
||||||
|
adminKeys = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "List of public signing keys of users that can access the admin panel";
|
||||||
|
example = [ "[cryptpad-user1@my.awesome.website/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]" ];
|
||||||
|
};
|
||||||
|
logToStdout = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Controls whether log output should go to stdout of the systemd service";
|
||||||
|
};
|
||||||
|
logLevel = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "info";
|
||||||
|
description = "Controls log level";
|
||||||
|
};
|
||||||
|
blockDailyCheck = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Disable telemetry. This setting is only effective if the 'Disable server telemetry'
|
||||||
|
setting in the admin menu has been untouched, and will be ignored by cryptpad once
|
||||||
|
that option is set either way.
|
||||||
|
Note that due to the service confinement, just enabling the option in the admin
|
||||||
|
menu will not be able to resolve DNS and fail; this setting must be set as well.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
installMethod = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "nixos";
|
||||||
|
description = ''
|
||||||
|
Install method is listed in telemetry if you agree to it through the consentToContact
|
||||||
|
setting in the admin panel.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
{
|
||||||
|
systemd.services.cryptpad = {
|
||||||
|
description = "Cryptpad service";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "networking.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
BindReadOnlyPaths = [
|
||||||
|
cryptpadConfigFile
|
||||||
|
# apparently needs proc for workers management
|
||||||
|
"/proc"
|
||||||
|
"/dev/urandom"
|
||||||
|
] ++ (if ! cfg.settings.blockDailyCheck then [
|
||||||
|
# allow DNS & TLS if telemetry is explicitly enabled
|
||||||
|
"-/etc/resolv.conf"
|
||||||
|
"-/run/systemd"
|
||||||
|
"/etc/hosts"
|
||||||
|
"/etc/ssl/certs/ca-certificates.crt"
|
||||||
|
] else []);
|
||||||
|
DynamicUser = true;
|
||||||
|
Environment = [
|
||||||
|
"CRYPTPAD_CONFIG=${cryptpadConfigFile}"
|
||||||
|
"HOME=%S/cryptpad"
|
||||||
|
];
|
||||||
|
ExecStart = lib.getExe cfg.package;
|
||||||
|
PrivateTmp = true;
|
||||||
|
Restart = "always";
|
||||||
|
StateDirectory = "cryptpad";
|
||||||
|
WorkingDirectory = "%S/cryptpad";
|
||||||
|
};
|
||||||
|
confinement = {
|
||||||
|
enable = true;
|
||||||
|
binSh = null;
|
||||||
|
mode = "chroot-only";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(mkIf cfg.configureNginx {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.settings.httpUnsafeOrigin != "";
|
||||||
|
message = "services.cryptpad.settings.httpUnsafeOrigin is required";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = strings.hasPrefix "https://" cfg.settings.httpUnsafeOrigin;
|
||||||
|
message = "services.cryptpad.settings.httpUnsafeOrigin must start with https://";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
cfg.settings.httpSafeOrigin == null || strings.hasPrefix "https://" cfg.settings.httpSafeOrigin;
|
||||||
|
message = "services.cryptpad.settings.httpSafeOrigin must start with https:// (or be unset)";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
recommendedTlsSettings = true;
|
||||||
|
recommendedProxySettings = true;
|
||||||
|
recommendedOptimisation = true;
|
||||||
|
recommendedGzipSettings = true;
|
||||||
|
|
||||||
|
virtualHosts = mkMerge [
|
||||||
|
{
|
||||||
|
"${mainDomain}" = {
|
||||||
|
serverAliases = lib.optionals (cfg.settings.httpSafeOrigin != null) [ sandboxDomain ];
|
||||||
|
enableACME = lib.mkDefault true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.httpPort}";
|
||||||
|
extraConfig = ''
|
||||||
|
client_max_body_size 150m;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
locations."/cryptpad_websocket" = {
|
||||||
|
proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.websocketPort}";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
|
@ -235,6 +235,7 @@ in {
|
||||||
couchdb = handleTest ./couchdb.nix {};
|
couchdb = handleTest ./couchdb.nix {};
|
||||||
crabfit = handleTest ./crabfit.nix {};
|
crabfit = handleTest ./crabfit.nix {};
|
||||||
cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {};
|
cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {};
|
||||||
|
cryptpad = runTest ./cryptpad.nix;
|
||||||
cups-pdf = handleTest ./cups-pdf.nix {};
|
cups-pdf = handleTest ./cups-pdf.nix {};
|
||||||
curl-impersonate = handleTest ./curl-impersonate.nix {};
|
curl-impersonate = handleTest ./curl-impersonate.nix {};
|
||||||
custom-ca = handleTest ./custom-ca.nix {};
|
custom-ca = handleTest ./custom-ca.nix {};
|
||||||
|
|
68
nixos/tests/cryptpad.nix
Normal file
68
nixos/tests/cryptpad.nix
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
certs = pkgs.runCommand "cryptpadSelfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
|
||||||
|
mkdir -p $out
|
||||||
|
cd $out
|
||||||
|
openssl req -x509 -newkey rsa:4096 \
|
||||||
|
-keyout key.pem -out cert.pem -nodes -days 3650 \
|
||||||
|
-subj '/CN=cryptpad.localhost' \
|
||||||
|
-addext 'subjectAltName = DNS.1:cryptpad.localhost, DNS.2:cryptpad-sandbox.localhost'
|
||||||
|
'';
|
||||||
|
# data sniffed from cryptpad's /checkup network trace, seems to be re-usable
|
||||||
|
test_write_data = pkgs.writeText "cryptpadTestData" ''
|
||||||
|
{"command":"WRITE_BLOCK","content":{"publicKey":"O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik=","signature":"aXcM9SMO59lwA7q7HbYB+AnzymmxSyy/KhkG/cXIBVzl8v+kkPWXmFuWhcuKfRF8yt3Zc3ktIsHoFyuyDSAwAA==","ciphertext":"AFwCIfBHKdFzDKjMg4cu66qlJLpP+6Yxogbl3o9neiQou5P8h8yJB8qgnQ=="},"publicKey":"O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik=","nonce":"bitSbJMNSzOsg98nEzN80a231PCkBQeH"}
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "cryptpad";
|
||||||
|
meta = with pkgs.lib.maintainers; {
|
||||||
|
maintainers = [ martinetd ];
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.machine = {
|
||||||
|
services.cryptpad = {
|
||||||
|
enable = true;
|
||||||
|
configureNginx = true;
|
||||||
|
settings = {
|
||||||
|
httpUnsafeOrigin = "https://cryptpad.localhost";
|
||||||
|
httpSafeOrigin = "https://cryptpad-sandbox.localhost";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.nginx = {
|
||||||
|
virtualHosts."cryptpad.localhost" = {
|
||||||
|
enableACME = false;
|
||||||
|
sslCertificate = "${certs}/cert.pem";
|
||||||
|
sslCertificateKey = "${certs}/key.pem";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
security = {
|
||||||
|
pki.certificateFiles = [ "${certs}/cert.pem" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
machine.wait_for_unit("cryptpad.service")
|
||||||
|
machine.wait_for_unit("nginx.service")
|
||||||
|
machine.wait_for_open_port(3000)
|
||||||
|
|
||||||
|
# test home page
|
||||||
|
machine.succeed("curl --fail https://cryptpad.localhost -o /tmp/cryptpad_home.html")
|
||||||
|
machine.succeed("grep -F 'CryptPad: Collaboration suite' /tmp/cryptpad_home.html")
|
||||||
|
|
||||||
|
# test scripts/build.js actually generated customize content from config
|
||||||
|
machine.succeed("grep -F 'meta property=\"og:url\" content=\"https://cryptpad.localhost/index.html' /tmp/cryptpad_home.html")
|
||||||
|
|
||||||
|
# make sure child pages are accessible (e.g. check nginx try_files paths)
|
||||||
|
machine.succeed(
|
||||||
|
"grep -oE '/(customize|components)[^\"]*' /tmp/cryptpad_home.html"
|
||||||
|
" | while read -r page; do"
|
||||||
|
" curl -O --fail https://cryptpad.localhost$page || exit;"
|
||||||
|
" done")
|
||||||
|
|
||||||
|
# test some API (e.g. check cryptpad main process)
|
||||||
|
machine.succeed("curl --fail -d @${test_write_data} -H 'Content-Type: application/json' https://cryptpad.localhost/api/auth")
|
||||||
|
|
||||||
|
# test telemetry has been disabled
|
||||||
|
machine.fail("journalctl -u cryptpad | grep TELEMETRY");
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue