Merge pull request #296641 from toastal/movim-service

nixos/movim: init, movim: additions + patches

authored by lassulus and committed by GitHub 4cde9116 578d870e

+940 -4
+1
nixos/modules/module-list.nix
··· 1365 1365 ./services/web-apps/miniflux.nix 1366 1366 ./services/web-apps/monica.nix 1367 1367 ./services/web-apps/moodle.nix 1368 + ./services/web-apps/movim.nix 1368 1369 ./services/web-apps/netbox.nix 1369 1370 ./services/web-apps/nextcloud.nix 1370 1371 ./services/web-apps/nextcloud-notify_push.nix
+711
nixos/modules/services/web-apps/movim.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + let 4 + inherit (lib) 5 + filterAttrsRecursive 6 + generators 7 + literalExpression 8 + mkDefault 9 + mkIf 10 + mkOption 11 + mkEnableOption 12 + mkPackageOption 13 + mkMerge 14 + pipe 15 + types 16 + ; 17 + 18 + cfg = config.services.movim; 19 + 20 + defaultPHPCfg = { 21 + "output_buffering" = 0; 22 + "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT"; 23 + "opcache.enable_cli" = 1; 24 + "opcache.interned_strings_buffer" = 8; 25 + "opcache.max_accelerated_files" = 6144; 26 + "opcache.memory_consumption" = 128; 27 + "opcache.revalidate_freq" = 2; 28 + "opcache.fast_shutdown" = 1; 29 + }; 30 + 31 + phpCfg = generators.toKeyValue 32 + { mkKeyValue = generators.mkKeyValueDefault { } " = "; } 33 + (defaultPHPCfg // cfg.phpCfg); 34 + 35 + podConfigFlags = 36 + let 37 + bevalue = a: lib.escapeShellArg (generators.mkValueStringDefault { } a); 38 + in 39 + lib.concatStringsSep " " 40 + (lib.attrsets.foldlAttrs 41 + (acc: k: v: acc ++ lib.optional (v != null) "--${k}=${bevalue v}") 42 + [ ] 43 + cfg.podConfig); 44 + 45 + package = 46 + let 47 + p = cfg.package.override 48 + ({ 49 + inherit phpCfg; 50 + withPgsql = cfg.database.type == "pgsql"; 51 + withMysql = cfg.database.type == "mysql"; 52 + inherit (cfg) minifyStaticFiles; 53 + } // lib.optionalAttrs (lib.isAttrs cfg.minifyStaticFiles) (with cfg.minifyStaticFiles; { 54 + esbuild = esbuild.package; 55 + lightningcss = lightningcss.package; 56 + scour = scour.package; 57 + })); 58 + in 59 + p.overrideAttrs (finalAttrs: prevAttrs: 60 + let 61 + appDir = "$out/share/php/${finalAttrs.pname}"; 62 + 63 + stateDirectories = '' 64 + # Symlinking in our state directories 65 + rm -rf $out/.env $out/cache ${appDir}/public/cache 66 + ln -s ${cfg.dataDir}/.env ${appDir}/.env 67 + ln -s ${cfg.dataDir}/public/cache ${appDir}/public/cache 68 + ln -s ${cfg.logDir} ${appDir}/log 69 + ln -s ${cfg.runtimeDir}/cache ${appDir}/cache 70 + ''; 71 + 72 + exposeComposer = '' 73 + # Expose PHP Composer for scripts 74 + mkdir -p $out/bin 75 + echo "#!${lib.getExe pkgs.dash}" > $out/bin/movim-composer 76 + echo "${finalAttrs.php.packages.composer}/bin/composer --working-dir="${appDir}" \"\$@\"" >> $out/bin/movim-composer 77 + chmod +x $out/bin/movim-composer 78 + ''; 79 + 80 + podConfigInputDisableReplace = lib.optionalString (podConfigFlags != "") 81 + (lib.concatStringsSep "\n" 82 + (lib.attrsets.foldlAttrs 83 + (acc: k: v: 84 + acc ++ lib.optional (v != null) 85 + # Disable all Admin panel options that were set in the 86 + # `cfg.podConfig` to prevent confusing situtions where the 87 + # values are rewritten on server reboot 88 + '' 89 + substituteInPlace ${appDir}/app/widgets/AdminMain/adminmain.tpl \ 90 + --replace-warn 'name="${k}"' 'name="${k}" disabled' 91 + '') 92 + [ ] 93 + cfg.podConfig)); 94 + 95 + precompressStaticFilesJobs = 96 + let 97 + inherit (cfg.precompressStaticFiles) brotli gzip; 98 + 99 + findTextFileNames = lib.concatStringsSep " -o " 100 + (builtins.map (n: ''-iname "*.${n}"'') 101 + [ "css" "ini" "js" "json" "manifest" "mjs" "svg" "webmanifest" ]); 102 + in 103 + lib.concatStringsSep "\n" [ 104 + (lib.optionalString brotli.enable '' 105 + echo -n "Precompressing static files with Brotli …" 106 + find ${appDir}/public -type f ${findTextFileNames} \ 107 + | ${lib.getExe pkgs.parallel} ${lib.escapeShellArgs [ 108 + "--will-cite" 109 + "-j $NIX_BUILD_CORES" 110 + "${lib.getExe brotli.package} --keep --quality=${builtins.toString brotli.compressionLevel} --output={}.br {}" 111 + ]} 112 + echo " done." 113 + '') 114 + (lib.optionalString gzip.enable '' 115 + echo -n "Precompressing static files with Gzip …" 116 + find ${appDir}/public -type f ${findTextFileNames} \ 117 + | ${lib.getExe pkgs.parallel} ${lib.escapeShellArgs [ 118 + "--will-cite" 119 + "-j $NIX_BUILD_CORES" 120 + "${lib.getExe gzip.package} -c -${builtins.toString gzip.compressionLevel} {} > {}.gz" 121 + ]} 122 + echo " done." 123 + '') 124 + ]; 125 + in 126 + { 127 + postInstall = lib.concatStringsSep "\n\n" [ 128 + prevAttrs.postInstall 129 + stateDirectories 130 + exposeComposer 131 + podConfigInputDisableReplace 132 + precompressStaticFilesJobs 133 + ]; 134 + }); 135 + 136 + configFile = pipe cfg.settings [ 137 + (filterAttrsRecursive (_: v: v != null)) 138 + (generators.toKeyValue { }) 139 + (pkgs.writeText "movim-env") 140 + ]; 141 + 142 + pool = "movim"; 143 + fpm = config.services.phpfpm.pools.${pool}; 144 + phpExecutionUnit = "phpfpm-${pool}"; 145 + 146 + dbService = { 147 + "postgresql" = "postgresql.service"; 148 + "mysql" = "mysql.service"; 149 + }.${cfg.database.type}; 150 + in 151 + { 152 + options.services = { 153 + movim = { 154 + enable = mkEnableOption "a Movim instance"; 155 + package = mkPackageOption pkgs "movim" { }; 156 + phpPackage = mkPackageOption pkgs "php" { }; 157 + 158 + phpCfg = mkOption { 159 + type = with types; attrsOf (oneOf [ int str bool ]); 160 + defaultText = literalExpression (generators.toPretty { } defaultPHPCfg); 161 + default = { }; 162 + description = "Extra PHP INI options such as `memory_limit`, `max_execution_time`, etc."; 163 + }; 164 + 165 + user = mkOption { 166 + type = types.nonEmptyStr; 167 + default = "movim"; 168 + description = "User running Movim service"; 169 + }; 170 + 171 + group = mkOption { 172 + type = types.nonEmptyStr; 173 + default = "movim"; 174 + description = "Group running Movim service"; 175 + }; 176 + 177 + dataDir = mkOption { 178 + type = types.nonEmptyStr; 179 + default = "/var/lib/movim"; 180 + description = "State directory of the `movim` user which holds the application’s state & data."; 181 + }; 182 + 183 + logDir = mkOption { 184 + type = types.nonEmptyStr; 185 + default = "/var/log/movim"; 186 + description = "Log directory of the `movim` user which holds the application’s logs."; 187 + }; 188 + 189 + runtimeDir = mkOption { 190 + type = types.nonEmptyStr; 191 + default = "/run/movim"; 192 + description = "Runtime directory of the `movim` user which holds the application’s caches & temporary files."; 193 + }; 194 + 195 + domain = mkOption { 196 + type = types.nonEmptyStr; 197 + description = "Fully-qualified domain name (FQDN) for the Movim instance."; 198 + }; 199 + 200 + port = mkOption { 201 + type = types.port; 202 + default = 8080; 203 + description = "Movim daemon port."; 204 + }; 205 + 206 + debug = mkOption { 207 + type = types.bool; 208 + default = false; 209 + description = "Debugging logs."; 210 + }; 211 + 212 + verbose = mkOption { 213 + type = types.bool; 214 + default = false; 215 + description = "Verbose logs."; 216 + }; 217 + 218 + minifyStaticFiles = mkOption { 219 + type = with types; either bool (submodule { 220 + options = { 221 + script = mkOption { 222 + type = types.submodule { 223 + options = { 224 + enable = mkEnableOption "Script minification"; 225 + package = mkPackageOption pkgs "esbuild" { }; 226 + target = mkOption { 227 + type = with types; nullOr nonEmptyStr; 228 + default = null; 229 + }; 230 + }; 231 + }; 232 + }; 233 + style = mkOption { 234 + type = types.submodule { 235 + options = { 236 + enable = mkEnableOption "Script minification"; 237 + package = mkPackageOption pkgs "lightningcss" { }; 238 + target = mkOption { 239 + type = with types; nullOr nonEmptyStr; 240 + default = null; 241 + }; 242 + }; 243 + }; 244 + }; 245 + svg = mkOption { 246 + type = types.submodule { 247 + options = { 248 + enable = mkEnableOption "SVG minification"; 249 + package = mkPackageOption pkgs "scour" { }; 250 + }; 251 + }; 252 + }; 253 + }; 254 + }); 255 + default = true; 256 + description = "Do minification on public static files"; 257 + }; 258 + 259 + precompressStaticFiles = mkOption { 260 + type = with types; submodule { 261 + options = { 262 + brotli = { 263 + enable = mkEnableOption "Brotli precompression"; 264 + package = mkPackageOption pkgs "brotli" { }; 265 + compressionLevel = mkOption { 266 + type = types.ints.between 0 11; 267 + default = 11; 268 + description = "Brotli compression level"; 269 + }; 270 + }; 271 + gzip = { 272 + enable = mkEnableOption "Gzip precompression"; 273 + package = mkPackageOption pkgs "gzip" { }; 274 + compressionLevel = mkOption { 275 + type = types.ints.between 1 9; 276 + default = 9; 277 + description = "Gzip compression level"; 278 + }; 279 + }; 280 + }; 281 + }; 282 + default = { 283 + brotli.enable = true; 284 + gzip.enable = false; 285 + }; 286 + description = "Aggressively precompress static files"; 287 + }; 288 + 289 + podConfig = mkOption { 290 + type = types.submodule { 291 + options = { 292 + info = mkOption { 293 + type = with types; nullOr str; 294 + default = null; 295 + description = "Content of the info box on the login page"; 296 + }; 297 + 298 + description = mkOption { 299 + type = with types; nullOr str; 300 + default = null; 301 + description = "General description of the instance"; 302 + }; 303 + 304 + timezone = mkOption { 305 + type = with types; nullOr str; 306 + default = null; 307 + description = "The server timezone"; 308 + }; 309 + 310 + restrictsuggestions = mkOption { 311 + type = with types; nullOr bool; 312 + default = null; 313 + description = "Only suggest chatrooms, Communities and other contents that are available on the user XMPP server and related services"; 314 + }; 315 + 316 + chatonly = mkOption { 317 + type = with types; nullOr bool; 318 + default = null; 319 + description = "Disable all the social feature (Communities, Blog…) and keep only the chat ones"; 320 + }; 321 + 322 + disableregistration = mkOption { 323 + type = with types; nullOr bool; 324 + default = null; 325 + description = "Remove the XMPP registration flow and buttons from the interface"; 326 + }; 327 + 328 + loglevel = mkOption { 329 + type = with types; nullOr (ints.between 0 3); 330 + default = null; 331 + description = "The server loglevel"; 332 + }; 333 + 334 + locale = mkOption { 335 + type = with types; nullOr str; 336 + default = null; 337 + description = "The server main locale"; 338 + }; 339 + 340 + xmppdomain = mkOption { 341 + type = with types; nullOr str; 342 + default = null; 343 + description = "The default XMPP server domain"; 344 + }; 345 + 346 + xmppdescription = mkOption { 347 + type = with types; nullOr str; 348 + default = null; 349 + description = "The default XMPP server description"; 350 + }; 351 + 352 + xmppwhitelist = mkOption { 353 + type = with types; nullOr str; 354 + default = null; 355 + description = "The allowlisted XMPP servers"; 356 + }; 357 + }; 358 + }; 359 + default = { }; 360 + description = '' 361 + Pod configuration (values from `php daemon.php config --help`). 362 + Note that these values will now be disabled in the admin panel. 363 + ''; 364 + }; 365 + 366 + settings = mkOption { 367 + type = with types; attrsOf (nullOr (oneOf [ int str bool ])); 368 + default = { }; 369 + description = ".env settings for Movim. Secrets should use `secretFile` option instead. `null`s will be culled."; 370 + }; 371 + 372 + secretFile = mkOption { 373 + type = with types; nullOr path; 374 + default = null; 375 + description = "The secret file to be sourced for the .env settings."; 376 + }; 377 + 378 + database = { 379 + type = mkOption { 380 + type = types.enum [ "mysql" "postgresql" ]; 381 + example = "mysql"; 382 + default = "postgresql"; 383 + description = "Database engine to use."; 384 + }; 385 + 386 + name = mkOption { 387 + type = types.str; 388 + default = "movim"; 389 + description = "Database name."; 390 + }; 391 + 392 + user = mkOption { 393 + type = types.str; 394 + default = "movim"; 395 + description = "Database username."; 396 + }; 397 + 398 + createLocally = mkOption { 399 + type = types.bool; 400 + default = true; 401 + description = "local database using UNIX socket authentication"; 402 + }; 403 + }; 404 + 405 + nginx = mkOption { 406 + type = with types; nullOr (submodule 407 + (import ../web-servers/nginx/vhost-options.nix { 408 + inherit config lib; 409 + })); 410 + default = null; 411 + example = lib.literalExpression /* nginx */ '' 412 + { 413 + serverAliases = [ 414 + "pics.''${config.networking.domain}" 415 + ]; 416 + enableACME = true; 417 + forceHttps = true; 418 + } 419 + ''; 420 + description = '' 421 + With this option, you can customize an nginx virtual host which already has sensible defaults for Movim. 422 + Set to `{ }` if you do not need any customization to the virtual host. 423 + If enabled, then by default, the {option}`serverName` is `''${domain}`, 424 + If this is set to null (the default), no nginx virtualHost will be configured. 425 + ''; 426 + }; 427 + 428 + poolConfig = mkOption { 429 + type = with types; attrsOf (oneOf [ int str bool ]); 430 + default = { }; 431 + description = "Options for Movim’s PHP-FPM pool."; 432 + }; 433 + }; 434 + }; 435 + 436 + config = mkIf cfg.enable { 437 + environment.systemPackages = [ cfg.package ]; 438 + 439 + users = { 440 + users = { 441 + movim = mkIf (cfg.user == "movim") { 442 + isSystemUser = true; 443 + group = cfg.group; 444 + }; 445 + "${config.services.nginx.user}".extraGroups = [ cfg.group ]; 446 + }; 447 + groups = { 448 + ${cfg.group} = { }; 449 + }; 450 + }; 451 + 452 + services = { 453 + movim = { 454 + settings = mkMerge [ 455 + { 456 + DAEMON_URL = "//${cfg.domain}"; 457 + DAEMON_PORT = cfg.port; 458 + DAEMON_INTERFACE = "127.0.0.1"; 459 + DAEMON_DEBUG = cfg.debug; 460 + DAEMON_VERBOSE = cfg.verbose; 461 + } 462 + (mkIf cfg.database.createLocally { 463 + DB_DRIVER = { 464 + "postgresql" = "pgsql"; 465 + "mysql" = "mysql"; 466 + }.${cfg.database.type}; 467 + DB_HOST = "localhost"; 468 + DB_PORT = config.services.${cfg.database.type}.settings.port; 469 + DB_DATABASE = cfg.database.name; 470 + DB_USERNAME = cfg.database.user; 471 + DB_PASSWORD = ""; 472 + }) 473 + ]; 474 + 475 + poolConfig = lib.mapAttrs' (n: v: lib.nameValuePair n (lib.mkDefault v)) { 476 + "pm" = "dynamic"; 477 + "php_admin_value[error_log]" = "stderr"; 478 + "php_admin_flag[log_errors]" = true; 479 + "catch_workers_output" = true; 480 + "pm.max_children" = 32; 481 + "pm.start_servers" = 2; 482 + "pm.min_spare_servers" = 2; 483 + "pm.max_spare_servers" = 8; 484 + "pm.max_requests" = 500; 485 + }; 486 + }; 487 + 488 + nginx = mkIf (cfg.nginx != null) { 489 + enable = true; 490 + recommendedOptimisation = true; 491 + recommendedGzipSettings = true; 492 + recommendedBrotliSettings = true; 493 + recommendedProxySettings = true; 494 + # TODO: recommended cache options already in Nginx⁇ 495 + appendHttpConfig = /* nginx */ '' 496 + fastcgi_cache_path /tmp/nginx_cache levels=1:2 keys_zone=nginx_cache:100m inactive=60m; 497 + fastcgi_cache_key "$scheme$request_method$host$request_uri"; 498 + ''; 499 + virtualHosts."${cfg.domain}" = mkMerge [ 500 + cfg.nginx 501 + { 502 + root = lib.mkForce "${package}/share/php/movim/public"; 503 + locations = { 504 + "/favicon.ico" = { 505 + priority = 100; 506 + extraConfig = /* nginx */ '' 507 + access_log off; 508 + log_not_found off; 509 + ''; 510 + }; 511 + "/robots.txt" = { 512 + priority = 100; 513 + extraConfig = /* nginx */ '' 514 + access_log off; 515 + log_not_found off; 516 + ''; 517 + }; 518 + "~ /\\.(?!well-known).*" = { 519 + priority = 210; 520 + extraConfig = /* nginx */ '' 521 + deny all; 522 + ''; 523 + }; 524 + # Ask nginx to cache every URL starting with "/picture" 525 + "/picture" = { 526 + priority = 400; 527 + tryFiles = "$uri $uri/ /index.php$is_args$args"; 528 + extraConfig = /* nginx */ '' 529 + set $no_cache 0; # Enable cache only there 530 + ''; 531 + }; 532 + "/" = { 533 + priority = 490; 534 + tryFiles = "$uri $uri/ /index.php$is_args$args"; 535 + extraConfig = /* nginx */ '' 536 + # https://github.com/movim/movim/issues/314 537 + 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';"; 538 + set $no_cache 1; 539 + ''; 540 + }; 541 + "~ \\.php$" = { 542 + priority = 500; 543 + tryFiles = "$uri =404"; 544 + extraConfig = /* nginx */ '' 545 + include ${config.services.nginx.package}/conf/fastcgi.conf; 546 + add_header X-Cache $upstream_cache_status; 547 + fastcgi_ignore_headers "Cache-Control" "Expires" "Set-Cookie"; 548 + fastcgi_cache nginx_cache; 549 + fastcgi_cache_valid any 7d; 550 + fastcgi_cache_bypass $no_cache; 551 + fastcgi_no_cache $no_cache; 552 + fastcgi_split_path_info ^(.+\.php)(/.+)$; 553 + fastcgi_index index.php; 554 + fastcgi_pass unix:${fpm.socket}; 555 + ''; 556 + }; 557 + "/ws/" = { 558 + priority = 900; 559 + proxyPass = "http://${cfg.settings.DAEMON_INTERFACE}:${builtins.toString cfg.port}/"; 560 + proxyWebsockets = true; 561 + recommendedProxySettings = true; 562 + extraConfig = /* nginx */ '' 563 + proxy_set_header X-Forwarded-Proto $scheme; 564 + proxy_redirect off; 565 + ''; 566 + }; 567 + }; 568 + extraConfig = /* ngnix */ '' 569 + index index.php; 570 + ''; 571 + } 572 + ]; 573 + }; 574 + 575 + mysql = mkIf (cfg.database.createLocally && cfg.database.type == "mysql") { 576 + enable = mkDefault true; 577 + package = mkDefault pkgs.mariadb; 578 + ensureDatabases = [ cfg.database.name ]; 579 + ensureUsers = [{ 580 + name = cfg.user; 581 + ensureDBOwnership = true; 582 + }]; 583 + }; 584 + 585 + postgresql = mkIf (cfg.database.createLocally && cfg.database.type == "postgresql") { 586 + enable = mkDefault true; 587 + ensureDatabases = [ cfg.database.name ]; 588 + ensureUsers = [{ 589 + name = cfg.user; 590 + ensureDBOwnership = true; 591 + }]; 592 + authentication = '' 593 + host ${cfg.database.name} ${cfg.database.user} localhost trust 594 + ''; 595 + }; 596 + 597 + phpfpm.pools.${pool} = 598 + let 599 + socketOwner = 600 + if (cfg.nginx != null) 601 + then config.services.nginx.user 602 + else cfg.user; 603 + in 604 + { 605 + phpPackage = package.php; 606 + user = cfg.user; 607 + group = cfg.group; 608 + 609 + phpOptions = '' 610 + error_log = 'stderr' 611 + log_errors = on 612 + ''; 613 + 614 + settings = { 615 + "listen.owner" = socketOwner; 616 + "listen.group" = cfg.group; 617 + "listen.mode" = "0660"; 618 + "catch_workers_output" = true; 619 + } // cfg.poolConfig; 620 + }; 621 + }; 622 + 623 + systemd = { 624 + services.movim-data-setup = { 625 + description = "Movim setup: .env file, databases init, cache reload"; 626 + wantedBy = [ "multi-user.target" ]; 627 + requiredBy = [ "${phpExecutionUnit}.service" ]; 628 + before = [ "${phpExecutionUnit}.service" ]; 629 + after = lib.optional cfg.database.createLocally dbService; 630 + requires = lib.optional cfg.database.createLocally dbService; 631 + 632 + serviceConfig = { 633 + Type = "oneshot"; 634 + User = cfg.user; 635 + Group = cfg.group; 636 + UMask = "077"; 637 + } // lib.optionalAttrs (cfg.secretFile != null) { 638 + LoadCredential = "env-secrets:${cfg.secretFile}"; 639 + }; 640 + 641 + script = '' 642 + # Env vars 643 + rm -f ${cfg.dataDir}/.env 644 + cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env 645 + echo -e '\n' >> ${cfg.dataDir}/.env 646 + if [[ -f "$CREDENTIALS_DIRECTORY/env-secrets" ]]; then 647 + cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env 648 + echo -e '\n' >> ${cfg.dataDir}/.env 649 + fi 650 + 651 + # Caches, logs 652 + mkdir -p ${cfg.dataDir}/public/cache ${cfg.logDir} ${cfg.runtimeDir}/cache 653 + chmod -R ug+rw ${cfg.dataDir}/public/cache 654 + chmod -R ug+rw ${cfg.logDir} 655 + chmod -R ug+rwx ${cfg.runtimeDir}/cache 656 + 657 + # Migrations 658 + MOVIM_VERSION="${package.version}" 659 + if [[ ! -f "${cfg.dataDir}/.migration-version" ]] || [[ "$MOVIM_VERSION" != "$(<${cfg.dataDir}/.migration-version)" ]]; then 660 + ${package}/bin/movim-composer movim:migrate && echo $MOVIM_VERSION > ${cfg.dataDir}/.migration-version 661 + fi 662 + '' 663 + + lib.optionalString (podConfigFlags != "") ( 664 + let 665 + flags = lib.concatStringsSep " " 666 + ([ "--no-interaction" ] 667 + ++ lib.optional cfg.debug "-vvv" 668 + ++ lib.optional (!cfg.debug && cfg.verbose) "-v"); 669 + in 670 + '' 671 + ${lib.getExe package} config ${podConfigFlags} 672 + '' 673 + ); 674 + }; 675 + 676 + services.movim = { 677 + description = "Movim daemon"; 678 + wantedBy = [ "multi-user.target" ]; 679 + after = [ "movim-data-setup.service" ]; 680 + requires = [ "movim-data-setup.service" ] 681 + ++ lib.optional cfg.database.createLocally dbService; 682 + environment = { 683 + PUBLIC_URL = "//${cfg.domain}"; 684 + WS_PORT = builtins.toString cfg.port; 685 + }; 686 + 687 + serviceConfig = { 688 + User = cfg.user; 689 + Group = cfg.group; 690 + WorkingDirectory = "${package}/share/php/movim"; 691 + ExecStart = "${lib.getExe package} start"; 692 + }; 693 + }; 694 + 695 + services.${phpExecutionUnit} = { 696 + after = [ "movim-data-setup.service" ]; 697 + requires = [ "movim-data-setup.service" ] 698 + ++ lib.optional cfg.database.createLocally dbService; 699 + }; 700 + 701 + tmpfiles.settings."10-movim" = with cfg; { 702 + "${dataDir}".d = { inherit user group; mode = "0710"; }; 703 + "${dataDir}/public".d = { inherit user group; mode = "0750"; }; 704 + "${dataDir}/public/cache".d = { inherit user group; mode = "0750"; }; 705 + "${runtimeDir}".d = { inherit user group; mode = "0700"; }; 706 + "${runtimeDir}/cache".d = { inherit user group; mode = "0700"; }; 707 + "${logDir}".d = { inherit user group; mode = "0700"; }; 708 + }; 709 + }; 710 + }; 711 + }
+1
nixos/tests/all-tests.nix
··· 558 558 morty = handleTest ./morty.nix {}; 559 559 mosquitto = handleTest ./mosquitto.nix {}; 560 560 moosefs = handleTest ./moosefs.nix {}; 561 + movim = discoverTests (import ./web-apps/movim { inherit handleTestOn; }); 561 562 mpd = handleTest ./mpd.nix {}; 562 563 mpv = handleTest ./mpv.nix {}; 563 564 mtp = handleTest ./mtp.nix {};
+8
nixos/tests/web-apps/movim/default.nix
··· 1 + { system ? builtins.currentSystem, handleTestOn }: 2 + 3 + let 4 + supportedSystems = [ "x86_64-linux" "i686-linux" ]; 5 + in 6 + { 7 + standard = handleTestOn supportedSystems ./standard.nix { inherit system; }; 8 + }
+102
nixos/tests/web-apps/movim/standard.nix
··· 1 + import ../../make-test-python.nix ({ lib, pkgs, ... }: 2 + 3 + let 4 + movim = { 5 + domain = "movim.local"; 6 + info = "No ToS in tests"; 7 + description = "NixOS testing server"; 8 + }; 9 + xmpp = { 10 + domain = "xmpp.local"; 11 + admin = rec { 12 + JID = "${username}@${xmpp.domain}"; 13 + username = "romeo"; 14 + password = "juliet"; 15 + }; 16 + }; 17 + in 18 + { 19 + name = "movim-standard"; 20 + 21 + meta = { 22 + maintainers = with pkgs.lib.maintainers; [ toastal ]; 23 + }; 24 + 25 + nodes = { 26 + server = { pkgs, ... }: { 27 + services.movim = { 28 + inherit (movim) domain; 29 + enable = true; 30 + verbose = true; 31 + podConfig = { 32 + inherit (movim) description info; 33 + xmppdomain = xmpp.domain; 34 + }; 35 + nginx = { }; 36 + }; 37 + 38 + services.prosody = { 39 + enable = true; 40 + xmppComplianceSuite = false; 41 + disco_items = [ 42 + { url = "upload.${xmpp.domain}"; description = "File Uploads"; } 43 + ]; 44 + virtualHosts."${xmpp.domain}" = { 45 + inherit (xmpp) domain; 46 + enabled = true; 47 + extraConfig = '' 48 + Component "pubsub.${xmpp.domain}" "pubsub" 49 + pubsub_max_items = 10000 50 + expose_publisher = true 51 + 52 + Component "upload.${xmpp.domain}" "http_file_share" 53 + http_external_url = "http://upload.${xmpp.domain}" 54 + http_file_share_expires_after = 300 * 24 * 60 * 60 55 + http_file_share_size_limit = 1024 * 1024 * 1024 56 + http_file_share_daily_quota = 4 * 1024 * 1024 * 1024 57 + ''; 58 + }; 59 + extraConfig = '' 60 + pep_max_items = 10000 61 + 62 + http_paths = { 63 + file_share = "/"; 64 + } 65 + ''; 66 + }; 67 + 68 + networking.extraHosts = '' 69 + 127.0.0.1 ${movim.domain} 70 + 127.0.0.1 ${xmpp.domain} 71 + ''; 72 + }; 73 + }; 74 + 75 + testScript = /* python */ '' 76 + server.wait_for_unit("phpfpm-movim.service") 77 + server.wait_for_unit("nginx.service") 78 + server.wait_for_open_port(80) 79 + 80 + server.wait_for_unit("prosody.service") 81 + server.succeed('prosodyctl status | grep "Prosody is running"') 82 + server.succeed("prosodyctl register ${xmpp.admin.username} ${xmpp.domain} ${xmpp.admin.password}") 83 + 84 + server.wait_for_unit("movim.service") 85 + 86 + # Test unauthenticated 87 + server.fail("curl -L --fail-with-body --max-redirs 0 http://${movim.domain}/chat") 88 + 89 + # Test basic Websocket 90 + server.succeed("echo \"\" | ${lib.getExe pkgs.websocat} 'ws://${movim.domain}/ws/?path=login&offset=0' --origin 'http://${movim.domain}'") 91 + 92 + # Test login + create cookiejar 93 + login_html = server.succeed("curl --fail-with-body -c /tmp/cookies http://${movim.domain}/login") 94 + assert "${movim.description}" in login_html 95 + assert "${movim.info}" in login_html 96 + 97 + # Test authentication POST 98 + 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") 99 + 100 + server.succeed("curl -L --fail-with-body --max-redirs 1 -b /tmp/cookies http://${movim.domain}/chat") 101 + ''; 102 + })
+117 -4
pkgs/by-name/mo/movim/package.nix
··· 1 1 { lib 2 + , fetchpatch 2 3 , fetchFromGitHub 4 + , dash 3 5 , php 4 6 , phpCfg ? null 5 7 , withPgsql ? true # “strongly recommended” according to docs 6 8 , withMysql ? false 9 + , minifyStaticFiles ? false # default files are often not minified 10 + , parallel 11 + , esbuild 12 + , lightningcss 13 + , scour 14 + , nixosTests 7 15 }: 8 16 17 + let 18 + defaultMinifyOpts = { 19 + script = { 20 + enable = false; 21 + target = "es2021"; 22 + }; 23 + style = { 24 + enable = false; 25 + browserslist = "defaults, Firefox ESR, last 20 Firefox major versions, last 20 Chrome major versions, last 3 Safari major versions, last 1 KaiOS version, and supports css-variables"; 26 + }; 27 + svg = { 28 + enable = false; 29 + }; 30 + }; 31 + 32 + minify = lib.recursiveUpdate defaultMinifyOpts 33 + (if lib.isBool minifyStaticFiles && minifyStaticFiles then 34 + { script.enable = true; style.enable = true; svg.enable = true; } 35 + else if lib.isAttrs minifyStaticFiles then 36 + lib.filterAttrsRecursive (_: v: v != null) minifyStaticFiles 37 + else 38 + { }); 39 + in 9 40 php.buildComposerProject (finalAttrs: { 10 41 pname = "movim"; 11 - version = "0.23"; 42 + version = "0.23.0.20240328"; 12 43 13 44 src = fetchFromGitHub { 14 45 owner = "movim"; 15 46 repo = "movim"; 16 - rev = "v${finalAttrs.version}"; 17 - hash = "sha256-9MBe2IRYxvUuCc5m7ajvIlBU7YVm4A3RABlOOIjpKoM="; 47 + rev = "c3a43cd7e3a1a3a6efd595470e6a85b2ec578cba"; 48 + hash = "sha256-x0C4w3SRP3NMOhGSZOQALk6PNWUre4MvFW5cESr8Wvk="; 18 49 }; 19 50 20 51 php = php.buildEnv ({ ··· 28 59 extraConfig = phpCfg; 29 60 }); 30 61 62 + nativeBuildInputs = 63 + lib.optional (lib.any (x: x.enable) (lib.attrValues minify)) parallel 64 + ++ lib.optional minify.script.enable esbuild 65 + ++ lib.optional minify.style.enable lightningcss 66 + ++ lib.optional minify.svg.enable scour; 67 + 31 68 # no listed license 32 69 # pinned commonmark 33 70 composerStrictValidation = false; 34 71 35 - vendorHash = "sha256-PBoJbVuF0Qy7nNlL4yx446ivlZpPYNIai78yC0wWkCM="; 72 + vendorHash = "sha256-RFIi1I+gcagRgkDpgQeR1oGJeBGA7z9q3DCfW+ZDr2Y="; 73 + 74 + postPatch = '' 75 + # Our modules are already wrapped, removes missing *.so warnings; 76 + # replacing `$configuration` with actually-used flags. 77 + substituteInPlace src/Movim/Daemon/Session.php \ 78 + --replace-fail "exec php ' . \$configuration " "exec php -dopcache.enable=1 -dopcache.enable_cli=1 ' " 79 + 80 + # Point to PHP + PHP INI in the Nix store 81 + substituteInPlace src/Movim/{Console/DaemonCommand.php,Daemon/Session.php} \ 82 + --replace-fail "exec php " "exec ${lib.getExe finalAttrs.php} " 83 + substituteInPlace src/Movim/Console/DaemonCommand.php \ 84 + --replace-fail "<info>php vendor/bin/phinx migrate</info>" \ 85 + "<info>${lib.getBin finalAttrs.php} vendor/bin/phinx migrate</info>" \ 86 + --replace-fail "<info>php daemon.php setAdmin {jid}</info>" \ 87 + "<info>${finalAttrs.meta.mainProgram} setAdmin {jid}</info>" 88 + 89 + # BUGFIX: Imagick API Changes for 7.x+ 90 + # See additionally: https://github.com/movim/movim/pull/1122 91 + substituteInPlace src/Movim/Image.php \ 92 + --replace-fail "Imagick::ALPHACHANNEL_REMOVE" "Imagick::ALPHACHANNEL_OFF" \ 93 + --replace-fail "Imagick::ALPHACHANNEL_ACTIVATE" "Imagick::ALPHACHANNEL_ON" 94 + ''; 95 + 96 + preBuild = lib.optionalString minify.script.enable '' 97 + find ./public -type f -iname "*.js" \ 98 + | parallel ${lib.escapeShellArgs [ 99 + "--will-cite" 100 + "-j $NIX_BUILD_CORES" 101 + '' 102 + tmp="$(mktemp)" 103 + esbuild {} --minify --target=${lib.escapeShellArg minify.script.target} --outfile=$tmp 104 + [[ "$(stat -c %s $tmp)" -lt "$(stat -c %s {})" ]] && mv $tmp {} 105 + '' 106 + ]} 107 + '' + lib.optionalString minify.style.enable '' 108 + export BROWSERLIST=${lib.escapeShellArg minify.style.browserslist} 109 + find ./public -type f -iname "*.css" \ 110 + | parallel ${lib.escapeShellArgs [ 111 + "--will-cite" 112 + "-j $NIX_BUILD_CORES" 113 + '' 114 + tmp="$(mktemp)" 115 + lightningcss {} --minify --browserslist --output-file=$tmp 116 + [[ "$(stat -c %s $tmp)" -lt "$(stat -c %s {})" ]] && mv $tmp {} 117 + '' 118 + ]} 119 + '' + lib.optionalString minify.svg.enable '' 120 + find ./public -type f -iname "*.svg" -a -not -path "*/emojis/*" \ 121 + | parallel ${lib.escapeShellArgs [ 122 + "--will-cite" 123 + "-j $NIX_BUILD_CORES" 124 + '' 125 + tmp="$(mktemp)" 126 + scour -i {} -o $tmp --disable-style-to-xml --enable-comment-stripping --enable-viewboxing --indent=tab 127 + [[ "$(stat -c %s $tmp)" -lt "$(stat -c %s {})" ]] && mv $tmp {} 128 + '' 129 + ]} 130 + ''; 131 + 132 + postInstall = '' 133 + mkdir -p $out/bin 134 + echo "#!${lib.getExe dash}" > $out/bin/movim 135 + echo "${lib.getExe finalAttrs.php} $out/share/php/${finalAttrs.pname}/daemon.php \"\$@\"" >> $out/bin/movim 136 + chmod +x $out/bin/movim 137 + 138 + mkdir -p $out/share/{bash-completion/completion,fish/vendor_completions.d,zsh/site-functions} 139 + $out/bin/movim completion bash | sed "s/daemon.php/movim/g" > $out/share/bash-completion/completion/movim.bash 140 + $out/bin/movim completion fish | sed "s/daemon.php/movim/g" > $out/share/fish/vendor_completions.d/movim.fish 141 + $out/bin/movim completion zsh | sed "s/daemon.php/movim/g" > $out/share/zsh/site-functions/_movim 142 + chmod +x $out/share/{bash-completion/completion/movim.bash,fish/vendor_completions.d/movim.fish,zsh/site-functions/_movim} 143 + ''; 144 + 145 + passthru = { 146 + tests = { inherit (nixosTests) movim; }; 147 + }; 36 148 37 149 meta = { 38 150 description = "a federated blogging & chat platform that acts as a web front end for the XMPP protocol"; 39 151 homepage = "https://movim.eu"; 40 152 license = lib.licenses.agpl3Plus; 41 153 maintainers = with lib.maintainers; [ toastal ]; 154 + mainProgram = "movim"; 42 155 }; 43 156 })