{ lib, pkgs, config, ... }: let scfg = config.secrets.fullPaths; cfg = config.myServices.tools.cloud.farm; apacheUser = config.services.websites.env.production.user; apacheGroup = config.services.websites.env.production.group; additionalConfs = icfg: lib.attrsets.mapAttrs (n: v: pkgs.writeText "${n}.json" (builtins.toJSON v)) icfg.rootDir.otherConfig; overrideConfig = icfg: pkgs.writeText "override.config.php" '' AcceptPathInfo On DirectoryIndex index.php Options FollowSymlinks Require all granted AllowOverride all Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains; preload" CGIPassAuth on SetHandler "proxy:unix:${config.services.phpfpm.pools.${icfg.phpPoolName}.socket}|fcgi://localhost" ''; in { options.myServices.tools.cloud.farm = { instances = lib.mkOption { description = "Instances names for the nextcloud Farm"; default = {}; type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: { options = { nextcloud = lib.mkOption { description = "Nextcloud version to use"; default = pkgs.webapps-nextcloud_27; type = lib.types.package; }; apps = lib.mkOption { description = "Applications to use"; default = a: []; #type = functionTo (listOf packages) type = lib.types.unspecified; }; config = lib.mkOption { description = "Config keys"; default = {}; type = lib.types.attrsOf lib.types.unspecified; }; secretsPath = lib.mkOption { description = "Path in secrets to nextcloud config file"; default = "websites/${name}/nextcloud"; type = lib.types.str; }; configOverride = lib.mkOption { description = "Path to config override"; readOnly = true; default = scfg."${config.secretsPath}"; type = lib.types.path; }; phpPackage = lib.mkOption { description = "PHP package to use"; default = pkgs.php81; type = lib.types.package; apply = v: (v.withExtensions({ enabled, all }: enabled ++ [ all.redis all.apcu all.opcache all.imagick all.sysvsem ])).override { extraConfig = '' apc.enable_cli = 1 apc.enabled = 1 ''; }; }; rootDir = lib.mkOption { description = "Instance root dirs"; readOnly = true; type = lib.types.package; default = config.nextcloud.withApps config.apps; }; phpPoolName = lib.mkOption { description = "Php pool name for the instance"; readOnly = true; type = lib.types.str; default = "nextcloud_farm_" + name; }; phpBaseDir = lib.mkOption { description = "Php basedir for the instance"; readOnly = true; type = lib.types.str; default = builtins.concatStringsSep ":" ( [ config.rootDir config.varDir ] ++ config.rootDir.apps ++ [ config.configOverride (overrideConfig config) ] ++ (builtins.attrValues (additionalConfs config)) ); }; varDir = lib.mkOption { description = "Instance var dir"; type = lib.types.path; default = "/var/lib/nextcloud_farm/${name}"; }; vhost = lib.mkOption { description = "Instance vhost config"; readOnly = true; type = lib.types.str; default = toVhost config; }; }; })); }; }; config = lib.mkIf (builtins.length (builtins.attrNames cfg.instances) > 0) { systemd.services = lib.mapAttrs' (k: v: lib.nameValuePair ("phpfpm-" + v.phpPoolName) { after = lib.mkAfter [ "postgresql.service" ]; wants = [ "postgresql.service" ]; serviceConfig.ExecStartPre = "+${pkgs.writeScript "phpfpm-nextcloud-${k}-pre-start" '' #!${pkgs.stdenv.shell} install -m 0755 -o wwwrun -g wwwrun -d ${v.varDir} -d ${v.varDir}/config ${builtins.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: f: "ln -sf ${f} ${v.varDir}/config/${n}.json" ) (additionalConfs v))} ln -sf ${overrideConfig v} ${v.varDir}/config/override.config.php ''}"; }) cfg.instances; services.phpfpm.pools = lib.mapAttrs' (k: v: lib.nameValuePair v.phpPoolName { user = apacheUser; group = apacheGroup; settings = { "listen.owner" = apacheUser; "listen.group" = apacheGroup; "pm" = "dynamic"; "pm.max_children" = "60"; "pm.start_servers" = "3"; "pm.min_spare_servers" = "3"; "pm.max_spare_servers" = "3"; "pm.process_idle_timeout" = "60"; "php_admin_value[output_buffering]" = "0"; "php_admin_value[max_execution_time]" = "1800"; "php_admin_value[zend_extension]" = "opcache"; "php_value[apc.enable_cli]" = "1"; "php_value[apc.enabled]" = "1"; #already enabled by default? #"php_value[opcache.enable]" = "1"; "php_value[opcache.enable_cli]" = "1"; "php_value[opcache.interned_strings_buffer]" = "32"; "php_value[opcache.max_accelerated_files]" = "10000"; "php_value[opcache.memory_consumption]" = "128"; "php_value[opcache.save_comments]" = "1"; "php_value[opcache.revalidate_freq]" = "1"; "php_admin_value[memory_limit]" = "512M"; "php_admin_value[open_basedir]" = "/run/wrappers/bin/sendmail:${v.phpBaseDir}:/proc/cpuinfo:/proc/meminfo:/dev/urandom:/proc/self/fd:/tmp"; "php_admin_value[session.save_handler]" = "redis"; "php_admin_value[session.save_path]" = "'unix:///run/redis-php-sessions/redis.sock?persistent=1&prefix=Tools:NextcloudFarm:${k}:'"; }; phpPackage = v.phpPackage; }) cfg.instances; environment.systemPackages = let toOcc = name: icfg: pkgs.writeScriptBin "nextcloud-occ-${name}" '' #! ${pkgs.stdenv.shell} cd ${icfg.rootDir} NEXTCLOUD_CONFIG_DIR="${icfg.varDir}/config" \ exec \ sudo -E -u wwwrun ${icfg.phpPackage}/bin/php \ -d memory_limit=512M \ -c ${icfg.phpPackage}/etc/php.ini \ occ $* ''; in lib.mapAttrsToList toOcc cfg.instances; services.cron = { enable = true; systemCronJobs = let toScript = name: icfg: pkgs.writeScriptBin "nextcloud-cron" '' #! ${pkgs.stdenv.shell} export LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive export PATH=/run/wrappers/bin:$PATH export NEXTCLOUD_CONFIG_DIR="${icfg.varDir}/config" ${icfg.phpPackage}/bin/php -c ${icfg.phpPackage}/etc/php.ini -d memory_limit=512M -f ${icfg.rootDir}/cron.php ''; toLine = name: icfg: '' */5 * * * * wwwrun ${toScript name icfg}/bin/nextcloud-cron ''; in lib.mapAttrsToList toLine cfg.instances; }; secrets.keys = lib.mapAttrs' (name: v: lib.nameValuePair "${v.secretsPath}" { user = "wwwrun"; group = "wwwrun"; permissions = "0600"; # Be careful when editing that: config from this file takes # precedence over the regular one, but if a key got removed, it my # still exist in the default config file text = builtins.toJSON ( { "datadirectory" = if name == "immae" then v.varDir else "${v.varDir}/data"; "appstoreenabled" = false; "integrity.check.disabled" = true; "updater.release.channel" = "stable"; "upgrade.disable-web" = true; "memcache.local" = "\\OC\\Memcache\\APCu"; "htaccess.RewriteBase" = "/"; "loglevel" = 2; "logtimezone" = "Europe/Paris"; "default_phone_region" = "FR"; "skeletondirectory" = ""; "theme" = ""; } // v.config); }) cfg.instances; }; }