Unverified Commit 6efae3d6 authored by Kevin Cox's avatar Kevin Cox Committed by GitHub
Browse files

Merge pull request #118093 from stuebinm/nextcloud-secrets

nixos/nextcloud: add extraOptions and secretFile options
parents 41a2795b 3fbc2a43
Loading
Loading
Loading
Loading
+59 −3
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ let
  cfg = config.services.nextcloud;
  fpm = config.services.phpfpm.pools.nextcloud;

  jsonFormat = pkgs.formats.json {};

  inherit (cfg) datadir;

  phpPackage = cfg.phpPackage.buildEnv {
@@ -547,6 +549,33 @@ in {
      '';
    };

    extraOptions = mkOption {
      type = jsonFormat.type;
      default = {};
      description = ''
        Extra options which should be appended to nextcloud's config.php file.
      '';
      example = literalExpression '' {
        redis = {
          host = "/run/redis/redis.sock";
          port = 0;
          dbindex = 0;
          password = "secret";
          timeout = 1.5;
        };
      } '';
    };

    secretFile = mkOption {
      type = types.nullOr types.str;
      default = null;
      description = ''
        Secret options which will be appended to nextcloud's config.php file (written as JSON, in the same
        form as the <xref linkend="opt-services.nextcloud.extraOptions"/> option), for example
        <programlisting>{"redis":{"password":"secret"}}</programlisting>.
      '';
    };

    nginx = {
      recommendedHttpHeaders = mkOption {
        type = types.bool;
@@ -706,10 +735,20 @@ in {
                    $file
                  ));
                }

                return trim(file_get_contents($file));
              }''}
            function nix_decode_json_file($file, $error) {
              if (!file_exists($file)) {
                throw new \RuntimeException(sprintf($error, $file));
              }
              $decoded = json_decode(file_get_contents($file), true);

              if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
              }

              return $decoded;
            }
            ''}
            $CONFIG = [
              'apps_paths' => [
                ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"}
@@ -728,7 +767,12 @@ in {
              ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
              ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
              ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_secret('${c.dbpassFile}'),"}
              ${optionalString (c.dbpassFile != null) ''
                  'dbpassword' => nix_read_secret(
                    "${c.dbpassFile}"
                  ),
                ''
              }
              'dbtype' => '${c.dbtype}',
              'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
              'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
@@ -736,6 +780,18 @@ in {
              ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"}
              ${objectstoreConfig}
            ];

            $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
              "${jsonFormat.generate "nextcloud-extraOptions.json" cfg.extraOptions}",
              "impossible: this should never happen (decoding generated extraOptions file %s failed)"
            ));

            ${optionalString (cfg.secretFile != null) ''
              $CONFIG = array_replace_recursive($CONFIG, nix_decode_json_file(
                "${cfg.secretFile}",
                "Cannot start Nextcloud, secrets file %s set by NixOS doesn't exist!"
              ));
            ''}
          '';
          occInstallCmd = let
            mkExport = { arg, value }: "export ${arg}=${value}";
+4 −0
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@ foldl
      inherit system pkgs;
      nextcloudVersion = ver;
    };
    "with-declarative-redis-and-secrets${toString ver}" = import ./with-declarative-redis-and-secrets.nix {
      inherit system pkgs;
      nextcloudVersion = ver;
    };
  })
{ }
  [ 23 24 ]
+118 −0
Original line number Diff line number Diff line
import ../make-test-python.nix ({ pkgs, ...}: let
  adminpass = "hunter2";
  adminuser = "custom-admin-username";
in {
  name = "nextcloud-with-declarative-redis";
  meta = with pkgs.lib.maintainers; {
    maintainers = [ eqyiel ];
  };

  nodes = {
    # The only thing the client needs to do is download a file.
    client = { ... }: {};

    nextcloud = { config, pkgs, ... }: {
      networking.firewall.allowedTCPPorts = [ 80 ];

      services.nextcloud = {
        enable = true;
        hostName = "nextcloud";
        caching = {
          apcu = false;
          redis = true;
          memcached = false;
        };
        config = {
          dbtype = "pgsql";
          dbname = "nextcloud";
          dbuser = "nextcloud";
          dbhost = "/run/postgresql";
          inherit adminuser;
          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
            ${adminpass}
          '');
        };
        secretFile = "/etc/nextcloud-secrets.json";

        extraOptions.redis = {
          host = "/run/redis/redis.sock";
          port = 0;
          dbindex = 0;
          timeout = 1.5;
          # password handled via secretfile below
        };
        extraOptions.memcache = {
          local = "\OC\Memcache\Redis";
          locking = "\OC\Memcache\Redis";
        };
      };

      services.redis = {
        enable = true;
      };

      systemd.services.nextcloud-setup= {
        requires = ["postgresql.service"];
        after = [
          "postgresql.service"
        ];
      };

      services.postgresql = {
        enable = true;
        ensureDatabases = [ "nextcloud" ];
        ensureUsers = [
          { name = "nextcloud";
            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
          }
        ];
      };

      # This file is meant to contain secret options which should
      # not go into the nix store. Here it is just used to set the
      # databyse type to postgres.
      environment.etc."nextcloud-secrets.json".text = ''
        {
          "redis": {
            "password": "secret"
          }
        }
      '';
    };
  };

  testScript = let
    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
      #!${pkgs.runtimeShell}
      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
      "''${@}"
    '';
    copySharedFile = pkgs.writeScript "copy-shared-file" ''
      #!${pkgs.runtimeShell}
      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
    '';

    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
      #!${pkgs.runtimeShell}
      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
    '';
  in ''
    start_all()
    nextcloud.wait_for_unit("multi-user.target")
    nextcloud.succeed("curl -sSf http://nextcloud/login")
    nextcloud.succeed(
        "${withRcloneEnv} ${copySharedFile}"
    )
    client.wait_for_unit("multi-user.target")
    client.succeed(
        "${withRcloneEnv} ${diffSharedFile}"
    )

    # redis cache should not be empty
    nextcloud.fail("redis-cli KEYS * | grep -q 'empty array'")
  '';
})