diff options
| author | Jakub Stachurski <jakub@wilkuu.xyz> | 2026-05-29 21:15:44 +0200 |
|---|---|---|
| committer | Jakub Stachurski <jakub@wilkuu.xyz> | 2026-05-29 21:15:44 +0200 |
| commit | 604b8e1850b19bf5acd2042b138dcd5a17698b65 (patch) | |
| tree | 8a1e50c05999d8bf28f0f38146a9e25c60b55f92 | |
| parent | 2e300b08ee2697656a76b4c95a4c66039510a239 (diff) | |
Move stalwart 0.16 stuff into a separate flake
| -rw-r--r-- | flake.lock | 24 | ||||
| -rw-r--r-- | flake.nix | 68 | ||||
| -rw-r--r-- | packages/stalwart-cli/package.nix | 52 | ||||
| -rw-r--r-- | packages/stalwart/package.nix | 211 | ||||
| -rw-r--r-- | packages/stalwart/spam-filter.nix | 42 | ||||
| -rw-r--r-- | packages/stalwart/webadmin.nix | 77 | ||||
| -rw-r--r-- | services/mail2.nix | 291 |
7 files changed, 351 insertions, 414 deletions
@@ -213,6 +213,7 @@ "nixpkgs": "nixpkgs", "nixpkgs-stable": "nixpkgs-stable", "sops-nix": "sops-nix", + "stalwart-nix": "stalwart-nix", "treefmt-nix": "treefmt-nix" } }, @@ -253,6 +254,29 @@ "type": "github" } }, + "stalwart-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "treefmt-nix": [ + "treefmt-nix" + ] + }, + "locked": { + "lastModified": 1780081489, + "narHash": "sha256-ZALATER+KJLI0BumJtC7VhvpC3bYDTFvz/fMazoutGI=", + "owner": "Wilkuu-2", + "repo": "stalwart-nix", + "rev": "80bccc3e1ed1f5a99f13d4bc18402eb5eac99d76", + "type": "github" + }, + "original": { + "owner": "Wilkuu-2", + "repo": "stalwart-nix", + "type": "github" + } + }, "treefmt-nix": { "inputs": { "nixpkgs": [ @@ -5,6 +5,14 @@ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-25.05"; + stalwart-nix = { + # local testing + # url = "path:/store2/code/stalwart-nix"; + url = "github:Wilkuu-2/stalwart-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.treefmt-nix.follows = "treefmt-nix"; + }; + sops-nix = { url = "github:Mic92/sops-nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -42,46 +50,41 @@ nixpkgs, treefmt-nix, disko, + stalwart-nix, + sops-nix, + home-manager, ... }@inputs: let lib = nixpkgs.lib; systems = [ "x86_64-linux" + "x86_64-darwin" "aarch64-linux" + "aarch64-darwin" ]; # Allows code to execute for all used architectures - forAllSystems = f: (lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system})); + pkgsPerSystem = (lib.genAttrs systems (system: nixpkgs.legacyPackages.${system})); + forAllSystems = f: (lib.genAttrs systems (system: f pkgsPerSystem.${system} system)); # Treefmt has a bunch of long paths that we want to bundle. - treefmtStuff = forAllSystems ( - pkgs: - let - treefmt = treefmt-nix.lib.evalModule pkgs ./modules/treefmt.nix; - in - { - formatter = treefmt.config.build.wrapper; - formatCheck = { - formatting = treefmt.config.build.check self; - }; - } - ); - # Convenient extractor which generates an attrset of system: attribute, with the attribute being picked from treefmtStuff by name. - treefmtExtract = name: (builtins.mapAttrs (_system: conf: conf."${name}") (treefmtStuff)); + treefmt = forAllSystems (pkgs: _: treefmt-nix.lib.evalModule pkgs ./modules/treefmt.nix); in { - packages = let system = "x86_64-linux"; pkgs = import nixpkgs {inherit system;}; in { - ${system} = { - full-iso = self.nixosConfigurations.full-iso.config.system.build.isoImage; - bulwark = pkgs.callPackage ./packages/bulwark/package.nix {}; - stalwart = pkgs.callPackage ./packages/stalwart/package.nix {}; - stalwart-cli = pkgs.callPackage ./packages/stalwart-cli/package.nix {}; + packages = + forAllSystems ( + pkgs: _system: { + bulwark = pkgs.callPackage ./packages/bulwark/package.nix { }; + } + ) + // { + "x86_64-linux".full-iso = self.nixosConfigurations.full-iso.config.system.build.isoImage; }; - }; + # for `nix fmt` - formatter = treefmtExtract "formatter"; + formatter = forAllSystems (_: system: treefmt.${system}.config.build.wrapper); # for `nix flake check` - checks = treefmtExtract "formatCheck"; + checks = forAllSystems (_: system: { formatting = treefmt.${system}.config.build.check self; }); nixosConfigurations = { apocalypse = nixpkgs.lib.nixosSystem { @@ -93,8 +96,9 @@ ./modules ./hosts/apocalypse ./users/wilkuu.nix - inputs.home-manager.nixosModules.default - inputs.sops-nix.nixosModules.sops + stalwart-nix.nixosModules.default + home-manager.nixosModules.default + sops-nix.nixosModules.default ]; }; full-iso = nixpkgs.lib.nixosSystem { @@ -108,8 +112,8 @@ ./modules ./hosts/full-iso ./users/live-user.nix - inputs.home-manager.nixosModules.default - inputs.sops-nix.nixosModules.sops + home-manager.nixosModules.default + sops-nix.nixosModules.default ]; }; omega-relay = nixpkgs.lib.nixosSystem { @@ -121,9 +125,9 @@ ./modules ./users/wilkuu-server.nix ./hosts/omega-relay - inputs.home-manager.nixosModules.default + home-manager.nixosModules.default disko.nixosModules.disko - inputs.sops-nix.nixosModules.sops + sops-nix.nixosModules.default ]; }; @@ -136,9 +140,9 @@ ./modules ./users/wilkuu-server.nix ./hosts/tacitus - inputs.home-manager.nixosModules.default + home-manager.nixosModules.default disko.nixosModules.disko - inputs.sops-nix.nixosModules.sops + sops-nix.nixosModules.default ]; }; diff --git a/packages/stalwart-cli/package.nix b/packages/stalwart-cli/package.nix deleted file mode 100644 index 6c89fa2..0000000 --- a/packages/stalwart-cli/package.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - lib, - rustPlatform, - versionCheckHook, - stalwart, - fetchFromGitHub, - openssl, - pkg-config, - nix-update-script, -}: -rustPlatform.buildRustPackage (finalAttrs: { - pname = "stalwart-cli"; - version = "1.0.7"; - src = fetchFromGitHub { - owner = "stalwartlabs"; - repo = "cli"; - tag = "v${finalAttrs.version}"; - hash = ""; - }; - - cargoHash = ""; - - nativeBuildInputs = [ pkg-config ]; - buildInputs = [ openssl ]; - - env.OPENSSL_NO_VENDOR = true; - - cargoBuildFlags = [ - "--package" - "stalwart-cli" - ]; - cargoTestFlags = [ - "--package" - "stalwart-cli" - ]; - - doInstallCheck = true; - nativeInstallCheckInputs = [ versionCheckHook ]; - - passthru.updateScript = nix-update-script { }; - - meta = { - description = "Stalwart Mail Server CLI"; - mainProgram = "stalwart-cli"; - homepage = "https://github.com/stalwartlabs/cli"; - changelog = "https://github.com/stalwartlabs/cli/blob/main/CHANGELOG.md"; - license = lib.licenses.agpl3Only; - # maintainers = with lib.maintainers; [ - # giomf - # ]; - }; -}) diff --git a/packages/stalwart/package.nix b/packages/stalwart/package.nix deleted file mode 100644 index 185fa5a..0000000 --- a/packages/stalwart/package.nix +++ /dev/null @@ -1,211 +0,0 @@ -{ - lib, - rustPlatform, - fetchFromGithub, - pkg-config, - protobuf, - bzip2, - openssl, - sqlite, - foundationdb, - zstd, - stdenv, - nix-update-script, - nixosTest, - rocksdb, - callPackage, - withFoundationdb ? false, - stalwartEnterprise ? false, - buildPackages, -}: - rustPlatform.makeRustPackage (finalAttrs: { - pname = "stalwart" + (lib.optionalString stalwartEnterprise "-enterprise"); - version = "0.16.6"; - src = fetchFromGithub { - owner = "stalwartlabs"; - repo = "stalwart"; - tag = "v${finalAttrs.version}"; - hash = ""; - }; - - cargoHash = ""; - - depsBuildBuild = [ - pkg-config - zstd - ]; - - nativeBuildInputs = [ - protobuf - rustPlatform.bindgenHook - ]; - - buildInputs = [ - bzip2 - openssl - sqlite - zstd - ] ++ lib.optional (stdenv.hostPlatform.isLinux && withFoundationdb) foundationdb; - - nativeCheckInputs = [ - openssl - ]; - - buildNoDefaultFeatures = true; - buildFeatures = [ - "sqlite" - "postgres" - "mysql" - "rocks" - "s3" - "redis" - "azure" - "nats" - ] - ++ lib.optionals withFoundationdb [ "foundationdb" ] - ++ lib.optionals stalwartEnterprise [ "enterprise" ]; - - env = { - OPENSSL_NO_VENDOR = true; - ZSTD_SYS_USE_PKG_CONFIG = true; - ROCKSDB_INCLUDE_DIR = "${rocksdb}/include"; - ROCKSDB_LIB_DIR = "${rocksdb}/lib"; - } - // - lib.optionalAttrs - (stdenv.hostPlatform.isLinux && (stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isArmv7)) - { - JEMALLOC_SYS_WITH_LG_PAGE = 16; - }; - - postInstall = '' - mkdir -p $out/etc/stalwart - - mkdir -p $out/lib/systemd/system - - substitute resources/systemd/stalwart-mail.service $out/lib/systemd/system/stalwart.service \ - --replace-fail "__PATH__" "$out" - ''; - - preCheck = '' - export STORE=Sqlite - ''; - checkFlags = lib.forEach [ - # Require running mysql, postgresql daemon - "directory::imap::imap_directory" - "directory::internal::internal_directory" - "directory::ldap::ldap_directory" - "directory::sql::sql_directory" - "directory::oidc::oidc_directory" - "store::blob::blob_tests" - "store::lookup::lookup_tests" - "smtp::lookup::sql::lookup_sql" - # thread 'directory::smtp::lmtp_directory' panicked at tests/src/store/mod.rs:122:44: - # called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" } - "directory::smtp::lmtp_directory" - # thread 'imap::imap_tests' panicked at tests/src/imap/mod.rs:436:14: - # Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent - "imap::imap_tests" - # thread 'jmap::jmap_tests' panicked at tests/src/jmap/mod.rs:303:14: - # Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent - "jmap::jmap_tests" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::data::data" - # Expected "X-My-Header: true" but got Received: from foobar.net (unknown [10.0.0.123]) - "smtp::inbound::scripts::sieve_scripts" - # thread 'smtp::outbound::lmtp::lmtp_delivery' panicked at tests/src/smtp/session.rs:313:13: - # Expected "<invalid@domain.org> (failed to lookup" but got From: "Mail Delivery Subsystem" <MAILER-DAEMON@localhost> - "smtp::outbound::lmtp::lmtp_delivery" - # thread 'smtp::outbound::extensions::extensions' panicked at tests/src/smtp/inbound/mod.rs:45:23: - # No queue event received. - "smtp::outbound::extensions::extensions" - # panicked at tests/src/smtp/outbound/smtp.rs:173:5: - "smtp::outbound::smtp::smtp_delivery" - # panicked at tests/src/smtp/outbound/lmtp.rs - "smtp::outbound::lmtp::lmtp_delivery" - # thread 'smtp::queue::retry::queue_retry' panicked at tests/src/smtp/queue/retry.rs:119:5: - # assertion `left == right` failed - # left: [1, 2, 2] - # right: [1, 2, 3] - "smtp::queue::retry::queue_retry" - # thread 'smtp::queue::virtualq::virtual_queue' panicked at /build/source/crates/store/src/dispatch/store.rs:548:14: - # called `Result::unwrap()` on an `Err` value: Error(Event { inner: Store(MysqlError), keys: [(Reason, String("Input/output error: Input/output error: Connection refused (os error 111)")), (CausedBy, String("crates/store/src/dispatch/store.rs:301"))] }) - "smtp::queue::virtualq::virtual_queue" - # Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent - "store::store_tests" - # Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent - "cluster::cluster_tests" - # Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent - "webdav::webdav_tests" - # thread 'config::parser::tests::toml_parse' panicked at crates/utils/src/config/parser.rs:463:58: - # called `Result::unwrap()` on an `Err` value: "Expected ['\\n'] but found '!' in value at line 70." - "config::parser::tests::toml_parse" - # error[E0432]: unresolved import `r2d2_sqlite` - # use of undeclared crate or module `r2d2_sqlite` - "backend::sqlite::pool::SqliteConnectionManager::with_init" - # thread 'smtp::reporting::analyze::report_analyze' panicked at tests/src/smtp/reporting/analyze.rs:88:5: - # assertion `left == right` failed - # left: 0 - # right: 12 - "smtp::reporting::analyze::report_analyze" - # thread 'smtp::inbound::dmarc::dmarc' panicked at tests/src/smtp/inbound/mod.rs:59:26: - # Expected empty queue but got Reload - "smtp::inbound::dmarc::dmarc" - # thread 'smtp::queue::concurrent::concurrent_queue' panicked at tests/src/smtp/inbound/mod.rs:65:9: - # assertion `left == right` failed - "smtp::queue::concurrent::concurrent_queue" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::auth::auth" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::antispam::antispam" - # Failed to read system DNS config: io error: No such file or directory (os error 2) - "smtp::inbound::vrfy::vrfy_expn" - # thread 'smtp::management::queue::manage_queue' panicked at tests/src/smtp/inbound/mod.rs:45:23: - # No queue event received. - # NOTE: Test unreliable on high load systems - "smtp::management::queue::manage_queue" - # thread 'responses::tests::parse_responses' panicked at crates/dav-proto/src/responses/mod.rs:671:17: - # assertion `left == right` failed: failed for 008.xml - # left: ElementEnd - # right: Bytes([...]) - "responses::tests::parse_responses" - # thread 'store::search_tests' (912386) panicked at tests/src/store/mod.rs:116:10: - # Missing store type. Try running `STORE=<store_type> cargo test`: NotPresent - "store::search_tests" - ] (test: "--skip=${test}"); - - doCheck = !(stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isAarch64); - - __darwinAllowLocalNetworking = true; - - passthru = { - inherit rocksdb; - # webadmin = buildPackages.callPackage ./webadmin.nix {}; - # spam-filter = callPackage ./spam-filter.nix {}; - updateScript = nix-update-script { }; - # test.stalwart = nixosTests.stalwart; - }; - - meta = { - description = "Secure, modern, all-in-one mail and collaboration server"; - longDescription = '' - Secure, scalable and fluent in every protocol (IMAP, JMAP, SMTP, CalDAV, CardDAV, WebDAV). - ''; - homepage = "https://github.com/stalwartlabs/stalwart"; - changelog = "https://github.com/stalwartlabs/stalwart/blob/main/CHANGELOG.md"; - license = [ - lib.licenses.agpl3Only - ] - ++ lib.optionals stalwartEnterprise [ - { - fullName = "Stalwart Enterprise License 1.0 (SELv1) Agreement"; - url = "https://github.com/stalwartlabs/stalwart/blob/main/LICENSES/LicenseRef-SEL.txt"; - free = false; - redistributable = false; - } - ]; - - mainProgram = "stalwart"; - }; -}) - diff --git a/packages/stalwart/spam-filter.nix b/packages/stalwart/spam-filter.nix deleted file mode 100644 index 5497322..0000000 --- a/packages/stalwart/spam-filter.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - lib, - fetchFromGithub, - stdenv, - stalwart, - nix-update-script, -}: stdenv.mkDerivation (finalAttrs: { - pname = "spam-filter"; - version = "2.0.5"; - - src = fetchFromGithub { - owner = "stalwartlabs"; - repo = "spam-filter"; - tag = "v${finalAttrs.version}"; - hash = ""; - }; - - buildPhase = '' - bash ./build.sh - ''; - - installPhase = '' - mkdir -p $out - cp spam-filter.toml $out/ - ''; - - passthru = {updateScript = nix-update-script {};}; - - meta = { - description = "Spam filter module for the Stalwart server"; - homepage = "https://github.com/stalwartlabs/spam-filter"; - changelog = "https://github.com/stalwartlabs/spam-filter/blob/${finalAttrs.src.tag}/CHANGELOG.md"; - license = with lib.licenses; [ - mit - asl20 - ]; - # inherit (stalwart.meta) maintainers; - }; - - -}) - diff --git a/packages/stalwart/webadmin.nix b/packages/stalwart/webadmin.nix deleted file mode 100644 index 0035bda..0000000 --- a/packages/stalwart/webadmin.nix +++ /dev/null @@ -1,77 +0,0 @@ -{ - lib, - rustPlatform, - stalwart, - fetchFromGitHub, - trunk, - tailwindcss_3, - fetchNpmDeps, - nix-update-script, - nodejs, - npmHooks, - llvmPackages, - wasm-bindgen-cli_0_2_93, - binaryen, - zip, -}: - -rustPlatform.buildRustPackage (finalAttrs: { - pname = "webadmin"; - version = "0.1.37"; - - src = fetchFromGitHub { - owner = "stalwartlabs"; - repo = "webadmin"; - tag = "v${finalAttrs.version}"; - hash = ""; - }; - - npmDeps = fetchNpmDeps { - name = "${finalAttrs.pname}-npm-deps"; - hash = ""; - }; - - cargoHash = ""; - - postPatch = '' - # Using local tailwindcss for compilation - substituteInPlace Trunk.toml --replace-fail "npx tailwindcss" "tailwindcss" - ''; - - nativeBuildInputs = [ - binaryen - llvmPackages.bintools-unwrapped - nodejs - npmHooks.npmConfigHook - tailwindcss_3 - trunk - # needs to match with wasm-bindgen version in upstreams Cargo.lock - wasm-bindgen-cli_0_2_93 - - zip - ]; - - env.NODE_PATH = "$npmDeps"; - - buildPhase = '' - trunk build --offline --frozen --release - ''; - - installPhase = '' - cd dist - mkdir -p $out - zip -r $out/webadmin.zip * - ''; - - passthru = { - updateScript = nix-update-script { }; - }; - - meta = { - description = "Web administration module for the Stalwart server"; - homepage = "https://github.com/stalwartlabs/webadmin"; - changelog = "https://github.com/stalwartlabs/webadmin/blob/${finalAttrs.src.tag}/CHANGELOG.md"; - license = lib.licenses.agpl3Only; - # inherit (stalwart.meta) maintainers; - }; -}) diff --git a/services/mail2.nix b/services/mail2.nix new file mode 100644 index 0000000..dcea8c0 --- /dev/null +++ b/services/mail2.nix @@ -0,0 +1,291 @@ +{ + config, + lib, + ... +}: +let + cfg = config.wilkuu.services.mail; + hostname = config.networking.hostName; +in +{ + imports = [ ./stalwart0_16.nix ]; + options.wilkuu.services.mail = with lib; { + enable = mkEnableOption "Enable webmail"; + doACME = mkEnableOption "Enable ACME for stalwart here"; + defaultDomain = mkOption { + type = lib.types.str; + default = "mail.${hostname}.local"; + example = "mail.wilkuu.xyz"; + description = "Domain for http connections."; + }; + domains = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "mail.wilkuu.xyz" ]; + description = "Domains for email."; + }; + wellKnownDomains = mkOption { + type = types.listOf types.str; + default = [ "${hostname}.local" ]; + example = [ "wilkuu.xyz" ]; + description = "Domain for well-known items"; + }; + extraConfig = mkOption { + type = types.listOf types.attrs; + description = "Additional plan steps added to the stalwart config"; + default = [ ]; + example = [ ]; + }; + extraCreate = mkOption { + type = types.listOf types.attrs; + description = "Additional idempotent create steps added to the stalwart config"; + default = [ ]; + example = [ ]; + }; + + startupMode = mkOption { + type = types.enum [ + "normal" + "bootstrap" + "recovery" + ]; + description = "Whenever to use the bootstrap or recovery mode, see https://stalw.art/docs/configuration/bootstrap-mode/ and https://stalw.art/docs/configuration/recovery-mode/"; + default = "normal"; + example = "bootstrap"; + }; + + }; + + config = lib.mkIf cfg.enable ( + let + tools = config.wilkuu.services.stalwart16.toolbox; + sopsPath = ../secrets/${hostname}/stalwart.yaml; + secrets = [ + "admin_user" + "admin_password" + "recovery_user" + "recovery_password" + ]; + toSops = (sname: "stalwart16/${sname}"); + toPlaceholder = (sname: config.sops.placeholder.${toSops sname}); + toCredfilePath = (name: config.sops.secrets.${toSops name}.path); + + domain_to_jid = lib.replaceString "." "_"; + # We do this to satisfy the foreign key constraint of the SystemSettings singleton + placeholderDomain = "bootstrap-placeholder.home.arpa"; + planPreamble = lib.concatLists [ + (tools.mkIdempotentCreateLine { + "object" = "Domain"; + deleteBy = "name"; + value."#placeholder-domain" = { + name = placeholderDomain; + certificateManagement = { + "@type" = "Manual"; + }; + dnsManagement = { + "@type" = "Manual"; + }; + dkimManagement = { + "@type" = "Manual"; + }; + subAddressing = { + "@type" = "Enabled"; + }; + }; + }) + [ + { + "@type" = "update"; + "object" = "SystemSettings"; + "value" = { + "defaultDomainId" = "#placeholder-domain"; + }; + } + ] + ]; + # TODO: Is this nice, or is using 1 object better? + # This approach makes it more atomic afaik? + domainCreateRules = ( + lib.forEach cfg.domains (domain: { + object = "Domain"; + deleteBy = "name"; + value.${domain_to_jid domain} = { + name = domain; + certificateManagement = { + "@type" = "Manual"; + }; + dnsManagement = { + "@type" = "Manual"; + }; + dkimManagement = { + "@type" = "Manual"; + }; + subAddressing = { + "@type" = "Enabled"; + }; + }; + }) + ); + certificateCreateRules = lib.optionals cfg.doACME ( + lib.forEach (lib.unique ([ cfg.default_domain ] ++ cfg.domains)) ( + (domain: { + object = "Certificate"; + deleteBy = "certificate.filePath"; + value."cert_${domain_to_jid domain}" = { + certificate = { + "@type" = "File"; + filePath = "/run/credentials/stalwart.service/tls_${domain}_cert.pem"; + }; + privateKey = { + "@type" = "File"; + filePath = "/run/credentials/stalwart.service/tls_${domain}_key.pem"; + }; + }; + }) + + ) + ); + baseSetupRules = [ + { + "@type" = "update"; + "object" = "SystemSettings"; + "value" = { + "defaultDomainId" = "#${domain_to_jid (builtins.elemAt cfg.domains 0)}"; + "defaultHostname" = cfg.defaultDomain; + }; + } + { + "@type" = "update"; + "object" = "BlobStore"; + "value" = { + "@type" = "Default"; + }; + } + { + "@type" = "update"; + "object" = "InMemoryStore"; + "value" = { + "@type" = "Default"; + }; + } + { + "@type" = "update"; + "object" = "SearchStore"; + "value" = { + "@type" = "Default"; + }; + } + ]; + proxyWellKnown = + names: + let + uris = map (n: "/.well-known/${n}") names; + in + (lib.genAttrs uris (uri: { + proxyPass = "http://localhost:3080${uri}"; + recommendedProxySettings = true; + })); + makeHTTPRedirectBody = target: https: "302 ${if https then "https" else "http"}://${target}"; + + in + { + users.users.stalwart = { + isSystemUser = true; + group = "stalwart"; + }; + users.groups.stalwart = { }; + sops.secrets = ( + lib.genAttrs (map toSops secrets) (_name: { + sopsFile = sopsPath; + mode = "0440"; + owner = "stalwart"; + }) + ); + + sops.templates = { + stalwart-config-creds = { + owner = "stalwart"; + mode = "0440"; + content = '' + STALWART_USER=${toPlaceholder "admin_user"} + STALWART_PASSWORD=${toPlaceholder "admin_password"} + ''; + }; + stalwart-recovery-creds = { + owner = "stalwart"; + mode = "0440"; + content = '' + STALWART_RECOVERY_ADMIN=${toPlaceholder "recovery_user"}:${toPlaceholder "recovery_password"} + ''; + }; + }; + + services.nginx.enable = lib.mkDefault true; + services.nginx.virtualHosts = + (lib.genAttrs + (lib.concatLists [ + cfg.wellKnownDomains + cfg.domains + [ cfg.defaultDomain ] + ]) + (_wdomain: { + addSSL = lib.mkDefault cfg.doACME; + enableACME = lib.mkDefault cfg.doACME; + locations = + (proxyWellKnown [ + "mta-sts.txt" + "mail-v1.xml" + "autoconfig/mail" + "openid-configuration" + "/.well-known/oauth-authorization-server" + ]) + // (lib.genAttrs [ "/.well-known/caldav/" "/.well-known/webdav/" "/.well-known/jmap" ] (uri: { + extraConfig = '' + return ${makeHTTPRedirectBody "${cfg.domain}${uri}" cfg.doACME}; + ''; + })); + + }) + ) + // (lib.genAttrs [ cfg.defaultDomain ] (_domain: { + addSSL = cfg.doACME; + enableACME = cfg.doACME; + #serverName = "${domain}"; + locations."/" = { + proxyPass = "http://localhost:8080"; + proxyWebsockets = true; + recommendedProxySettings = true; + }; + })); + + wilkuu.services.stalwart16 = { + enable = cfg.enable; + url = if cfg.startupMode != "normal" then "http://localhost:8080/" else cfg.defaultDomain; + credentialsFile = config.sops.templates.stalwart-config-creds.path; + recoveryCredentialsFile = config.sops.templates.stalwart-recovery-creds.path; + startupMode = cfg.startupMode; + user = "stalwart"; + group = "stalwart"; + configPlanPre = planPreamble; + idempotentCreate = certificateCreateRules ++ domainCreateRules ++ cfg.extraCreate; + configPlanPost = baseSetupRules ++ cfg.extraConfig; + credentials = + (lib.genAttrs secrets toCredfilePath) + // (builtins.foldl' (a: b: a // b) ({ }) ( + map ( + domain: + let + acme_dir = config.security.acme.certs.${domain}.directory; + cert_path = file: "${acme_dir}/${file}"; + in + { + "tls_${domain}_cert.pem" = cert_path "cert.pem"; + "tls_${domain}_key.pem" = cert_path "key.pem"; + } + ) (lib.optionals cfg.doACME (lib.unique ([ cfg.defaultDomain ] ++ cfg.domains))) + )); + }; + } + ); + +} |
