{ lib, config, pkgs, namespace, ... }: let name = "nextcloud"; cfg = config.${namespace}.services.${name}; net = lib.${namespace}.network; nextcloudConfig = lib.${namespace}.mkModule { inherit config name; serviceName = "nextcloud"; description = "Nextcloud - Secure file sync and sharing platform"; options = { }; moduleConfig = { # Setup the native NixOS Nextcloud service services = { nextcloud = { enable = true; package = pkgs.nextcloud33; hostName = "cloud.mjallen.dev"; home = "${cfg.dataDir}/nextcloud"; datadir = "${cfg.dataDir}/nextcloud"; configureRedis = true; enableImagemagick = true; appstoreEnable = true; # extraApps = with pkgs.${namespace}; { # richdocumentscode = nextcloud-code-server; # # richdocuments = nextcloud-richdocuments; # }; # Use PostgreSQL for database config = { dbtype = "pgsql"; dbname = "nextcloud"; dbuser = "nextcloud"; dbhost = "/run/postgresql"; # Socket directory # dbpassFile = config.sops.secrets."jallen-nas/nextcloud/dbpassword".path; adminuser = "mjallen"; adminpassFile = config.sops.secrets."matt_password".path; }; # PHP settings phpOptions = lib.mkOverride 90 { memory_limit = "512M"; upload_max_filesize = "10G"; post_max_size = "10G"; output_buffering = "0"; "opcache.interned_strings_buffer" = "16"; "opcache.max_accelerated_files" = "10000"; "opcache.memory_consumption" = "128"; "opcache.save_comments" = "1"; "opcache.revalidate_freq" = "1"; }; # Configure caching for better performance caching = { apcu = true; redis = true; memcached = false; }; # Auto-update apps autoUpdateApps = { enable = false; startAt = "05:00:00"; }; # Configure HTTPS if enabled https = false; settings = { installed = true; auth.bruteforce.protection.enabled = false; user_oidc = { auto_provision = false; }; overwrite.cli.url = "https://cloud.mjallen.dev"; overwriteprotocol = "https"; overwritehost = "cloud.mjallen.dev"; log_type = "file"; default_phone_region = "US"; trusted_proxies = [ net.hosts.nas.lan "127.0.0.1" "::1" ]; trusted_domains = [ "cloud.mjallen.dev" "${net.hosts.nas.lan}:${toString cfg.port}" ]; enabledPreviewProviders = [ "OC\\Preview\\PNG" "OC\\Preview\\JPEG" "OC\\Preview\\GIF" "OC\\Preview\\BMP" "OC\\Preview\\XBitmap" "OC\\Preview\\Krita" "OC\\Preview\\WebP" "OC\\Preview\\MarkDown" "OC\\Preview\\TXT" "OC\\Preview\\OpenDocument" ]; }; }; nginx = { enable = true; group = "jallen-nas"; virtualHosts.${config.services.nextcloud.hostName} = { listen = [ { inherit (cfg) port; addr = "0.0.0.0"; ssl = false; } ]; }; }; }; users = { users = { nextcloud = { isSystemUser = lib.mkForce true; isNormalUser = lib.mkForce false; }; }; groups = { nextcloud = { }; }; }; # Ensure nextcloud services start after PostgreSQL is ready. # The upstream NixOS module only adds this ordering when services.postgresql.enable # is true in the same config, but here PostgreSQL is managed separately. systemd = { services = { # Override the empty systemd service created by mkModule. # The native NixOS nextcloud module doesn't create a persistent "nextcloud.service" # (it uses PHP-FPM pools and cron instead), so we clear this to avoid the error: # "Service has no ExecStart=, ExecStop=, or SuccessAction=. Refusing." nextcloud = lib.mkForce { }; nextcloud-setup = { after = [ "postgresql.service" ]; requires = [ "postgresql.service" ]; serviceConfig = let # Extract the override.config.php store-path from the already-evaluated # tmpfiles rules list at Nix eval time, so we never have to parse files at # runtime. The upstream module emits exactly one rule of the form: # "L+ - - - - " overrideLine = lib.findFirst ( r: lib.hasInfix "override.config.php" r ) null config.systemd.tmpfiles.rules; overrideStorePath = if overrideLine != null then lib.last (lib.splitString " " overrideLine) else null; in lib.mkIf (overrideStorePath != null) { # systemd-tmpfiles refuses to create the override.config.php symlink because # /media/nas/main is owned by nix-apps (not root/nextcloud), triggering an # "unsafe path transition" error. Work around this by creating the symlink # directly as root (the '+' prefix) before the setup script's ownership check. # The target store path is resolved at Nix eval time so it is always current. ExecStartPre = [ ( "+" + pkgs.writeShellScript "nextcloud-fix-override-config" '' dest="${cfg.dataDir}/nextcloud/config/override.config.php" echo "Creating symlink: $dest -> ${overrideStorePath}" ${pkgs.coreutils}/bin/ln -sf "${overrideStorePath}" "$dest" '' ) ]; }; }; nextcloud-update-db = { after = [ "postgresql.service" ]; requires = [ "postgresql.service" ]; }; }; }; }; }; in { imports = [ nextcloudConfig ]; }