Merge pull request #118093 from stuebinm/nextcloud-secrets

nixos/nextcloud: add extraOptions and secretFile options

authored by Kevin Cox and committed by GitHub 6efae3d6 41a2795b

+181 -3
+59 -3
nixos/modules/services/web-apps/nextcloud.nix
··· 6 cfg = config.services.nextcloud; 7 fpm = config.services.phpfpm.pools.nextcloud; 8 9 inherit (cfg) datadir; 10 11 phpPackage = cfg.phpPackage.buildEnv { ··· 547 ''; 548 }; 549 550 nginx = { 551 recommendedHttpHeaders = mkOption { 552 type = types.bool; ··· 706 $file 707 )); 708 } 709 - 710 return trim(file_get_contents($file)); 711 } 712 - ''} 713 $CONFIG = [ 714 'apps_paths' => [ 715 ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"} ··· 728 ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"} 729 ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"} 730 ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"} 731 - ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('${c.dbpassFile}'),"} 732 'dbtype' => '${c.dbtype}', 733 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)}, 734 'trusted_proxies' => ${writePhpArrary (c.trustedProxies)}, ··· 736 ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"} 737 ${objectstoreConfig} 738 ]; 739 ''; 740 occInstallCmd = let 741 mkExport = { arg, value }: "export ${arg}=${value}";
··· 6 cfg = config.services.nextcloud; 7 fpm = config.services.phpfpm.pools.nextcloud; 8 9 + jsonFormat = pkgs.formats.json {}; 10 + 11 inherit (cfg) datadir; 12 13 phpPackage = cfg.phpPackage.buildEnv { ··· 549 ''; 550 }; 551 552 + extraOptions = mkOption { 553 + type = jsonFormat.type; 554 + default = {}; 555 + description = '' 556 + Extra options which should be appended to nextcloud's config.php file. 557 + ''; 558 + example = literalExpression '' { 559 + redis = { 560 + host = "/run/redis/redis.sock"; 561 + port = 0; 562 + dbindex = 0; 563 + password = "secret"; 564 + timeout = 1.5; 565 + }; 566 + } ''; 567 + }; 568 + 569 + secretFile = mkOption { 570 + type = types.nullOr types.str; 571 + default = null; 572 + description = '' 573 + Secret options which will be appended to nextcloud's config.php file (written as JSON, in the same 574 + form as the <xref linkend="opt-services.nextcloud.extraOptions"/> option), for example 575 + <programlisting>{"redis":{"password":"secret"}}</programlisting>. 576 + ''; 577 + }; 578 + 579 nginx = { 580 recommendedHttpHeaders = mkOption { 581 type = types.bool; ··· 735 $file 736 )); 737 } 738 return trim(file_get_contents($file)); 739 + }''} 740 + function nix_decode_json_file($file, $error) { 741 + if (!file_exists($file)) { 742 + throw new \RuntimeException(sprintf($error, $file)); 743 } 744 + $decoded = json_decode(file_get_contents($file), true); 745 + 746 + if (json_last_error() !== JSON_ERROR_NONE) { 747 + throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg())); 748 + } 749 + 750 + return $decoded; 751 + } 752 $CONFIG = [ 753 'apps_paths' => [ 754 ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"} ··· 767 ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"} 768 ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"} 769 ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"} 770 + ${optionalString (c.dbpassFile != null) '' 771 + 'dbpassword' => nix_read_secret( 772 + "${c.dbpassFile}" 773 + ), 774 + '' 775 + } 776 'dbtype' => '${c.dbtype}', 777 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)}, 778 'trusted_proxies' => ${writePhpArrary (c.trustedProxies)}, ··· 780 ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"} 781 ${objectstoreConfig} 782 ]; 783 + 784 + $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file( 785 + "${jsonFormat.generate "nextcloud-extraOptions.json" cfg.extraOptions}", 786 + "impossible: this should never happen (decoding generated extraOptions file %s failed)" 787 + )); 788 + 789 + ${optionalString (cfg.secretFile != null) '' 790 + $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file( 791 + "${cfg.secretFile}", 792 + "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!" 793 + )); 794 + ''} 795 ''; 796 occInstallCmd = let 797 mkExport = { arg, value }: "export ${arg}=${value}";
+4
nixos/tests/nextcloud/default.nix
··· 16 inherit system pkgs; 17 nextcloudVersion = ver; 18 }; 19 }) 20 { } 21 [ 23 24 ]
··· 16 inherit system pkgs; 17 nextcloudVersion = ver; 18 }; 19 + "with-declarative-redis-and-secrets${toString ver}" = import ./with-declarative-redis-and-secrets.nix { 20 + inherit system pkgs; 21 + nextcloudVersion = ver; 22 + }; 23 }) 24 { } 25 [ 23 24 ]
+118
nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
···
··· 1 + import ../make-test-python.nix ({ pkgs, ...}: let 2 + adminpass = "hunter2"; 3 + adminuser = "custom-admin-username"; 4 + in { 5 + name = "nextcloud-with-declarative-redis"; 6 + meta = with pkgs.lib.maintainers; { 7 + maintainers = [ eqyiel ]; 8 + }; 9 + 10 + nodes = { 11 + # The only thing the client needs to do is download a file. 12 + client = { ... }: {}; 13 + 14 + nextcloud = { config, pkgs, ... }: { 15 + networking.firewall.allowedTCPPorts = [ 80 ]; 16 + 17 + services.nextcloud = { 18 + enable = true; 19 + hostName = "nextcloud"; 20 + caching = { 21 + apcu = false; 22 + redis = true; 23 + memcached = false; 24 + }; 25 + config = { 26 + dbtype = "pgsql"; 27 + dbname = "nextcloud"; 28 + dbuser = "nextcloud"; 29 + dbhost = "/run/postgresql"; 30 + inherit adminuser; 31 + adminpassFile = toString (pkgs.writeText "admin-pass-file" '' 32 + ${adminpass} 33 + ''); 34 + }; 35 + secretFile = "/etc/nextcloud-secrets.json"; 36 + 37 + extraOptions.redis = { 38 + host = "/run/redis/redis.sock"; 39 + port = 0; 40 + dbindex = 0; 41 + timeout = 1.5; 42 + # password handled via secretfile below 43 + }; 44 + extraOptions.memcache = { 45 + local = "\OC\Memcache\Redis"; 46 + locking = "\OC\Memcache\Redis"; 47 + }; 48 + }; 49 + 50 + services.redis = { 51 + enable = true; 52 + }; 53 + 54 + systemd.services.nextcloud-setup= { 55 + requires = ["postgresql.service"]; 56 + after = [ 57 + "postgresql.service" 58 + ]; 59 + }; 60 + 61 + services.postgresql = { 62 + enable = true; 63 + ensureDatabases = [ "nextcloud" ]; 64 + ensureUsers = [ 65 + { name = "nextcloud"; 66 + ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES"; 67 + } 68 + ]; 69 + }; 70 + 71 + # This file is meant to contain secret options which should 72 + # not go into the nix store. Here it is just used to set the 73 + # databyse type to postgres. 74 + environment.etc."nextcloud-secrets.json".text = '' 75 + { 76 + "redis": { 77 + "password": "secret" 78 + } 79 + } 80 + ''; 81 + }; 82 + }; 83 + 84 + testScript = let 85 + withRcloneEnv = pkgs.writeScript "with-rclone-env" '' 86 + #!${pkgs.runtimeShell} 87 + export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav 88 + export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/" 89 + export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" 90 + export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" 91 + export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" 92 + "''${@}" 93 + ''; 94 + copySharedFile = pkgs.writeScript "copy-shared-file" '' 95 + #!${pkgs.runtimeShell} 96 + echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file 97 + ''; 98 + 99 + diffSharedFile = pkgs.writeScript "diff-shared-file" '' 100 + #!${pkgs.runtimeShell} 101 + diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) 102 + ''; 103 + in '' 104 + start_all() 105 + nextcloud.wait_for_unit("multi-user.target") 106 + nextcloud.succeed("curl -sSf http://nextcloud/login") 107 + nextcloud.succeed( 108 + "${withRcloneEnv} ${copySharedFile}" 109 + ) 110 + client.wait_for_unit("multi-user.target") 111 + client.succeed( 112 + "${withRcloneEnv} ${diffSharedFile}" 113 + ) 114 + 115 + # redis cache should not be empty 116 + nextcloud.fail("redis-cli KEYS * | grep -q 'empty array'") 117 + ''; 118 + })