commit bd4d8d3e3a1f78be0b9c4a236c1388871cbc1ba2 Author: niten Date: Thu Aug 31 14:48:31 2023 -0700 Initial commit diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d526897 --- /dev/null +++ b/flake.lock @@ -0,0 +1,233 @@ +{ + "nodes": { + "arion": { + "inputs": { + "flake-parts": "flake-parts", + "haskell-flake": "haskell-flake", + "hercules-ci-effects": "hercules-ci-effects", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1692787336, + "narHash": "sha256-WabgeYsUiMRbpb1bCT3oY6GJEciZQIf3tYD8RQAUf2c=", + "owner": "hercules-ci", + "repo": "arion", + "rev": "28902d348807c494115177595f812a3e54cc913b", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "arion", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "arion", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1675933616, + "narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "47478a4a003e745402acf63be7f9a092d51b83d7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1688466019, + "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": [ + "arion", + "hercules-ci-effects", + "hercules-ci-agent", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688466019, + "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "haskell-flake": { + "locked": { + "lastModified": 1675296942, + "narHash": "sha256-u1X1sblozi5qYEcLp1hxcyo8FfDHnRUVX3dJ/tW19jY=", + "owner": "srid", + "repo": "haskell-flake", + "rev": "c2cafce9d57bfca41794dc3b99c593155006c71e", + "type": "github" + }, + "original": { + "owner": "srid", + "ref": "0.1.0", + "repo": "haskell-flake", + "type": "github" + } + }, + "haskell-flake_2": { + "locked": { + "lastModified": 1684780604, + "narHash": "sha256-2uMZsewmRn7rRtAnnQNw1lj0uZBMh4m6Cs/7dV5YF08=", + "owner": "srid", + "repo": "haskell-flake", + "rev": "74210fa80a49f1b6f67223debdbf1494596ff9f2", + "type": "github" + }, + "original": { + "owner": "srid", + "ref": "0.3.0", + "repo": "haskell-flake", + "type": "github" + } + }, + "hercules-ci-agent": { + "inputs": { + "flake-parts": "flake-parts_3", + "haskell-flake": "haskell-flake_2", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1688568579, + "narHash": "sha256-ON0M56wtY/TIIGPkXDlJboAmuYwc73Hi8X9iJGtxOhM=", + "owner": "hercules-ci", + "repo": "hercules-ci-agent", + "rev": "367dd8cd649b57009a6502e878005a1e54ad78c5", + "type": "github" + }, + "original": { + "id": "hercules-ci-agent", + "type": "indirect" + } + }, + "hercules-ci-effects": { + "inputs": { + "flake-parts": "flake-parts_2", + "hercules-ci-agent": "hercules-ci-agent", + "nixpkgs": [ + "arion", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1689397210, + "narHash": "sha256-fVxZnqxMbsDkB4GzGAs/B41K0wt/e+B/fLxmTFF/S20=", + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "rev": "0a63bfa3f00a3775ea3a6722b247880f1ffe91ce", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1688322751, + "narHash": "sha256-eW62dC5f33oKZL7VWlomttbUnOTHrAbte9yNUNW8rbk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0fbe93c5a7cac99f90b60bdf5f149383daaa615f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1688049487, + "narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1676300157, + "narHash": "sha256-1HjRzfp6LOLfcj/HJHdVKWAkX9QRAouoh6AjzJiIerU=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "545c7a31e5dedea4a6d372712a18e00ce097d462", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1693341273, + "narHash": "sha256-wrsPjsIx2767909MPGhSIOmkpGELM9eufqLQOPxmZQg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2ab91c8d65c00fd22a441c69bbf1bc9b420d5ea1", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "arion": "arion", + "nixpkgs": "nixpkgs_3" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..8af844b --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "Nextcloud running in a container"; + + inputs = { + nixpkgs.url = "nixpkgs/nixos-23.05"; + arion.url = "github:hercules-ci/arion"; + }; + + outputs = { self, nixpkgs, arion, ... }: { + nixosModules = rec { + default = nextcloudContainer; + nextcloudContainer = { ... }: { + imports = [ arion.nixosModules.arion ./nextcloud-container.nix ]; + }; + }; + }; +} diff --git a/nextcloud-container.nix b/nextcloud-container.nix new file mode 100644 index 0000000..c22ec8d --- /dev/null +++ b/nextcloud-container.nix @@ -0,0 +1,254 @@ +{ config, lib, pkgs, ... }@toplevel: + +with lib; +let + cfg = config.services.nextcloudContainer; + + hostname = config.instance.hostname; + + mkEnvFile = envVars: + let + envLines = + mapAttrsToList (var: val: ''${var}="${toString val}"'') envVars; + in pkgs.writeText "envFile" (concatStringsSep "\n" envLines); + + mkUserMap = uid: "${toString uid}:${toString uid}"; + + postgresPasswdFile = + pkgs.lib.passwd.stablerandom-passwd-file "nextcloud-postgres-passwd" + config.instance.build-seed; + +in { + options.services.nextcloudContainer = with types; { + enable = mkEnableOption "Enable Nextcloud running in an Arion container."; + + state-directory = mkOption { + type = str; + description = "Directory at which to store server state data."; + }; + + images = { + nextcloud = mkOption { type = str; }; + postgres = mkOption { type = str; }; + nginx = mkOption { type = str; }; + }; + + uids = { + nextcloud = mkOption { + type = int; + default = 740; + }; + postgres = mkOption { + type = int; + default = 741; + }; + }; + + port = mkOption { + type = port; + description = "Intenal port on which to listen for requests."; + default = 6093; + }; + + timezone = mkOption { + type = str; + default = "America/Winnipeg"; + }; + }; + + config = mkIf cfg.enable { + systemd = { + tmpfiles.rules = [ + "d ${cfg.state-directory}/nextcloud 0700 nextcloud root - -" + "d ${cfg.state-directory}/data 0700 nextcloud root - -" + "d ${cfg.state-directory}/postgres 0700 nextcloud-postgres root - -" + ]; + services.arion-nextcloud = { + after = [ "network-online.target" ]; + requires = [ "network-online.target" ]; + }; + }; + + users.users = { + nextcloud = { + isSystemUser = true; + group = "nextcloud"; + uid = cfg.uids.nextcloud; + }; + nextcloud-postgres = { + isSystemUser = true; + group = "nextcloud"; + uid = cfg.uids.postgres; + }; + }; + + fudo.secrets.host-secrets."${hostname}" = { + nextcloudEnv = { + source-file = mkEnvFile { + POSTGRES_HOST = "postgres"; + POSTGRES_DB = "nextcloud"; + POSTGRES_USER = "nextcloud"; + POSTGRES_PASSWORD = readFile postgresPasswdFile; + TZ = cfg.timezone; + }; + target-file = "/run/nextcloud/nextcloud.env"; + }; + nextcloudPostgresEnv = { + source-file = mkEnvFile { + POSTGRES_DB = "nextcloud"; + POSTGRES_USER = "nextcloud"; + POSTGRES_PASSWORD = readFile postgresPasswdFile; + }; + target-file = "/run/nextcloud/postgres.env"; + }; + }; + + virtualisation.ario.projects.nextcloud.settings = let + image = { ... }: { + project.name = "nextcloud"; + services = { + nextcloud.service = { + image = cfg.images.nextcloud; + restart = "always"; + links = [ "mariadb" ]; + env_file = [ hostSecrets.nextcloudEnv.target-file ]; + volumes = [ + "${cfg.state-directory}/nextcloud:/var/www/html" + "${cfg.state-directory}/data:/data" + ]; + user = mkUserMap cfg.uids.nextcloud; + depends_on = [ "postgres" ]; + }; + postgres.service = { + image = cfg.images.postgres; + restart = "always"; + command = "-c 'max_connections=300'"; + env_file = [ hostSecrets.nextcloudPostgresEnv.target-file ]; + volumes = + [ "${cfg.state-directory}/postgres:/var/lib/postgresql/data" ]; + healthcheck = { + test = [ "CMD" "pg_isready" "-U" "authentik" "-d" "authentik" ]; + start_period = "20s"; + interval = "30s"; + timeout = "3s"; + retries = 5; + }; + user = mkUserMap cfg.uids.postgres; + }; + proxy = { pkgs, lib, ... }: { + nixos = { + useSystemd = true; + configuration = { + boot.tmpOnTmpfs = true; + services.nginx = { + enable = true; + recommendedZstdSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + upstreams.php-handler.extraConfig = "server nextcloud:9000;"; + virtualHosts."localhost" = { + listen = [{ port = 80; }]; + extraConfig = '' + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + fastcgi_hide_header X-Powered-By; + client_max_body_size 10G; + fastcgi_buffers 64 4K; + ''; + locations = { + "/robots.txt".extraConfig = '' + allow all; + log_not_found off; + access_log off; + ''; + "/.well-known/carddav" = { + return = + "301 $scheme://$host:$server_port/remote.hph/dav"; + }; + "/.well-known/caldav" = { + return = + "301 $scheme://$host:$server_port/remote.hph/dav"; + }; + "/" = { extraConfig = "rewrite ^ /index.php"; }; + "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/".extraConfig = + "deny all;"; + "~ ^/(?:.|autotest|occ|issue|indie|db_|console)".extraConfig = + "deny all;"; + "~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|oc[ms]-provider/.+).php(?:$|/)".extaConfig = + '' + fastcgi_split_path_info ^(.+?\.php)(\/.*|)$; + set $path_info $fastcgi_path_info; + try_files $fastcgi_script_name =404; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $path_info; + # fastcgi_param HTTPS on; + + # Avoid sending the security headers twice + fastcgi_param modHeadersAvailable true; + + # Enable pretty urls + fastcgi_param front_controller_active true; + fastcgi_pass php-handler; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + ''; + "~ ^/(?:updater|oc[ms]-provider)(?:$|/)" = { + index = "index.php"; + tryFiles = "$uri/ =404"; + }; + + "~ .(?:css|js|woff2?|svg|gif|map)$" = { + tryFiles = "$uri /index.php$request_uri"; + extraConfig = '' + add_header Cache-Control "public, max-age=15778463"; + add_header Referrer-Policy "no-referrer" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Download-Options "noopen" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Permitted-Cross-Domain-Policies "none" always; + add_header X-Robots-Tag "none" always; + add_header X-XSS-Protection "1; mode=block" always; + access_log off; + ''; + }; + "~ .(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$" = { + tryFiles = "$uri /index.php$request_uri"; + extraConfig = "access_log off;"; + }; + }; + }; + }; + }; + nssModules = lib.mkForce [ ]; + systemd.services.nginx.serviceConfig.AmbientCapabilities = + lib.mkForce [ "CAP_NET_BIND_SERVICE" ]; + }; + service = { + useHostStore = true; + ports = [ "${toString cfg.port}:80" ]; + links = [ "nextcloud" ]; + healthcheck = { + test = [ + '' + curl -sSf 'http://localhost/status.php' | grep '"installed":true' | grep '"maintenance":false' | grep '"needsDbUpgrade":false' || exit 1'' + ]; + start_period = "20s"; + interval = "30s"; + timeout = "3s"; + retries = 5; + }; + depends_on = [ "nextcloud" ]; + }; + }; + }; + }; + in { imports = [ image ]; }; + }; +}