From e2d9f841095db837637d625b8828a0619b7e19af Mon Sep 17 00:00:00 2001 From: stuebinm Date: Thu, 25 Mar 2021 18:28:31 +0100 Subject: [PATCH] nextcloud: fork module from nixpkgs and add extraOptions - nextcloud now has its own container - redis caching appears to work - this now uses nextcloud 21 --- hosts/hainich/services/nextcloud.nix | 153 +++--- modules/nextcloud.nix | 721 +++++++++++++++++++++++++++ 2 files changed, 811 insertions(+), 63 deletions(-) create mode 100644 modules/nextcloud.nix 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; +}