diff --git a/nixos/modules/services/web-apps/movim.nix b/nixos/modules/services/web-apps/movim.nix index c651c476d168..78c3001753d5 100644 --- a/nixos/modules/services/web-apps/movim.nix +++ b/nixos/modules/services/web-apps/movim.nix @@ -175,6 +175,37 @@ let "mysql" = "mysql.service"; } .${cfg.database.type}; + + # exclusivity asserted in `assertions` + webServerService = + if cfg.h2o != null then + "h2o.service" + else if cfg.nginx != null then + "nginx.service" + else + null; + + socketOwner = + if cfg.h2o != null then + config.services.h2o.user + else if cfg.nginx != null then + config.services.nginx.user + else + cfg.user; + + # Movim needs a lot of unsafe values to function at this time. Perhaps if + # this is ever addressed in the future, the PHP application will send up the + # proper directive. For now this fairly conservative CSP will restrict a lot + # of potentially bad stuff as well as take in inventory of the features used. + # + # See: https://github.com/movim/movim/issues/314 + movimCSP = lib.concatStringsSep "; " [ + "default-src 'self'" + "img-src 'self' aesgcm: data: https:" + "media-src 'self' aesgcm: https:" + "script-src 'self' 'unsafe-eval' 'unsafe-inline'" + "style-src 'self' 'unsafe-inline'" + ]; in { options.services = { @@ -209,19 +240,19 @@ in }; dataDir = mkOption { - type = types.nonEmptyStr; + type = types.path; default = "/var/lib/movim"; description = "State directory of the `movim` user which holds the application’s state & data."; }; logDir = mkOption { - type = types.nonEmptyStr; + type = types.path; default = "/var/log/movim"; description = "Log directory of the `movim` user which holds the application’s logs."; }; runtimeDir = mkOption { - type = types.nonEmptyStr; + type = types.path; default = "/run/movim"; description = "Runtime directory of the `movim` user which holds the application’s caches & temporary files."; }; @@ -319,30 +350,28 @@ in }; precompressStaticFiles = mkOption { - type = - with types; - submodule { - options = { - brotli = { - enable = mkEnableOption "Brotli precompression"; - package = mkPackageOption pkgs "brotli" { }; - compressionLevel = mkOption { - type = types.ints.between 0 11; - default = 11; - description = "Brotli compression level"; - }; + type = types.submodule { + options = { + brotli = { + enable = mkEnableOption "Brotli precompression"; + package = mkPackageOption pkgs "brotli" { }; + compressionLevel = mkOption { + type = types.ints.between 0 11; + default = 11; + description = "Brotli compression level"; }; - gzip = { - enable = mkEnableOption "Gzip precompression"; - package = mkPackageOption pkgs "gzip" { }; - compressionLevel = mkOption { - type = types.ints.between 1 9; - default = 9; - description = "Gzip compression level"; - }; + }; + gzip = { + enable = mkEnableOption "Gzip precompression"; + package = mkPackageOption pkgs "gzip" { }; + compressionLevel = mkOption { + type = types.ints.between 1 9; + default = 9; + description = "Gzip compression level"; }; }; }; + }; default = { brotli.enable = true; gzip.enable = false; @@ -354,67 +383,67 @@ in type = types.submodule { options = { info = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "Content of the info box on the login page"; }; description = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "General description of the instance"; }; timezone = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "The server timezone"; }; restrictsuggestions = mkOption { - type = with types; nullOr bool; + type = types.nullOr types.bool; default = null; description = "Only suggest chatrooms, Communities and other contents that are available on the user XMPP server and related services"; }; chatonly = mkOption { - type = with types; nullOr bool; + type = types.nullOr types.bool; default = null; description = "Disable all the social feature (Communities, Blog…) and keep only the chat ones"; }; disableregistration = mkOption { - type = with types; nullOr bool; + type = types.nullOr types.bool; default = null; description = "Remove the XMPP registration flow and buttons from the interface"; }; loglevel = mkOption { - type = with types; nullOr (ints.between 0 3); + type = types.nullOr (types.ints.between 0 3); default = null; description = "The server loglevel"; }; locale = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "The server main locale"; }; xmppdomain = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "The default XMPP server domain"; }; xmppdescription = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "The default XMPP server description"; }; xmppwhitelist = mkOption { - type = with types; nullOr str; + type = types.nullOr types.nonEmptyStr; default = null; description = "The allowlisted XMPP servers"; }; @@ -442,7 +471,7 @@ in }; secretFile = mkOption { - type = with types; nullOr path; + type = types.nullOr types.path; default = null; description = "The secret file to be sourced for the .env settings."; }; @@ -459,13 +488,13 @@ in }; name = mkOption { - type = types.str; + type = types.nonEmptyStr; default = "movim"; description = "Database name."; }; user = mkOption { - type = types.str; + type = types.nonEmptyStr; default = "movim"; description = "Database username."; }; @@ -477,33 +506,53 @@ in }; }; - nginx = mkOption { - type = - with types; - nullOr ( - submodule ( - import ../web-servers/nginx/vhost-options.nix { - inherit config lib; - } - ) - ); + h2o = mkOption { + type = types.nullOr ( + types.submodule (import ../web-servers/h2o/vhost-options.nix { inherit config lib; }) + ); default = null; example = - lib.literalExpression # nginx + lib.literalExpression # nix '' { serverAliases = [ - "pics.''${config.networking.domain}" + "pics.''${config.movim.domain}" + ]; + acme.enable = true; + tls.policy = "force"; + } + ''; + description = '' + With this option, you can customize an H2O virtual host which already + has sensible defaults for Movim. Set to `{ }` if you do not need any + customization to the virtual host. If enabled, then by default, the + {option}`serverName` is `''${domain}`, If this is set to `null` (the + default), no H2O `hosts` will be configured. + ''; + }; + + nginx = mkOption { + type = types.nullOr ( + types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) + ); + default = null; + example = + lib.literalExpression # nix + '' + { + serverAliases = [ + "pics.''${config.movim.domain}" ]; enableACME = true; forceHttps = true; } ''; description = '' - With this option, you can customize an nginx virtual host which already has sensible defaults for Movim. - Set to `{ }` if you do not need any customization to the virtual host. - If enabled, then by default, the {option}`serverName` is `''${domain}`, - If this is set to null (the default), no nginx virtualHost will be configured. + With this option, you can customize an Nginx virtual host which + already has sensible defaults for Movim. Set to `{ }` if you do not + need any customization to the virtual host. If enabled, then by + default, the {option}`serverName` is `''${domain}`, If this is set to + `null` (the default), no Nginx `virtualHost` will be configured. ''; }; @@ -522,6 +571,25 @@ in }; config = mkIf cfg.enable { + assertions = [ + ( + let + webServers = [ + "h2o" + "nginx" + ]; + checkConfigs = lib.concatMapStringsSep ", " (ws: "services.movim.${ws}") webServers; + in + { + assertion = builtins.length (lib.lists.filter (ws: cfg.${ws} != null) webServers) <= 1; + message = '' + At most 1 web server virtual host configuration should be enabled + for Movim at a time. Check ${checkConfigs}. + ''; + } + ) + ]; + environment.systemPackages = [ package ]; users = { @@ -532,6 +600,9 @@ in group = cfg.group; }; } + // lib.optionalAttrs (cfg.h2o != null) { + "${config.services.h2o.user}".extraGroups = [ cfg.group ]; + } // lib.optionalAttrs (cfg.nginx != null) { "${config.services.nginx.user}".extraGroups = [ cfg.group ]; }; @@ -578,6 +649,51 @@ in }; }; + h2o = mkIf (cfg.h2o != null) { + enable = true; + hosts."${cfg.domain}" = mkMerge [ + { + settings = { + paths = { + "/ws/" = { + "proxy.preserve-host" = "ON"; + "proxy.tunnel" = "ON"; + "proxy.reverse.url" = "http://${cfg.settings.DAEMON_INTERFACE}:${builtins.toString cfg.port}/"; + }; + "/" = + { + "file.dir" = "${package}/share/php/movim/public"; + "file.index" = [ + "index.php" + "index.html" + ]; + redirect = { + url = "/index.php/"; + internal = "YES"; + status = 307; + }; + "header.set" = [ + "Content-Security-Policy: ${movimCSP}" + ]; + } + // lib.optionalAttrs (with cfg.precompressStaticFiles; brotli.enable || gzip.enable) { + "file.send-compressed" = "ON"; + }; + }; + "file.custom-handler" = { + extension = [ ".php" ]; + "fastcgi.document_root" = package; + "fastcgi.connect" = { + port = fpm.socket; + type = "unix"; + }; + }; + }; + } + cfg.h2o + ]; + }; + nginx = mkIf (cfg.nginx != null) ( { enable = true; @@ -631,8 +747,7 @@ in tryFiles = "$uri $uri/ /index.php$is_args$args"; extraConfig = # nginx '' - # https://github.com/movim/movim/issues/314 - add_header Content-Security-Policy "default-src 'self'; img-src 'self' aesgcm: https:; media-src 'self' aesgcm: https:; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"; + add_header Content-Security-Policy "${movimCSP}"; set $no_cache 1; ''; }; @@ -665,7 +780,7 @@ in ''; }; }; - extraConfig = # ngnix + extraConfig = # nginx '' index index.php; ''; @@ -706,27 +821,23 @@ in ''; }; - phpfpm.pools.${pool} = - let - socketOwner = if (cfg.nginx != null) then config.services.nginx.user else cfg.user; - in - { - phpPackage = package.php; - user = cfg.user; - group = cfg.group; + phpfpm.pools.${pool} = { + phpPackage = package.php; + user = cfg.user; + group = cfg.group; - phpOptions = '' - error_log = 'stderr' - log_errors = on - ''; + phpOptions = '' + error_log = 'stderr' + log_errors = on + ''; - settings = { - "listen.owner" = socketOwner; - "listen.group" = cfg.group; - "listen.mode" = "0660"; - "catch_workers_output" = true; - } // cfg.poolConfig; - }; + settings = { + "listen.owner" = socketOwner; + "listen.group" = cfg.group; + "listen.mode" = "0660"; + "catch_workers_output" = true; + } // cfg.poolConfig; + }; }; systemd = { @@ -788,9 +899,9 @@ in }; services.${phpExecutionUnit} = { - wantedBy = lib.optional (cfg.nginx != null) "nginx.service"; + wantedBy = lib.optional (webServerService != null) webServerService; requiredBy = [ "movim.service" ]; - before = [ "movim.service" ] ++ lib.optional (cfg.nginx != null) "nginx.service"; + before = [ "movim.service" ] ++ lib.optional (webServerService != null) webServerService; wants = [ "network.target" ]; requires = [ "movim-data-setup.service" ] ++ lib.optional cfg.database.createLocally dbService; after = [ "movim-data-setup.service" ] ++ lib.optional cfg.database.createLocally dbService; @@ -809,14 +920,14 @@ in "${phpExecutionUnit}.service" ] ++ lib.optional cfg.database.createLocally dbService - ++ lib.optional (cfg.nginx != null) "nginx.service"; + ++ lib.optional (webServerService != null) webServerService; after = [ "movim-data-setup.service" "${phpExecutionUnit}.service" ] ++ lib.optional cfg.database.createLocally dbService - ++ lib.optional (cfg.nginx != null) "nginx.service"; + ++ lib.optional (webServerService != null) webServerService; environment = { PUBLIC_URL = "//${cfg.domain}"; WS_PORT = builtins.toString cfg.port; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 5ffcaccf17f9..b71f6e32833c 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -802,7 +802,7 @@ in morty = handleTest ./morty.nix { }; mosquitto = runTest ./mosquitto.nix; moosefs = handleTest ./moosefs.nix { }; - movim = discoverTests (import ./web-apps/movim { inherit handleTestOn; }); + movim = import ./web-apps/movim { inherit recurseIntoAttrs runTest; }; mpd = handleTest ./mpd.nix { }; mpv = runTest ./mpv.nix; mtp = handleTest ./mtp.nix { }; diff --git a/nixos/tests/web-apps/movim/default.nix b/nixos/tests/web-apps/movim/default.nix index 8274a390990e..7da1e7d88e67 100644 --- a/nixos/tests/web-apps/movim/default.nix +++ b/nixos/tests/web-apps/movim/default.nix @@ -1,14 +1,6 @@ -{ - system ? builtins.currentSystem, - handleTestOn, -}: +{ recurseIntoAttrs, runTest }: -let - supportedSystems = [ - "x86_64-linux" - "i686-linux" - ]; -in -{ - standard = handleTestOn supportedSystems ./standard.nix { inherit system; }; +recurseIntoAttrs { + ejabberd-h2o = runTest ./ejabberd-h2o.nix; + prosody-nginx = runTest ./prosody-nginx.nix; } diff --git a/nixos/tests/web-apps/movim/ejabberd-h2o.nix b/nixos/tests/web-apps/movim/ejabberd-h2o.nix new file mode 100644 index 000000000000..399d7a3b9574 --- /dev/null +++ b/nixos/tests/web-apps/movim/ejabberd-h2o.nix @@ -0,0 +1,274 @@ +{ hostPkgs, lib, ... }: + +let + movim = { + domain = "movim.local"; + port = 8080; + info = "No ToS in tests"; + description = "NixOS testing server"; + }; + ejabberd = { + domain = "ejabberd.local"; + ports = { + c2s = 5222; + s2s = 5269; + http = 5280; + }; + spoolDir = "/var/lib/ejabberd"; + admin = rec { + JID = "${username}@${ejabberd.domain}"; + username = "romeo"; + password = "juliet"; + }; + }; + + # START OF EJABBERD CONFIG ################################################## + # + # Ejabberd has sparse defaults as it is a generic XMPP server. As such this + # config might be longer than expected for a test. + # + # Movim suggests: https://github.com/movim/movim/wiki/Configure ejabberd + # + # In the future this may be the default setup + # See: https://github.com/NixOS/nixpkgs/pull/312316 + ejabberd_config_file = + let + settingsFormat = hostPkgs.formats.yaml { }; + in + settingsFormat.generate "ejabberd.yml" { + loglevel = "info"; + hide_sensitive_log_data = false; + hosts = [ ejabberd.domain ]; + default_db = "mnesia"; + acme.auto = false; + s2s_access = "s2s"; + s2s_use_starttls = false; + new_sql_schema = true; + acl = { + admin = [ + { user = ejabberd.admin.JID; } + ]; + local.user_regexp = ""; + loopback.ip = [ + "127.0.0.1/8" + "::1/128" + ]; + }; + access_rules = { + c2s = { + deny = "blocked"; + allow = "all"; + }; + s2s = { + allow = "all"; + }; + local.allow = "local"; + announce.allow = "admin"; + configure.allow = "admin"; + pubsub_createnode.allow = "local"; + trusted_network.allow = "loopback"; + }; + api_permissions = { + "console commands" = { + from = [ "ejabberd_ctl" ]; + who = "all"; + what = "*"; + }; + }; + shaper = { + normal = { + rate = 3000; + burst_size = 20000; + }; + fast = 100000; + }; + modules = { + mod_caps = { }; + mod_disco = { }; + mod_mam = { }; + mod_http_upload = { + docroot = "${ejabberd.spoolDir}/uploads"; + dir_mode = "0755"; + file_mode = "0644"; + get_url = "http://@HOST@/upload"; + put_url = "http://@HOST@/upload"; + max_size = 65536; + custom_headers = { + Access-Control-Allow-Origin = "http://@HOST@,http://${movim.domain}"; + Access-Control-Allow-Methods = "GET,HEAD,PUT,OPTIONS"; + Access-Control-Allow-Headers = "Content-Type"; + }; + }; + # This PubSub block is required for Movim to work. + # + # See: https://github.com/movim/movim/wiki/Configure ejabberd#pubsub + mod_pubsub = { + hosts = [ "pubsub.@HOST@" ]; + access_createnode = "pubsub_createnode"; + ignore_pep_from_offline = false; + last_item_cache = false; + max_items_node = 2048; + default_node_config = { + max_items = 2048; + }; + plugins = [ + "flat" + "pep" + ]; + force_node_config = { + "storage:bookmarks".access_model = "whitelist"; + "eu.siacs.conversations.axolotl.*".access_model = "open"; + "urn:xmpp:bookmarks:0" = { + access_model = "whitelist"; + send_last_published_item = "never"; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:bookmarks:1" = { + access_model = "whitelist"; + send_last_published_item = "never"; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:pubsub:movim-public-subscription" = { + access_model = "whitelist"; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:microblog:0" = { + notify_retract = true; + max_items = "infinity"; + persist_items = true; + }; + "urn:xmpp:microblog:0:comments*" = { + access_model = "open"; + notify_retract = true; + max_items = "infinity"; + persist_items = true; + }; + }; + }; + mod_stream_mgmt = { }; + }; + listen = [ + { + module = "ejabberd_c2s"; + port = ejabberd.ports.c2s; + max_stanza_size = 262144; + access = "c2s"; + starttls_required = false; + } + { + module = "ejabberd_s2s_in"; + port = ejabberd.ports.s2s; + max_stanza_size = 524288; + shaper = "fast"; + } + { + module = "ejabberd_http"; + port = ejabberd.ports.http; + request_handlers = { + "/upload" = "mod_http_upload"; + }; + } + ]; + }; + # END OF EJABBERD CONFIG ################################################## +in +{ + name = "movim-ejabberd-h2o"; + + meta = { + maintainers = with lib.maintainers; [ toastal ]; + }; + + nodes = { + server = + { pkgs, ... }: + { + environment.systemPackages = [ + # For testing + pkgs.websocat + ]; + + services.movim = { + inherit (movim) domain port; + enable = true; + verbose = true; + podConfig = { + inherit (movim) description info; + xmppdomain = ejabberd.domain; + }; + database = { + type = "postgresql"; + createLocally = true; + }; + h2o = { }; + }; + + services.ejabberd = { + inherit (ejabberd) spoolDir; + enable = true; + configFile = ejabberd_config_file; + imagemagick = false; + }; + + services.h2o.settings = { + compress = "ON"; + }; + + systemd.services.ejabberd = { + serviceConfig = { + # Certain misconfigurations can cause RAM usage to swell before + # crashing; fail sooner with more-than-liberal memory limits + StartupMemoryMax = "1G"; + MemoryMax = "512M"; + }; + }; + + networking = { + firewall.allowedTCPPorts = with ejabberd.ports; [ + c2s + s2s + ]; + extraHosts = '' + 127.0.0.1 ${movim.domain} + 127.0.0.1 ${ejabberd.domain} + ''; + }; + }; + }; + + testScript = # python + '' + ejabberdctl = "su ejabberd -s $(which ejabberdctl) " + + server.wait_for_unit("phpfpm-movim.service") + server.wait_for_unit("h2o.service") + server.wait_for_open_port(${builtins.toString movim.port}) + server.wait_for_open_port(80) + + server.wait_for_unit("ejabberd.service") + ejabberd_status = server.succeed(ejabberdctl + "status") + assert "status: started" in ejabberd_status + server.succeed(ejabberdctl + "register ${ejabberd.admin.username} ${ejabberd.domain} ${ejabberd.admin.password}") + + server.wait_for_unit("movim.service") + + # Test unauthenticated + server.fail("curl -L --fail-with-body --max-redirs 0 http://${movim.domain}/chat") + + # Test basic Websocket + server.succeed("echo | websocat --origin 'http://${movim.domain}' 'ws://${movim.domain}/ws/?path=login&offset=0'") + + # Test login + create cookiejar + login_html = server.succeed("curl --fail-with-body -c /tmp/cookies http://${movim.domain}/login") + assert "${movim.description}" in login_html + assert "${movim.info}" in login_html + + # Test authentication POST + server.succeed("curl --fail-with-body -b /tmp/cookies -X POST --data-urlencode 'username=${ejabberd.admin.JID}' --data-urlencode 'password=${ejabberd.admin.password}' http://${movim.domain}/login") + + server.succeed("curl -L --fail-with-body --max-redirs 1 -b /tmp/cookies http://${movim.domain}/chat") + ''; +} diff --git a/nixos/tests/web-apps/movim/prosody-nginx.nix b/nixos/tests/web-apps/movim/prosody-nginx.nix new file mode 100644 index 000000000000..5477a46a17c8 --- /dev/null +++ b/nixos/tests/web-apps/movim/prosody-nginx.nix @@ -0,0 +1,115 @@ +{ lib, ... }: + +let + movim = { + domain = "movim.local"; + port = 8080; + info = "No ToS in tests"; + description = "NixOS testing server"; + }; + prosody = { + domain = "prosody.local"; + admin = rec { + JID = "${username}@${prosody.domain}"; + username = "romeo"; + password = "juliet"; + }; + }; +in +{ + name = "movim-prosody-nginx"; + + meta = { + maintainers = with lib.maintainers; [ toastal ]; + }; + + nodes = { + server = + { pkgs, ... }: + { + environment.systemPackages = [ + # For testing + pkgs.websocat + ]; + + services.movim = { + inherit (movim) domain port; + enable = true; + verbose = true; + podConfig = { + inherit (movim) description info; + xmppdomain = prosody.domain; + }; + nginx = { }; + }; + + services.prosody = { + enable = true; + xmppComplianceSuite = false; + disco_items = [ + { + url = "upload.${prosody.domain}"; + description = "File Uploads"; + } + ]; + virtualHosts."${prosody.domain}" = { + inherit (prosody) domain; + enabled = true; + extraConfig = '' + Component "pubsub.${prosody.domain}" "pubsub" + pubsub_max_items = 10000 + expose_publisher = true + + Component "upload.${prosody.domain}" "http_file_share" + http_external_url = "http://upload.${prosody.domain}" + http_file_share_expires_after = 300 * 24 * 60 * 60 + http_file_share_size_limit = 1024 * 1024 * 1024 + http_file_share_daily_quota = 4 * 1024 * 1024 * 1024 + ''; + }; + extraConfig = '' + pep_max_items = 10000 + + http_paths = { + file_share = "/"; + } + ''; + }; + + networking.extraHosts = '' + 127.0.0.1 ${movim.domain} + 127.0.0.1 ${prosody.domain} + ''; + }; + }; + + testScript = # python + '' + server.wait_for_unit("phpfpm-movim.service") + server.wait_for_unit("nginx.service") + server.wait_for_open_port(${builtins.toString movim.port}) + server.wait_for_open_port(80) + + server.wait_for_unit("prosody.service") + server.succeed('prosodyctl status | grep "Prosody is running"') + server.succeed("prosodyctl register ${prosody.admin.username} ${prosody.domain} ${prosody.admin.password}") + + server.wait_for_unit("movim.service") + + # Test unauthenticated + server.fail("curl -L --fail-with-body --max-redirs 0 http://${movim.domain}/chat") + + # Test basic Websocket + server.succeed("echo | websocat --origin 'http://${movim.domain}' 'ws://${movim.domain}/ws/?path=login&offset=0'") + + # Test login + create cookiejar + login_html = server.succeed("curl --fail-with-body -c /tmp/cookies http://${movim.domain}/login") + assert "${movim.description}" in login_html + assert "${movim.info}" in login_html + + # Test authentication POST + server.succeed("curl --fail-with-body -b /tmp/cookies -X POST --data-urlencode 'username=${prosody.admin.JID}' --data-urlencode 'password=${prosody.admin.password}' http://${movim.domain}/login") + + server.succeed("curl -L --fail-with-body --max-redirs 1 -b /tmp/cookies http://${movim.domain}/chat") + ''; +} diff --git a/nixos/tests/web-apps/movim/standard.nix b/nixos/tests/web-apps/movim/standard.nix deleted file mode 100644 index 1207000b922b..000000000000 --- a/nixos/tests/web-apps/movim/standard.nix +++ /dev/null @@ -1,110 +0,0 @@ -import ../../make-test-python.nix ( - { lib, pkgs, ... }: - - let - movim = { - domain = "movim.local"; - info = "No ToS in tests"; - description = "NixOS testing server"; - }; - xmpp = { - domain = "xmpp.local"; - admin = rec { - JID = "${username}@${xmpp.domain}"; - username = "romeo"; - password = "juliet"; - }; - }; - in - { - name = "movim-standard"; - - meta = { - maintainers = with pkgs.lib.maintainers; [ toastal ]; - }; - - nodes = { - server = - { pkgs, ... }: - { - services.movim = { - inherit (movim) domain; - enable = true; - verbose = true; - podConfig = { - inherit (movim) description info; - xmppdomain = xmpp.domain; - }; - nginx = { }; - }; - - services.prosody = { - enable = true; - xmppComplianceSuite = false; - disco_items = [ - { - url = "upload.${xmpp.domain}"; - description = "File Uploads"; - } - ]; - virtualHosts."${xmpp.domain}" = { - inherit (xmpp) domain; - enabled = true; - extraConfig = '' - Component "pubsub.${xmpp.domain}" "pubsub" - pubsub_max_items = 10000 - expose_publisher = true - - Component "upload.${xmpp.domain}" "http_file_share" - http_external_url = "http://upload.${xmpp.domain}" - http_file_share_expires_after = 300 * 24 * 60 * 60 - http_file_share_size_limit = 1024 * 1024 * 1024 - http_file_share_daily_quota = 4 * 1024 * 1024 * 1024 - ''; - }; - extraConfig = '' - pep_max_items = 10000 - - http_paths = { - file_share = "/"; - } - ''; - }; - - networking.extraHosts = '' - 127.0.0.1 ${movim.domain} - 127.0.0.1 ${xmpp.domain} - ''; - }; - }; - - testScript = # python - '' - server.wait_for_unit("phpfpm-movim.service") - server.wait_for_unit("nginx.service") - server.wait_for_open_port(80) - - server.wait_for_unit("prosody.service") - server.succeed('prosodyctl status | grep "Prosody is running"') - server.succeed("prosodyctl register ${xmpp.admin.username} ${xmpp.domain} ${xmpp.admin.password}") - - server.wait_for_unit("movim.service") - - # Test unauthenticated - server.fail("curl -L --fail-with-body --max-redirs 0 http://${movim.domain}/chat") - - # Test basic Websocket - server.succeed("echo \"\" | ${lib.getExe pkgs.websocat} 'ws://${movim.domain}/ws/?path=login&offset=0' --origin 'http://${movim.domain}'") - - # Test login + create cookiejar - login_html = server.succeed("curl --fail-with-body -c /tmp/cookies http://${movim.domain}/login") - assert "${movim.description}" in login_html - assert "${movim.info}" in login_html - - # Test authentication POST - server.succeed("curl --fail-with-body -b /tmp/cookies -X POST --data-urlencode 'username=${xmpp.admin.JID}' --data-urlencode 'password=${xmpp.admin.password}' http://${movim.domain}/login") - - server.succeed("curl -L --fail-with-body --max-redirs 1 -b /tmp/cookies http://${movim.domain}/chat") - ''; - } -)