diff --git a/hosts/hainich/services/nextcloud.nix b/hosts/hainich/services/nextcloud.nix
index 477f8dd..63a1679 100644
--- a/hosts/hainich/services/nextcloud.nix
+++ b/hosts/hainich/services/nextcloud.nix
@@ -1,11 +1,7 @@
# TODOs before actually using this
# - change root auth to use adminpassFile
-# - figure out how to enable redis caching
# - figure out how to use multiple pools (do we need this?)
# - how to enable ldap?
-# - move this into a container (only reason it's not in one already is
-# to make testing easy; just run the following for a local test:
-# `nixos-container create nextcloud --config-file nextcloud.nix`
#
# Additional notes:
# - there is a services.nextcloud.phpExtraExtensions, which may be
@@ -13,70 +9,101 @@
# - there's a services.nextcloud.autoUpdateApps – do we trust nextcloud
# enough to enable it, or will everything break if we do?
-{ pkgs, ... }:
+
+
+{pkgs, config, ...}:
+
{
- environment.systemPackages = [ pkgs.htop ];
+ containers.nextcloud = {
- services.nextcloud = {
- enable = true;
-
- # must be set manually; may not be incremented by more than one at
- # a time, otherwise nextcloud WILL break
- package = pkgs.nextcloud20;
-
- hostName = "10.233.2.2";
- config = {
- dbtype = "pgsql";
- dbuser = "nextcloud";
- dbhost = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
- dbname = "nextcloud";
- # there's also a adminpassFile option, but for testing this seems
- # enough (less fiddling with getting the file into a nixos
- # container for ad-hoc setups)
- adminpass = "root";
- adminuser = "root";
+ autoStart = true;
+ privateNetwork = true;
+ hostAddress6 = "fd00::10:1";
+ localAddress6 = "fs00::10:2";
+
+ config = { pkgs, ... }: {
+
+ environment.systemPackages = [ pkgs.htop ];
+
+ imports = [ ./nextcloud-module.nix ];
+
+ services.nextcloud-patched = {
+ enable = true;
+
+ # must be set manually; may not be incremented by more than one at
+ # a time, otherwise nextcloud WILL break
+ package = pkgs.nextcloud21;
+
+ hostName = "cloud2.infra4future.de";
+ config = {
+ dbtype = "pgsql";
+ dbuser = "nextcloud";
+ dbhost = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
+ dbname = "nextcloud";
+ # there's also a adminpassFile option, but for testing this seems
+ # enough (less fiddling with getting the file into a nixos
+ # container for ad-hoc setups)
+ adminpass = "root";
+ adminuser = "root";
+ };
+
+ caching.redis = true;
+
+ # multiple pools may be doable using services.phpfpm.pools,
+ # but i have not tried this yet. The nextcloud module defines a
+ # pool "nextcloud"
+ poolSettings = {
+ pm = "dynamic";
+ "pm.max_children" = "32";
+ "pm.max_requests" = "500";
+ "pm.max_spare_servers" = "4";
+ "pm.min_spare_servers" = "2";
+ "pm.start_servers" = "2";
+ };
+
+ extraOptions = ''
+ 'redis' => array(
+ 'host' => '/run/redis/redis.sock',
+ 'port' => 0,
+ 'dbindex' => 0,
+ 'password' => 'secret',
+ 'timeout' => 1.5,
+ ),
+ '';
+
+ };
+
+ services.redis = {
+ enable = true;
+ unixSocket = "/var/run/redis/redis.sock";
+ };
+
+ services.postgresql = {
+ enable = true;
+ ensureDatabases = [ "nextcloud" ];
+ ensureUsers = [
+ { # by default, postgres has unix sockets enabled, and allows a
+ # system user `nextcloud` to log in without other authentication
+ name = "nextcloud";
+ ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+ }
+ ];
+ };
+
+ # ensure that postgres is running *before* running the setup
+ systemd.services."nextcloud-setup" = {
+ requires = ["postgresql.service"];
+ after = ["postgresql.service"];
+ };
+
+ networking.firewall.allowedTCPPorts = [ 80 443 ];
};
-
- caching.redis = true;
-
- # multiple pools may be doable using services.phpfpm.pools,
- # but i have not tried this yet. The nextcloud module defines a
- # pool "nextcloud"
- poolSettings = {
- pm = "dynamic";
- "pm.max_children" = "32";
- "pm.max_requests" = "500";
- "pm.max_spare_servers" = "4";
- "pm.min_spare_servers" = "2";
- "pm.start_servers" = "2";
- };
-
- };
-
- # TODO: this needs extra stuff in config.php, which right now can't
- # be configured using this module. Perhaps we could fork it?
- services.redis = {
- enable = true;
};
- services.postgresql = {
- enable = true;
- ensureDatabases = [ "nextcloud" ];
- ensureUsers = [
- { # by default, postgres has unix sockets enabled, and allows a
- # system user `nextcloud` to log in without other authentication
- name = "nextcloud";
- ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
- }
- ];
+ services.nginx.virtualHosts."cloud2.infra4future.de" = {
+ locations."/".proxyPass = "http:[${config.containers.nextcloud.localAddress6}]";
+ enableACME = true;
+ forceSSL = true;
};
-
- # ensure that postgres is running *before* running the setup
- systemd.services."nextcloud-setup" = {
- requires = ["postgresql.service"];
- after = ["postgresql.service"];
- };
-
- networking.firewall.allowedTCPPorts = [ 80 443 ];
}
diff --git a/modules/nextcloud.nix b/modules/nextcloud.nix
new file mode 100644
index 0000000..53a8f67
--- /dev/null
+++ b/modules/nextcloud.nix
@@ -0,0 +1,721 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.nextcloud-patched;
+ fpm = config.services.phpfpm.pools.nextcloud;
+
+ phpPackage =
+ let
+ base = pkgs.php74;
+ in
+ base.buildEnv {
+ extensions = { enabled, all }: with all;
+ enabled ++ [
+ apcu redis memcached imagick
+ ];
+ extraConfig = phpOptionsStr;
+ };
+
+ toKeyValue = generators.toKeyValue {
+ mkKeyValue = generators.mkKeyValueDefault {} " = ";
+ };
+
+ phpOptions = {
+ upload_max_filesize = cfg.maxUploadSize;
+ post_max_size = cfg.maxUploadSize;
+ memory_limit = cfg.maxUploadSize;
+ } // cfg.phpOptions
+ // optionalAttrs cfg.caching.apcu {
+ "apc.enable_cli" = "1";
+ };
+ phpOptionsStr = toKeyValue phpOptions;
+
+ occ = pkgs.writeScriptBin "nextcloud-occ" ''
+ #! ${pkgs.runtimeShell}
+ cd ${cfg.package}
+ sudo=exec
+ if [[ "$USER" != nextcloud ]]; then
+ sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
+ fi
+ export NEXTCLOUD_CONFIG_DIR="${cfg.home}/config"
+ $sudo \
+ ${phpPackage}/bin/php \
+ occ $*
+ '';
+
+ inherit (config.system) stateVersion;
+
+in {
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "nextcloud-patched" "nginx" "enable" ] ''
+ The nextcloud module supports `nginx` as reverse-proxy by default and doesn't
+ support other reverse-proxies officially.
+
+ However it's possible to use an alternative reverse-proxy by
+
+ * disabling nginx
+ * setting `listen.owner` & `listen.group` in the phpfpm-pool to a different value
+
+ Further details about this can be found in the `Nextcloud`-section of the NixOS-manual
+ (which can be openend e.g. by running `nixos-help`).
+ '')
+ ];
+
+ options.services.nextcloud-patched = {
+ enable = mkEnableOption "nextcloud";
+ hostName = mkOption {
+ type = types.str;
+ description = "FQDN for the nextcloud instance.";
+ };
+ home = mkOption {
+ type = types.str;
+ default = "/var/lib/nextcloud";
+ description = "Storage path of nextcloud.";
+ };
+ logLevel = mkOption {
+ type = types.ints.between 0 4;
+ default = 2;
+ description = "Log level value between 0 (DEBUG) and 4 (FATAL).";
+ };
+ https = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Use https for generated links.";
+ };
+ package = mkOption {
+ type = types.package;
+ description = "Which package to use for the Nextcloud instance.";
+ relatedPackages = [ "nextcloud18" "nextcloud19" "nextcloud20" "nextcloud21" ];
+ };
+
+ maxUploadSize = mkOption {
+ default = "512M";
+ type = types.str;
+ description = ''
+ Defines the upload limit for files. This changes the relevant options
+ in php.ini and nginx if enabled.
+ '';
+ };
+
+ skeletonDirectory = mkOption {
+ default = "";
+ type = types.str;
+ description = ''
+ The directory where the skeleton files are located. These files will be
+ copied to the data directory of new users. Leave empty to not copy any
+ skeleton files.
+ '';
+ };
+
+ webfinger = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable this option if you plan on using the webfinger plugin.
+ The appropriate nginx rewrite rules will be added to your configuration.
+ '';
+ };
+
+ phpOptions = mkOption {
+ type = types.attrsOf types.str;
+ default = {
+ short_open_tag = "Off";
+ expose_php = "Off";
+ error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
+ display_errors = "stderr";
+ "opcache.enable_cli" = "1";
+ "opcache.interned_strings_buffer" = "8";
+ "opcache.max_accelerated_files" = "10000";
+ "opcache.memory_consumption" = "128";
+ "opcache.revalidate_freq" = "1";
+ "opcache.fast_shutdown" = "1";
+ "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+ catch_workers_output = "yes";
+ };
+ description = ''
+ Options for PHP's php.ini file for nextcloud.
+ '';
+ };
+
+ poolSettings = mkOption {
+ type = with types; attrsOf (oneOf [ str int bool ]);
+ default = {
+ "pm" = "dynamic";
+ "pm.max_children" = "32";
+ "pm.start_servers" = "2";
+ "pm.min_spare_servers" = "2";
+ "pm.max_spare_servers" = "4";
+ "pm.max_requests" = "500";
+ };
+ description = ''
+ Options for nextcloud's PHP pool. See the documentation on php-fpm.conf for details on configuration directives.
+ '';
+ };
+
+ poolConfig = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Options for nextcloud's PHP pool. See the documentation on php-fpm.conf for details on configuration directives.
+ '';
+ };
+
+ config = {
+ dbtype = mkOption {
+ type = types.enum [ "sqlite" "pgsql" "mysql" ];
+ default = "sqlite";
+ description = "Database type.";
+ };
+ dbname = mkOption {
+ type = types.nullOr types.str;
+ default = "nextcloud";
+ description = "Database name.";
+ };
+ dbuser = mkOption {
+ type = types.nullOr types.str;
+ default = "nextcloud";
+ description = "Database user.";
+ };
+ dbpass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Database password. Use dbpassFile to avoid this
+ being world-readable in the /nix/store.
+ '';
+ };
+ dbpassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The full path to a file that contains the database password.
+ '';
+ };
+ dbhost = mkOption {
+ type = types.nullOr types.str;
+ default = "localhost";
+ description = ''
+ Database host.
+
+ Note: for using Unix authentication with PostgreSQL, this should be
+ set to /run/postgresql.
+ '';
+ };
+ dbport = mkOption {
+ type = with types; nullOr (either int str);
+ default = null;
+ description = "Database port.";
+ };
+ dbtableprefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Table prefix in Nextcloud database.";
+ };
+ adminuser = mkOption {
+ type = types.str;
+ default = "root";
+ description = "Admin username.";
+ };
+ adminpass = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Admin password. Use adminpassFile to avoid this
+ being world-readable in the /nix/store.
+ '';
+ };
+ adminpassFile = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The full path to a file that contains the admin's password. Must be
+ readable by user nextcloud.
+ '';
+ };
+
+ extraTrustedDomains = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Trusted domains, from which the nextcloud installation will be
+ acessible. You don't need to add
+ services.nextcloud.hostname here.
+ '';
+ };
+
+ trustedProxies = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Trusted proxies, to provide if the nextcloud installation is being
+ proxied to secure against e.g. spoofing.
+ '';
+ };
+
+ overwriteProtocol = mkOption {
+ type = types.nullOr (types.enum [ "http" "https" ]);
+ default = null;
+ example = "https";
+
+ description = ''
+ Force Nextcloud to always use HTTPS i.e. for link generation. Nextcloud
+ uses the currently used protocol by default, but when behind a reverse-proxy,
+ it may use http for everything although Nextcloud
+ may be served via HTTPS.
+ '';
+ };
+
+ defaultPhoneRegion = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "DE";
+ description = ''
+
+ This option exists since Nextcloud 21! If older versions are used,
+ this will throw an eval-error!
+
+
+ ISO 3611-1
+ country codes for automatic phone-number detection without a country code.
+
+ With e.g. DE set, the +49 can be omitted for
+ phone-numbers.
+ '';
+ };
+ };
+
+ caching = {
+ apcu = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to load the APCu module into PHP.
+ '';
+ };
+ redis = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to load the Redis module into PHP.
+ You still need to enable Redis in your config.php.
+ See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+ '';
+ };
+ memcached = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to load the Memcached module into PHP.
+ You still need to enable Memcached in your config.php.
+ See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+ '';
+ };
+ };
+ autoUpdateApps = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Run regular auto update of all apps installed from the nextcloud app store.
+ '';
+ };
+ startAt = mkOption {
+ type = with types; either str (listOf str);
+ default = "05:00:00";
+ example = "Sun 14:00:00";
+ description = ''
+ When to run the update. See `systemd.services.<name>.startAt`.
+ '';
+ };
+ };
+ occ = mkOption {
+ type = types.package;
+ default = occ;
+ internal = true;
+ description = ''
+ The nextcloud-occ program preconfigured to target this Nextcloud instance.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Extra options which should be appended to nextcloud's config.php file
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ { assertions = let acfg = cfg.config; in [
+ { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
+ message = "Please specify no more than one of dbpass or dbpassFile";
+ }
+ { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
+ && !(acfg.adminpass != null && acfg.adminpassFile != null));
+ message = "Please specify exactly one of adminpass or adminpassFile";
+ }
+ { assertion = versionOlder cfg.package.version "21" -> cfg.config.defaultPhoneRegion == null;
+ message = "The `defaultPhoneRegion'-setting is only supported for Nextcloud >=21!";
+ }
+ ];
+
+ warnings = []
+ ++ (optional (cfg.poolConfig != null) ''
+ Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
+ Please migrate your configuration to config.services.nextcloud.poolSettings.
+ '')
+ ++ (optional (versionOlder cfg.package.version "18") ''
+ A legacy Nextcloud install (from before NixOS 20.03) may be installed.
+
+ You're currently deploying an older version of Nextcloud. This may be needed
+ since Nextcloud doesn't allow major version upgrades that skip multiple
+ versions (i.e. an upgrade from 16 is possible to 17, but not 16 to 18).
+
+ It is assumed that Nextcloud will be upgraded from version 16 to 17.
+
+ * If this is a fresh install, there will be no upgrade to do now.
+
+ * If this server already had Nextcloud installed, first deploy this to your
+ server, and wait until the upgrade to 17 is finished.
+
+ Then, set `services.nextcloud.package` to `pkgs.nextcloud18` to upgrade to
+ Nextcloud version 18. Please note that Nextcloud 19 is already out and it's
+ recommended to upgrade to nextcloud19 after that.
+ '')
+ ++ (optional (versionOlder cfg.package.version "19") ''
+ A legacy Nextcloud install (from before NixOS 20.09) may be installed.
+
+ If/After nextcloud18 is installed successfully, you can safely upgrade to
+ nextcloud19. If not, please upgrade to nextcloud18 first since Nextcloud doesn't
+ support upgrades that skip multiple versions (i.e. an upgrade from 17 to 19 isn't
+ possible, but an upgrade from 18 to 19).
+ '')
+ ++ (optional (versionOlder cfg.package.version "21") ''
+ The latest Nextcloud release is v21 which can be installed by setting
+ `services.nextcloud.package` to `pkgs.nextcloud21`. Please note that if you're
+ on `pkgs.nextcloud19`, you'll have to install `pkgs.nextcloud20` first.
+ '');
+
+ services.nextcloud-patched.package = with pkgs;
+ mkDefault (
+ if pkgs ? nextcloud
+ then throw ''
+ The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default
+ nextcloud defined in an overlay, please set `services.nextcloud.package` to
+ `pkgs.nextcloud`.
+ ''
+ else if versionOlder stateVersion "20.03" then nextcloud17
+ else if versionOlder stateVersion "20.09" then nextcloud18
+ else nextcloud19
+ );
+ }
+
+ { systemd.timers.nextcloud-cron = {
+ wantedBy = [ "timers.target" ];
+ timerConfig.OnBootSec = "5m";
+ timerConfig.OnUnitActiveSec = "15m";
+ timerConfig.Unit = "nextcloud-cron.service";
+ };
+
+ systemd.services = {
+ # When upgrading the Nextcloud package, Nextcloud can report errors such as
+ # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly"
+ # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround).
+ phpfpm-nextcloud.restartTriggers = [ cfg.package ];
+
+ nextcloud-setup = let
+ c = cfg.config;
+ writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
+ overrideConfig = pkgs.writeText "nextcloud-config.php" ''
+ [
+ [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
+ [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
+ ],
+ 'datadirectory' => '${cfg.home}/data',
+ 'skeletondirectory' => '${cfg.skeletonDirectory}',
+ ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+ 'log_type' => 'syslog',
+ 'log_level' => '${builtins.toString cfg.logLevel}',
+ ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"}
+ ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
+ ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
+ ${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.dbpass != null) "'dbpassword' => '${c.dbpass}',"}
+ ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"}
+ 'dbtype' => '${c.dbtype}',
+ 'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
+ 'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
+ ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"}
+ ${optionalString (cfg.extraOptions != "") cfg.extraOptions}
+ ];
+ '';
+ occInstallCmd = let
+ dbpass = if c.dbpassFile != null
+ then ''"$(<"${toString c.dbpassFile}")"''
+ else if c.dbpass != null
+ then ''"${toString c.dbpass}"''
+ else ''""'';
+ adminpass = if c.adminpassFile != null
+ then ''"$(<"${toString c.adminpassFile}")"''
+ else ''"${toString c.adminpass}"'';
+ installFlags = concatStringsSep " \\\n "
+ (mapAttrsToList (k: v: "${k} ${toString v}") {
+ "--database" = ''"${c.dbtype}"'';
+ # The following attributes are optional depending on the type of
+ # database. Those that evaluate to null on the left hand side
+ # will be omitted.
+ ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
+ ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
+ ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
+ ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
+ "--database-pass" = dbpass;
+ ${if c.dbtableprefix != null
+ then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
+ "--admin-user" = ''"${c.adminuser}"'';
+ "--admin-pass" = adminpass;
+ "--data-dir" = ''"${cfg.home}/data"'';
+ });
+ in ''
+ ${occ}/bin/nextcloud-occ maintenance:install \
+ ${installFlags}
+ '';
+ occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
+ (i: v: ''
+ ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
+ ${toString i} --value="${toString v}"
+ '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+
+ in {
+ wantedBy = [ "multi-user.target" ];
+ before = [ "phpfpm-nextcloud.service" ];
+ path = [ occ ];
+ script = ''
+ chmod og+x ${cfg.home}
+
+ ${optionalString (c.dbpassFile != null) ''
+ if [ ! -r "${c.dbpassFile}" ]; then
+ echo "dbpassFile ${c.dbpassFile} is not readable by nextcloud:nextcloud! Aborting..."
+ exit 1
+ fi
+ if [ -z "$(<${c.dbpassFile})" ]; then
+ echo "dbpassFile ${c.dbpassFile} is empty!"
+ exit 1
+ fi
+ ''}
+ ${optionalString (c.adminpassFile != null) ''
+ if [ ! -r "${c.adminpassFile}" ]; then
+ echo "adminpassFile ${c.adminpassFile} is not readable by nextcloud:nextcloud! Aborting..."
+ exit 1
+ fi
+ if [ -z "$(<${c.adminpassFile})" ]; then
+ echo "adminpassFile ${c.adminpassFile} is empty!"
+ exit 1
+ fi
+ ''}
+
+ ln -sf ${cfg.package}/apps ${cfg.home}/
+
+ # create nextcloud directories.
+ # if the directories exist already with wrong permissions, we fix that
+ for dir in ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps; do
+ if [ ! -e $dir ]; then
+ install -o nextcloud -g nextcloud -d $dir
+ elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then
+ chgrp -R nextcloud $dir
+ fi
+ done
+
+ ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
+
+ # Do not install if already installed
+ if [[ ! -e ${cfg.home}/config/config.php ]]; then
+ ${occInstallCmd}
+ fi
+
+ ${occ}/bin/nextcloud-occ upgrade
+
+ ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
+ ${occSetTrustedDomainsCmd}
+ '';
+ serviceConfig.Type = "oneshot";
+ serviceConfig.User = "nextcloud";
+ };
+ nextcloud-cron = {
+ environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+ serviceConfig.Type = "oneshot";
+ serviceConfig.User = "nextcloud";
+ serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php";
+ };
+ nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
+ serviceConfig.Type = "oneshot";
+ serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
+ serviceConfig.User = "nextcloud";
+ startAt = cfg.autoUpdateApps.startAt;
+ };
+ };
+
+ services.phpfpm = {
+ pools.nextcloud = {
+ user = "nextcloud";
+ group = "nextcloud";
+ phpOptions = phpOptionsStr;
+ phpPackage = phpPackage;
+ phpEnv = {
+ NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+ PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
+ };
+ settings = mapAttrs (name: mkDefault) {
+ "listen.owner" = config.services.nginx.user;
+ "listen.group" = config.services.nginx.group;
+ } // cfg.poolSettings;
+ extraConfig = cfg.poolConfig;
+ };
+ };
+
+ users.users.nextcloud = {
+ home = "${cfg.home}";
+ group = "nextcloud";
+ createHome = true;
+ };
+ users.groups.nextcloud.members = [ "nextcloud" config.services.nginx.user ];
+
+ environment.systemPackages = [ occ ];
+
+ services.nginx.enable = mkDefault true;
+
+ services.nginx.virtualHosts.${cfg.hostName} = let
+ major = toInt (versions.major cfg.package.version);
+ in {
+ root = cfg.package;
+ locations = {
+ "= /robots.txt" = {
+ priority = 100;
+ extraConfig = ''
+ allow all;
+ log_not_found off;
+ access_log off;
+ '';
+ };
+ "= /" = {
+ priority = 100;
+ extraConfig = ''
+ if ( $http_user_agent ~ ^DavClnt ) {
+ return 302 /remote.php/webdav/$is_args$args;
+ }
+ '';
+ };
+ "/" = {
+ priority = 900;
+ extraConfig = "rewrite ^ /index.php;";
+ };
+ "~ ^/store-apps" = {
+ priority = 201;
+ extraConfig = "root ${cfg.home};";
+ };
+ "^~ /.well-known" = {
+ priority = 210;
+ extraConfig = ''
+ absolute_redirect off;
+ location = /.well-known/carddav {
+ return 301 /remote.php/dav;
+ }
+ location = /.well-known/caldav {
+ return 301 /remote.php/dav;
+ }
+ location ~ ^/\.well-known/(?!acme-challenge|pki-validation) {
+ return 301 /index.php$request_uri;
+ }
+ try_files $uri $uri/ =404;
+ '';
+ };
+ "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = ''
+ return 404;
+ '';
+ "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = ''
+ return 404;
+ '';
+ "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = {
+ priority = 500;
+ extraConfig = ''
+ include ${config.services.nginx.package}/conf/fastcgi.conf;
+ fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
+ set $path_info $fastcgi_path_info;
+ try_files $fastcgi_script_name =404;
+ fastcgi_param PATH_INFO $path_info;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
+ fastcgi_param modHeadersAvailable true;
+ fastcgi_param front_controller_active true;
+ fastcgi_pass unix:${fpm.socket};
+ fastcgi_intercept_errors on;
+ fastcgi_request_buffering off;
+ fastcgi_read_timeout 120s;
+ '';
+ };
+ "~ \\.(?:css|js|woff2?|svg|gif|map)$".extraConfig = ''
+ try_files $uri /index.php$request_uri;
+ expires 6M;
+ access_log off;
+ '';
+ "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
+ try_files $uri/ =404;
+ index index.php;
+ '';
+ "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = ''
+ try_files $uri /index.php$request_uri;
+ access_log off;
+ '';
+ };
+ extraConfig = ''
+ index index.php index.html /index.php$request_uri;
+ expires 1m;
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+ add_header X-Robots-Tag none;
+ add_header X-Download-Options noopen;
+ add_header X-Permitted-Cross-Domain-Policies none;
+ add_header X-Frame-Options sameorigin;
+ add_header Referrer-Policy no-referrer;
+ add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+ client_max_body_size ${cfg.maxUploadSize};
+ fastcgi_buffers 64 4K;
+ fastcgi_hide_header X-Powered-By;
+ gzip on;
+ gzip_vary on;
+ gzip_comp_level 4;
+ gzip_min_length 256;
+ gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+ gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+ ${optionalString cfg.webfinger ''
+ rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+ rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
+ ''}
+ '';
+ };
+ }
+ ]);
+
+ meta.doc = ./nextcloud.xml;
+}