From f918dfda8c624313ec0eaeaf6f5f6a74b6c8b94f Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Nov 2024 14:26:48 +0100 Subject: [PATCH] feat: stalwart config --- config/basic/default.nix | 2 + config/basic/secrets.nix | 21 + config/basic/users.nix | 2 + config/services/idmail.nix | 92 +++ config/services/stalwart.nix | 662 ++++++++++++++++++ flake.lock | 646 +++++++++++++---- flake.nix | 4 + hosts/mailnix/default.nix | 2 + hosts/mailnix/net.nix | 16 +- .../idmail-mailbox-hash_catch-all.age | 17 + .../generated/idmail-mailbox-pw_catch-all.age | 18 + .../generated/idmail-user-hash_admin.age | Bin 0 -> 865 bytes .../generated/idmail-user-pw_admin.age | 16 + .../secrets/generated/resticpasswd.age | 16 + .../secrets/generated/stalwart-admin-hash.age | Bin 0 -> 860 bytes .../secrets/generated/stalwart-admin-pw.age | 15 + .../generated/stalwartHetznerSshKey.age | 16 + secrets/secrets.nix.age | Bin 7968 -> 8022 bytes 18 files changed, 1418 insertions(+), 127 deletions(-) create mode 100644 config/basic/secrets.nix create mode 100644 config/services/idmail.nix create mode 100644 config/services/stalwart.nix create mode 100644 hosts/mailnix/secrets/generated/idmail-mailbox-hash_catch-all.age create mode 100644 hosts/mailnix/secrets/generated/idmail-mailbox-pw_catch-all.age create mode 100644 hosts/mailnix/secrets/generated/idmail-user-hash_admin.age create mode 100644 hosts/mailnix/secrets/generated/idmail-user-pw_admin.age create mode 100644 hosts/mailnix/secrets/generated/resticpasswd.age create mode 100644 hosts/mailnix/secrets/generated/stalwart-admin-hash.age create mode 100644 hosts/mailnix/secrets/generated/stalwart-admin-pw.age create mode 100644 hosts/mailnix/secrets/generated/stalwartHetznerSshKey.age diff --git a/config/basic/default.nix b/config/basic/default.nix index ae7f99c..6cd2e55 100644 --- a/config/basic/default.nix +++ b/config/basic/default.nix @@ -10,6 +10,7 @@ ./net.nix ./nftables.nix ./nix.nix + ./secrets.nix ./ssh.nix ./system.nix ./users.nix @@ -28,6 +29,7 @@ inputs.agenix.nixosModules.default inputs.disko.nixosModules.disko inputs.home-manager.nixosModules.default + inputs.idmail.nixosModules.default inputs.impermanence.nixosModules.impermanence inputs.lanzaboote.nixosModules.lanzaboote inputs.nix-topology.nixosModules.default diff --git a/config/basic/secrets.nix b/config/basic/secrets.nix new file mode 100644 index 0000000..b7b7bcb --- /dev/null +++ b/config/basic/secrets.nix @@ -0,0 +1,21 @@ +{ + age.generators.argon2id = + { + pkgs, + lib, + decrypt, + deps, + ... + }: + let + dep = builtins.head deps; + in + '' + echo " -> Deriving argon2id hash from "${lib.escapeShellArg dep.host}":"${lib.escapeShellArg dep.name}"" >&2 + ${decrypt} ${lib.escapeShellArg dep.file} \ + | tr -d '\n' \ + | ${pkgs.libargon2}/bin/argon2 "$(${pkgs.openssl}/bin/openssl rand -base64 16)" -id -e \ + || die "Failure while generating argon2id hash" + ''; + +} diff --git a/config/basic/users.nix b/config/basic/users.nix index 5f6efd1..bd9ee3b 100644 --- a/config/basic/users.nix +++ b/config/basic/users.nix @@ -40,6 +40,8 @@ signal = uidGid 228; netbird-main = uidGid 229; paperless = uidGid 315; + stalwart-mail = uidGid 316; + build = uidGid 317; systemd-oom = uidGid 300; systemd-coredump = uidGid 301; patrick = uidGid 1000; diff --git a/config/services/idmail.nix b/config/services/idmail.nix new file mode 100644 index 0000000..b4674a2 --- /dev/null +++ b/config/services/idmail.nix @@ -0,0 +1,92 @@ +{ + inputs, + config, + ... +}: +let + domain = config.secrets.secrets.global.domains.mail_public; + idmailDomain = "alias.${domain}"; + priv_domain = config.secrets.secrets.global.domains.mail_private; + + mkRandomSecret = { + generator.script = "alnum"; + mode = "000"; + intermediary = true; + }; + + mkArgon2id = secret: { + generator.dependencies = [ config.age.secrets.${secret} ]; + generator.script = "argon2id"; + mode = "440"; + group = "stalwart-mail"; + }; +in +{ + environment.persistence."/persist".directories = [ + { + directory = config.services.idmail.dataDir; + user = "stalwart-mail"; + group = "stalwart-mail"; + mode = "4770"; + } + ]; + + age.secrets = { + idmail-user-pw_admin = mkRandomSecret; + idmail-user-hash_admin = mkArgon2id "idmail-user-pw_admin"; + idmail-mailbox-pw_catch-all = mkRandomSecret; + idmail-mailbox-hash_catch-all = mkArgon2id "idmail-mailbox-pw_catch-all"; + }; + + services.idmail = { + package = inputs.idmail.packages."aarch64-linux".default; + enable = true; + # Stalwart will change permissions due to SQLite implementation. + # Therefore, run as stalwart-mail since we don't allow reading + # stalwarts folder anyway (sandboxing is on). + user = "stalwart-mail"; + provision = { + enable = true; + users.admin = { + admin = true; + password_hash = "%{file:${config.age.secrets.idmail-user-hash_admin.path}}%"; + }; + domains = { + "${domain}" = { + owner = "admin"; + catch_all = "catch-all@${domain}"; + public = true; + }; + "${priv_domain}" = { + owner = "admin"; + catch_all = "catch-all@${domain}"; + public = false; + }; + }; + mailboxes."catch-all@${domain}" = { + password_hash = "%{file:${config.age.secrets.idmail-mailbox-hash_catch-all.path}}%"; + owner = "admin"; + }; + # XXX: create mailboxes for git@ vaultwarden@ and simultaneously alias them to the catch all for a send only mail. + }; + }; + systemd.services.idmail.serviceConfig.RestartSec = "60"; # Retry every minute + + services.nginx = { + upstreams.idmail = { + servers."127.0.0.1:3000" = { }; + extraConfig = '' + zone idmail 64k; + keepalive 2; + ''; + }; + virtualHosts.${idmailDomain} = { + forceSSL = true; + useACMEWildcardHost = true; + locations."/" = { + proxyPass = "http://idmail"; + proxyWebsockets = true; + }; + }; + }; +} diff --git a/config/services/stalwart.nix b/config/services/stalwart.nix new file mode 100644 index 0000000..17d96bc --- /dev/null +++ b/config/services/stalwart.nix @@ -0,0 +1,662 @@ +{ + config, + lib, + pkgs, + ... +}: +let + priv_domain = config.secrets.secrets.global.domains.mail_private; + domain = config.secrets.secrets.global.domains.mail_public; + mailDomains = [ + priv_domain + domain + ]; + mailBackupDir = "/var/cache/mail-backup"; + dataDir = "/var/lib/stalwart-mail"; +in +{ + age.secrets.resticpasswd = { + generator.script = "alnum"; + }; + age.secrets.stalwartHetznerSshKey = { + generator.script = "ssh-ed25519"; + }; + services.restic.backups = { + main = { + user = "root"; + timerConfig = { + OnCalendar = "06:00"; + Persistent = true; + RandomizedDelaySec = "3h"; + }; + initialize = true; + passwordFile = config.age.secrets.resticpasswd.path; + hetznerStorageBox = { + enable = true; + inherit (config.secrets.secrets.global.hetzner) mainUser; + inherit (config.secrets.secrets.global.hetzner.users.stalwart-mail) subUid path; + sshAgeSecret = "stalwartHetznerSshKey"; + }; + paths = [ + mailBackupDir + ]; + #pruneOpts = [ + # "--keep-daily 10" + # "--keep-weekly 7" + # "--keep-monthly 12" + # "--keep-yearly 75" + #]; + }; + }; + systemd.services.backup-mail = { + description = "Mail backup"; + environment = { + STALWART_DATA = dataDir; + IDMAIL_DATA = config.services.idmail.dataDir; + BACKUP_DIR = mailBackupDir; + }; + serviceConfig = { + SyslogIdentifier = "backup-mail"; + Type = "oneshot"; + User = "stalwart-mail"; + Group = "stalwart-mail"; + ExecStart = lib.getExe ( + pkgs.writeShellApplication { + name = "backup-mail"; + runtimeInputs = [ pkgs.sqlite ]; + text = '' + sqlite3 "$STALWART_DATA/database.sqlite3" ".backup '$BACKUP_DIR/database.sqlite3'" + sqlite3 "$IDMAIL_DATA/database.sqlite3" ".backup '$BACKUP_DIR/idmail.db'" + cp -r "$STALWART_DATA/dkim" "$BACKUP_DIR/" + ''; + } + ); + ReadWritePaths = [ + dataDir + config.services.idmail.dataDir + mailBackupDir + ]; + Restart = "no"; + }; + requiredBy = [ "restic-backups-main.service" ]; + before = [ "restic-backups-main.service" ]; + }; + + age.secrets.stalwart-admin-pw = { + generator.script = "alnum"; + mode = "000"; + intermediary = true; + }; + + age.secrets.stalwart-admin-hash = { + generator.dependencies = [ config.age.secrets.stalwart-admin-pw ]; + generator.script = "argon2id"; + mode = "440"; + group = "stalwart-mail"; + }; + + users.groups.acme.members = [ "stalwart-mail" ]; + + networking.firewall.allowedTCPPorts = [ + 25 # smtp + 465 # submission tls + # 587 # submission starttls + 993 # imap tls + # 143 # imap starttls + 4190 # manage sieve + ]; + environment.persistence."/persist".directories = [ + { + directory = dataDir; + user = "stalwart-mail"; + group = "stalwart-mail"; + mode = "0700"; + } + ]; + + # Needed so we don't run out of tmpfs space for large backups. + # Technically this could be cleared each boot but whatever. + environment.persistence."/state".directories = [ + { + directory = mailBackupDir; + user = "stalwart-mail"; + group = "stalwart-mail"; + mode = "0700"; + } + ]; + services.nginx = { + upstreams.stalwart = { + servers."127.0.0.1:8080" = { }; + extraConfig = '' + zone stalwart 64k; + keepalive 2; + ''; + }; + virtualHosts = + { + ${domain} = { + forceSSL = true; + useACMEWildcardHost = true; + extraConfig = '' + client_max_body_size 512M; + ''; + locations."/" = { + proxyPass = "http://stalwart"; + proxyWebsockets = true; + }; + }; + } + // lib.genAttrs + [ + "autoconfig.${domain}" + "autodiscover.${domain}" + "mta-sts.${domain}" + ] + (_: { + forceSSL = true; + useACMEWildcardHost = true; + locations."/".proxyPass = "http://stalwart"; + }); + }; + systemd.services.stalwart-mail = + let + cfg = config.services.stalwart-mail; + configFormat = pkgs.formats.toml { }; + configFile = configFormat.generate "stalwart-mail.toml" cfg.settings; + in + { + preStart = lib.mkAfter ( + '' + cat ${configFile} > /run/stalwart-mail/config.toml + cat ${config.age.secrets.stalwart-admin-hash.path} \ + | tr -d '\n' > /run/stalwart-mail/admin-hash + + mkdir -p /var/lib/stalwart-mail/dkim + '' + # Generate DKIM keys if necessary + + lib.concatLines ( + lib.forEach mailDomains (domain: '' + if [[ ! -e /var/lib/stalwart-mail/dkim/rsa-${domain}.key ]]; then + echo "Generating DKIM key for ${domain} (rsa)" + ${lib.getExe pkgs.openssl} genrsa -traditional -out /var/lib/stalwart-mail/dkim/rsa-${domain}.key 2048 + fi + if [[ ! -e /var/lib/stalwart-mail/dkim/ed25519-${domain}.key ]]; then + echo "Generating DKIM key for ${domain} (ed25519)" + ${lib.getExe pkgs.openssl} genpkey -algorithm ed25519 -out /var/lib/stalwart-mail/dkim/ed25519-${domain}.key + fi + '') + ) + ); + + serviceConfig = { + RuntimeDirectory = "stalwart-mail"; + ReadWritePaths = [ config.services.idmail.dataDir ]; + ExecStart = lib.mkForce [ + "" + "${cfg.package}/bin/stalwart-mail --config=/run/stalwart-mail/config.toml" + ]; + RestartSec = "60"; # Retry every minute + }; + }; + + services.stalwart-mail = { + enable = true; + settings = + let + case = field: check: value: data: { + "if" = field; + ${check} = value; + "then" = data; + }; + ifthen = field: data: { + "if" = field; + "then" = data; + }; + otherwise = value: { "else" = value; }; + is-smtp = case "listener" "eq" "smtp"; + is-authenticated = data: { + "if" = "!is_empty(authenticated_as)"; + "then" = data; + }; + in + lib.mkForce { + config.local-keys = [ + "store.*" + "directory.*" + "tracer.*" + "server.*" + "!server.blocked-ip.*" + "!server.allowed-ip.*" + "authentication.fallback-admin.*" + "cluster.node-id" + "storage.data" + "storage.blob" + "storage.lookup" + "storage.fts" + "storage.directory" + "lookup.default.hostname" + "certificate.*" + "auth.dkim.*" + "signature.*" + ]; + + authentication.fallback-admin = { + user = "admin"; + secret = "%{file:/run/stalwart-mail/admin-hash}%"; + }; + + tracer.stdout = { + # Do not use the built-in journal tracer, as it shows much less auxiliary + # information for the same loglevel + type = "stdout"; + level = "info"; + ansi = false; # no colour markers to journald + enable = true; + }; + + store.db = { + type = "sqlite"; + path = "${dataDir}/database.sqlite3"; + }; + + store.idmail = { + type = "sqlite"; + path = "${config.services.idmail.dataDir}/idmail.db"; + query = + let + # Remove comments from SQL and make it single-line + toSingleLineSql = + sql: + lib.concatStringsSep " " ( + lib.forEach (lib.flatten (lib.split "\n" sql)) ( + line: lib.optionalString (builtins.match "^[[:space:]]*--.*" line == null) line + ) + ); + in + { + # "SELECT name, type, secret, description, quota FROM accounts WHERE name = ?1 AND active = true"; + name = toSingleLineSql '' + SELECT + m.address AS name, + 'individual' AS type, + m.password_hash AS secret, + m.address AS description, + 0 AS quota + FROM mailboxes AS m + JOIN domains AS d ON m.domain = d.domain + JOIN users AS u ON m.owner = u.username + WHERE m.address = ?1 + AND m.active = true + AND d.active = true + AND u.active = true + ''; + # "SELECT member_of FROM group_members WHERE name = ?1"; + members = ""; + # "SELECT name FROM emails WHERE address = ?1"; + recipients = toSingleLineSql '' + -- It is important that we return only one value here, but in theory three UNIONed + -- queries are guaranteed to be distinct. This is because a mailbox address + -- and alias address can never be the same, their cross-table uniqueness is guaranteed on insert. + -- The catch-all union can also only return something if @domain.tld is given as a parameter, + -- which is invalid for aliases and mailboxes. + -- + -- Nonetheless, it may be beneficial to allow an alias to override an existing mailbox, + -- so we can have send-only mailboxes which have their incoming mail redirected somewhere else. + -- Therefore, we make sure to order the query by (aliases -> mailboxes -> catch all) and only return the + -- highest priority one. + SELECT name FROM ( + -- Select the target of a matching alias (if any) + -- but make sure that all related parts are active. + SELECT a.target AS name, 1 AS rowOrder + FROM aliases AS a + JOIN domains AS d ON a.domain = d.domain + JOIN ( + -- To check whether the owner is active we need to make a subquery + -- because the owner could be a user or mailbox + SELECT username + FROM users + WHERE active = true + UNION + SELECT m.address AS username + FROM mailboxes AS m + JOIN users AS u ON m.owner = u.username + WHERE m.active = true + AND u.active = true + ) AS u ON a.owner = u.username + WHERE a.address = ?1 + AND a.active = true + AND d.active = true + -- Select the primary mailbox address if it matches and + -- all related parts are active. + UNION + SELECT m.address AS name, 2 AS rowOrder + FROM mailboxes AS m + JOIN domains AS d ON m.domain = d.domain + JOIN users AS u ON m.owner = u.username + WHERE m.address = ?1 + AND m.active = true + AND d.active = true + AND u.active = true + -- Finally, select any catch_all address that would catch this. + -- Again make sure everything is active. + UNION + SELECT d.catch_all AS name, 3 AS rowOrder + FROM domains AS d + JOIN mailboxes AS m ON d.catch_all = m.address + JOIN users AS u ON m.owner = u.username + WHERE ?1 = ('@' || d.domain) + AND d.active = true + AND m.active = true + AND u.active = true + ORDER BY rowOrder, name ASC + LIMIT 1 + ) + ''; + # "SELECT address FROM emails WHERE name = ?1 AND type != 'list' ORDER BY type DESC, address ASC"; + emails = toSingleLineSql '' + -- Return first the primary address, then any aliases. + SELECT address FROM ( + -- Select primary address, if active + SELECT m.address AS address, 1 AS rowOrder + FROM mailboxes AS m + JOIN domains AS d ON m.domain = d.domain + JOIN users AS u ON m.owner = u.username + WHERE m.address = ?1 + AND m.active = true + AND d.active = true + AND u.active = true + -- Select any active aliases + UNION + SELECT a.address AS address, 2 AS rowOrder + FROM aliases AS a + JOIN domains AS d ON a.domain = d.domain + JOIN ( + -- To check whether the owner is active we need to make a subquery + -- because the owner could be a user or mailbox + SELECT username + FROM users + WHERE active = true + UNION + SELECT m.address AS username + FROM mailboxes AS m + JOIN users AS u ON m.owner = u.username + WHERE m.active = true + AND u.active = true + ) AS u ON a.owner = u.username + WHERE a.target = ?1 + AND a.active = true + AND d.active = true + -- Select the catch-all marker, if we are the target. + UNION + -- Order 2 is correct, it counts as an alias + SELECT ('@' || d.domain) AS address, 2 AS rowOrder + FROM domains AS d + JOIN mailboxes AS m ON d.catch_all = m.address + JOIN users AS u ON m.owner = u.username + WHERE d.catch_all = ?1 + AND d.active = true + AND m.active = true + AND u.active = true + ORDER BY rowOrder, address ASC + ) + ''; + # "SELECT address FROM emails WHERE address LIKE '%' || ?1 || '%' AND type = 'primary' ORDER BY address LIMIT 5"; + verify = toSingleLineSql '' + SELECT m.address AS address + FROM mailboxes AS m + JOIN domains AS d ON m.domain = d.domain + JOIN users AS u ON m.owner = u.username + WHERE m.address LIKE '%' || ?1 || '%' + AND m.active = true + AND d.active = true + AND u.active = true + UNION + SELECT a.address AS address + FROM aliases AS a + JOIN domains AS d ON a.domain = d.domain + JOIN ( + -- To check whether the owner is active we need to make a subquery + -- because the owner could be a user or mailbox + SELECT username + FROM users + WHERE active = true + UNION + SELECT m.address AS username + FROM mailboxes AS m + JOIN users AS u ON m.owner = u.username + WHERE m.active = true + AND u.active = true + ) AS u ON a.owner = u.username + WHERE a.address LIKE '%' || ?1 || '%' + AND a.active = true + AND d.active = true + ORDER BY address + LIMIT 5 + ''; + # "SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ?1 AND l.type = 'list' ORDER BY p.address LIMIT 50"; + # XXX: We don't actually expand, but return the same address if it exists since we don't support mailing lists + expand = toSingleLineSql '' + SELECT m.address AS address + FROM mailboxes AS m + JOIN domains AS d ON m.domain = d.domain + JOIN users AS u ON m.owner = u.username + WHERE m.address = ?1 + AND m.active = true + AND d.active = true + AND u.active = true + UNION + SELECT a.address AS address + FROM aliases AS a + JOIN domains AS d ON a.domain = d.domain + JOIN ( + -- To check whether the owner is active we need to make a subquery + -- because the owner could be a user or mailbox + SELECT username + FROM users + WHERE active = true + UNION + SELECT m.address AS username + FROM mailboxes AS m + JOIN users AS u ON m.owner = u.username + WHERE m.active = true + AND u.active = true + ) AS u ON a.owner = u.username + WHERE a.address = ?1 + AND a.active = true + AND d.active = true + ORDER BY address + LIMIT 50 + ''; + # "SELECT 1 FROM emails WHERE address LIKE '%@' || ?1 LIMIT 1"; + domains = toSingleLineSql '' + SELECT domain + FROM domains + WHERE domain = ?1 + ''; + }; + }; + + storage = { + data = "db"; + fts = "db"; + lookup = "db"; + blob = "db"; + directory = "idmail"; + }; + + directory.idmail = { + type = "sql"; + store = "idmail"; + columns = { + name = "name"; + description = "description"; + secret = "secret"; + email = "email"; + #quota = "quota"; + class = "type"; + }; + }; + + resolver = { + type = "system"; + public-suffix = [ + "file://${pkgs.publicsuffix-list}/share/publicsuffix/public_suffix_list.dat" + ]; + }; + + config.resource.spam-filter = "file://${config.services.stalwart-mail.package}/etc/stalwart/spamfilter.toml"; + config.resource.webadmin = "file://${config.services.stalwart-mail.package.webadmin}/webadmin.zip"; + webadmin.path = "/var/cache/stalwart-mail"; + + certificate.default = { + cert = "%{file:${config.security.acme.certs.${domain}.directory}/fullchain.pem}%"; + private-key = "%{file:${config.security.acme.certs.${domain}.directory}/key.pem}%"; + default = true; + }; + + lookup.default.hostname = domain; + server = { + tls = { + certificate = "default"; + ignore-client-order = true; + }; + socket = { + nodelay = true; + reuse-addr = true; + }; + listener = { + smtp = { + protocol = "smtp"; + bind = "[::]:25"; + }; + submissions = { + protocol = "smtp"; + bind = "[::]:465"; + tls.implicit = true; + }; + imaps = { + protocol = "imap"; + bind = "[::]:993"; + tls.implicit = true; + }; + http = { + # jmap, web interface + protocol = "http"; + bind = "[::]:8080"; + url = "https://${domain}"; + use-x-forwarded = true; + }; + sieve = { + protocol = "managesieve"; + bind = "[::]:4190"; + tls.implicit = true; + }; + }; + }; + + imap = { + request.max-size = 52428800; + auth = { + max-failures = 3; + allow-plain-text = false; + }; + timeout = { + authentication = "30m"; + anonymous = "1m"; + idle = "30m"; + }; + rate-limit = { + requests = "20000/1m"; + concurrent = 32; + }; + }; + + auth.dkim.sign = [ + (ifthen "is_local_domain('*', sender_domain)" "['rsa-' + sender_domain, 'ed25519-' + sender_domain]") + (otherwise false) + ]; + + signature = lib.mergeAttrsList ( + lib.forEach mailDomains (domain: { + "ed25519-${domain}" = { + private-key = "%{file:/var/lib/stalwart-mail/dkim/ed25519-${domain}.key}%"; + inherit domain; + selector = "ed_default"; + headers = [ + "From" + "To" + "Date" + "Subject" + "Message-ID" + ]; + algorithm = "ed25519-sha256"; + canonicalization = "relaxed/relaxed"; + set-body-length = false; + report = true; + }; + "rsa-${domain}" = { + private-key = "%{file:/var/lib/stalwart-mail/dkim/rsa-${domain}.key}%"; + inherit domain; + selector = "rsa_default"; + headers = [ + "From" + "To" + "Date" + "Subject" + "Message-ID" + ]; + algorithm = "rsa-sha256"; + canonicalization = "relaxed/relaxed"; + set-body-length = false; + report = true; + }; + }) + ); + + session.extensions = { + pipelining = true; + chunking = true; + requiretls = true; + no-soliciting = ""; + dsn = false; + expn = [ + (is-authenticated true) + (otherwise false) + ]; + vrfy = [ + (is-authenticated true) + (otherwise false) + ]; + future-release = [ + (is-authenticated "30d") + (otherwise false) + ]; + deliver-by = [ + (is-authenticated "365d") + (otherwise false) + ]; + mt-priority = [ + (is-authenticated "mixer") + (otherwise false) + ]; + }; + + session.ehlo = { + require = true; + reject-non-fqdn = [ + (is-smtp true) + (otherwise false) + ]; + }; + + session.rcpt = { + catch-all = true; + relay = [ + (is-authenticated true) + (otherwise false) + ]; + max-recipients = 25; + }; + }; + }; +} diff --git a/flake.lock b/flake.lock index 6237aaf..7e8fdc5 100644 --- a/flake.lock +++ b/flake.lock @@ -115,6 +115,23 @@ } }, "crane": { + "flake": false, + "locked": { + "lastModified": 1727316705, + "narHash": "sha256-/mumx8AQ5xFuCJqxCIOFCHTVlxHkMT21idpbgbm/TIE=", + "owner": "ipetkov", + "repo": "crane", + "rev": "5b03654ce046b5167e7b0bccbd8244cb56c16f0e", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "ref": "v0.19.0", + "repo": "crane", + "type": "github" + } + }, + "crane_2": { "inputs": { "flake-compat": [ "lanzaboote", @@ -147,7 +164,7 @@ "type": "github" } }, - "crane_2": { + "crane_3": { "flake": false, "locked": { "lastModified": 1727316705, @@ -252,7 +269,7 @@ "devshell_3": { "inputs": { "nixpkgs": [ - "nix-topology", + "idmail", "nixpkgs" ] }, @@ -271,6 +288,27 @@ } }, "devshell_4": { + "inputs": { + "nixpkgs": [ + "nix-topology", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1728330715, + "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", + "owner": "numtide", + "repo": "devshell", + "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "devshell_5": { "inputs": { "nixpkgs": [ "nixos-extra-modules", @@ -292,7 +330,7 @@ "type": "github" } }, - "devshell_5": { + "devshell_6": { "inputs": { "nixpkgs": [ "nixp-meta", @@ -313,7 +351,7 @@ "type": "github" } }, - "devshell_6": { + "devshell_7": { "inputs": { "nixpkgs": [ "nixvim", @@ -357,13 +395,37 @@ "dream2nix": { "inputs": { "nixpkgs": [ - "nixp-meta", + "idmail", "nci", "nixpkgs" ], "purescript-overlay": "purescript-overlay", "pyproject-nix": "pyproject-nix" }, + "locked": { + "lastModified": 1732214960, + "narHash": "sha256-ViyEMSYwaza6y55XTDrsRi2K4YKCLsefMTorjWSE27s=", + "owner": "nix-community", + "repo": "dream2nix", + "rev": "a8dac99db44307fdecead13a39c584b97812d0d4", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "dream2nix", + "type": "github" + } + }, + "dream2nix_2": { + "inputs": { + "nixpkgs": [ + "nixp-meta", + "nci", + "nixpkgs" + ], + "purescript-overlay": "purescript-overlay_2", + "pyproject-nix": "pyproject-nix_2" + }, "locked": { "lastModified": 1731424167, "narHash": "sha256-nKKeRwq7mxcW8cBTmPKzSg0DR/inVrtuJudVM81GISU=", @@ -395,6 +457,20 @@ } }, "flake-compat_10": { + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "revCount": 57, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" + } + }, + "flake-compat_11": { "flake": false, "locked": { "lastModified": 1696426674, @@ -410,7 +486,23 @@ "type": "github" } }, - "flake-compat_11": { + "flake-compat_12": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_13": { "flake": false, "locked": { "lastModified": 1673956053, @@ -429,11 +521,11 @@ "flake-compat_2": { "flake": false, "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "owner": "edolstra", "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "type": "github" }, "original": { @@ -493,11 +585,11 @@ "flake-compat_6": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { @@ -507,35 +599,6 @@ } }, "flake-compat_7": { - "locked": { - "lastModified": 1717312683, - "narHash": "sha256-FrlieJH50AuvagamEvWMIE6D2OAnERuDboFDYAED/dE=", - "owner": "nix-community", - "repo": "flake-compat", - "rev": "38fd3954cf65ce6faf3d0d45cd26059e059f07ea", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_8": { - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" - } - }, - "flake-compat_9": { "flake": false, "locked": { "lastModified": 1696426674, @@ -551,6 +614,37 @@ "type": "github" } }, + "flake-compat_8": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_9": { + "locked": { + "lastModified": 1717312683, + "narHash": "sha256-FrlieJH50AuvagamEvWMIE6D2OAnERuDboFDYAED/dE=", + "owner": "nix-community", + "repo": "flake-compat", + "rev": "38fd3954cf65ce6faf3d0d45cd26059e059f07ea", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "flake-compat", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -570,6 +664,24 @@ } }, "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1730504689, + "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "506278e768c2a08bec68eb62932193e341f55c90", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { "inputs": { "nixpkgs-lib": [ "lanzaboote", @@ -590,9 +702,9 @@ "type": "github" } }, - "flake-parts_3": { + "flake-parts_4": { "inputs": { - "nixpkgs-lib": "nixpkgs-lib_2" + "nixpkgs-lib": "nixpkgs-lib_3" }, "locked": { "lastModified": 1730504689, @@ -608,7 +720,7 @@ "type": "github" } }, - "flake-parts_4": { + "flake-parts_5": { "inputs": { "nixpkgs-lib": [ "nixpkgs-wayland", @@ -630,7 +742,7 @@ "type": "github" } }, - "flake-parts_5": { + "flake-parts_6": { "inputs": { "nixpkgs-lib": [ "nixvim", @@ -820,7 +932,7 @@ "nixvim", "flake-compat" ], - "gitignore": "gitignore_6", + "gitignore": "gitignore_7", "nixpkgs": [ "nixvim", "nixpkgs" @@ -867,6 +979,28 @@ } }, "gitignore_2": { + "inputs": { + "nixpkgs": [ + "idmail", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gitignore_3": { "inputs": { "nixpkgs": [ "lanzaboote", @@ -888,7 +1022,7 @@ "type": "github" } }, - "gitignore_3": { + "gitignore_4": { "inputs": { "nixpkgs": [ "nix-topology", @@ -910,7 +1044,7 @@ "type": "github" } }, - "gitignore_4": { + "gitignore_5": { "inputs": { "nixpkgs": [ "nixos-extra-modules", @@ -932,7 +1066,7 @@ "type": "github" } }, - "gitignore_5": { + "gitignore_6": { "inputs": { "nixpkgs": [ "nixp-meta", @@ -954,7 +1088,7 @@ "type": "github" } }, - "gitignore_6": { + "gitignore_7": { "inputs": { "nixpkgs": [ "nixvim", @@ -976,7 +1110,7 @@ "type": "github" } }, - "gitignore_7": { + "gitignore_8": { "inputs": { "nixpkgs": [ "pre-commit-hooks", @@ -1076,6 +1210,31 @@ "type": "github" } }, + "idmail": { + "inputs": { + "devshell": "devshell_3", + "flake-parts": "flake-parts_2", + "nci": "nci", + "nixpkgs": [ + "nixpkgs" + ], + "pre-commit-hooks": "pre-commit-hooks_2", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1732405639, + "narHash": "sha256-/amsyYIdOMr3ZUrkbiFYEDtYfNeRw+mWExCisQL2Cj0=", + "owner": "oddlama", + "repo": "idmail", + "rev": "0e7170cc5888f33c3c630390f9601771ae0bb2b8", + "type": "github" + }, + "original": { + "owner": "oddlama", + "repo": "idmail", + "type": "github" + } + }, "impermanence": { "locked": { "lastModified": 1731242966, @@ -1121,15 +1280,15 @@ }, "lanzaboote": { "inputs": { - "crane": "crane", - "flake-compat": "flake-compat_2", - "flake-parts": "flake-parts_2", + "crane": "crane_2", + "flake-compat": "flake-compat_4", + "flake-parts": "flake-parts_3", "flake-utils": "flake-utils_2", "nixpkgs": [ "nixpkgs" ], "pre-commit-hooks-nix": "pre-commit-hooks-nix", - "rust-overlay": "rust-overlay" + "rust-overlay": "rust-overlay_2" }, "locked": { "lastModified": 1682802423, @@ -1149,7 +1308,7 @@ "lib-aggregate": { "inputs": { "flake-utils": "flake-utils_6", - "nixpkgs-lib": "nixpkgs-lib_3" + "nixpkgs-lib": "nixpkgs-lib_4" }, "locked": { "lastModified": 1731845570, @@ -1216,16 +1375,59 @@ "type": "github" } }, + "mk-naked-shell_2": { + "flake": false, + "locked": { + "lastModified": 1681286841, + "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=", + "owner": "yusdacra", + "repo": "mk-naked-shell", + "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd", + "type": "github" + }, + "original": { + "owner": "yusdacra", + "repo": "mk-naked-shell", + "type": "github" + } + }, "nci": { "inputs": { - "crane": "crane_2", + "crane": "crane", "dream2nix": "dream2nix", "mk-naked-shell": "mk-naked-shell", - "nixpkgs": "nixpkgs_2", + "nixpkgs": [ + "idmail", + "nixpkgs" + ], "parts": "parts", - "rust-overlay": "rust-overlay_2", + "rust-overlay": "rust-overlay", "treefmt": "treefmt" }, + "locked": { + "lastModified": 1732342495, + "narHash": "sha256-7qfvmnJQByEtl5bS+rTydLCe3Saz9kMRaJxPCdqb1wQ=", + "owner": "yusdacra", + "repo": "nix-cargo-integration", + "rev": "ae9de2d06519a3bb26b649e1c0d1cfa22c20dc0e", + "type": "github" + }, + "original": { + "owner": "yusdacra", + "repo": "nix-cargo-integration", + "type": "github" + } + }, + "nci_2": { + "inputs": { + "crane": "crane_3", + "dream2nix": "dream2nix_2", + "mk-naked-shell": "mk-naked-shell_2", + "nixpkgs": "nixpkgs_2", + "parts": "parts_2", + "rust-overlay": "rust-overlay_3", + "treefmt": "treefmt_2" + }, "locked": { "lastModified": 1731605339, "narHash": "sha256-O0vWXiC1pBYXgdsKbQGw0Jev8Sc6dxR9Up0NKgIeH9g=", @@ -1263,10 +1465,10 @@ }, "nix-eval-jobs": { "inputs": { - "flake-parts": "flake-parts_4", + "flake-parts": "flake-parts_5", "nix-github-actions": "nix-github-actions", "nixpkgs": "nixpkgs_7", - "treefmt-nix": "treefmt-nix_2" + "treefmt-nix": "treefmt-nix_3" }, "locked": { "lastModified": 1731682758, @@ -1326,10 +1528,10 @@ }, "nix-topology": { "inputs": { - "devshell": "devshell_3", + "devshell": "devshell_4", "flake-utils": "flake-utils_4", "nixpkgs": "nixpkgs", - "pre-commit-hooks": "pre-commit-hooks_2" + "pre-commit-hooks": "pre-commit-hooks_3" }, "locked": { "lastModified": 1730803396, @@ -1362,13 +1564,13 @@ }, "nixos-extra-modules": { "inputs": { - "devshell": "devshell_4", + "devshell": "devshell_5", "flake-utils": "flake-utils_5", "lib-net": "lib-net", "nixpkgs": [ "nixpkgs" ], - "pre-commit-hooks": "pre-commit-hooks_3" + "pre-commit-hooks": "pre-commit-hooks_4" }, "locked": { "lastModified": 1732216602, @@ -1443,12 +1645,12 @@ }, "nixp-meta": { "inputs": { - "devshell": "devshell_5", - "flake-parts": "flake-parts_3", - "nci": "nci", + "devshell": "devshell_6", + "flake-parts": "flake-parts_4", + "nci": "nci_2", "nixpkgs": "nixpkgs_3", - "pre-commit-hooks": "pre-commit-hooks_4", - "treefmt-nix": "treefmt-nix" + "pre-commit-hooks": "pre-commit-hooks_5", + "treefmt-nix": "treefmt-nix_2" }, "locked": { "lastModified": 1731864011, @@ -1505,6 +1707,18 @@ } }, "nixpkgs-lib_3": { + "locked": { + "lastModified": 1730504152, + "narHash": "sha256-lXvH/vOfb4aGYyvFmZK/HlsNsr/0CVWlwYvo2rxJk3s=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz" + } + }, + "nixpkgs-lib_4": { "locked": { "lastModified": 1731805462, "narHash": "sha256-yhEMW4MBi+IAyEJyiKbnFvY1uARyMKJpLUhkczI49wk=", @@ -1552,6 +1766,22 @@ } }, "nixpkgs-stable_2": { + "locked": { + "lastModified": 1730741070, + "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable_3": { "locked": { "lastModified": 1678872516, "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=", @@ -1567,7 +1797,7 @@ "type": "github" } }, - "nixpkgs-stable_3": { + "nixpkgs-stable_4": { "locked": { "lastModified": 1685801374, "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", @@ -1583,22 +1813,6 @@ "type": "github" } }, - "nixpkgs-stable_4": { - "locked": { - "lastModified": 1730741070, - "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-24.05", - "repo": "nixpkgs", - "type": "github" - } - }, "nixpkgs-stable_5": { "locked": { "lastModified": 1730741070, @@ -1615,9 +1829,25 @@ "type": "github" } }, + "nixpkgs-stable_6": { + "locked": { + "lastModified": 1730741070, + "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-wayland": { "inputs": { - "flake-compat": "flake-compat_7", + "flake-compat": "flake-compat_9", "lib-aggregate": "lib-aggregate", "nix-eval-jobs": "nix-eval-jobs", "nixpkgs": [ @@ -1752,9 +1982,9 @@ }, "nixvim": { "inputs": { - "devshell": "devshell_6", - "flake-compat": "flake-compat_8", - "flake-parts": "flake-parts_5", + "devshell": "devshell_7", + "flake-compat": "flake-compat_10", + "flake-parts": "flake-parts_6", "git-hooks": "git-hooks", "home-manager": "home-manager_2", "nix-darwin": "nix-darwin", @@ -1762,7 +1992,7 @@ "nixpkgs" ], "nuschtosSearch": "nuschtosSearch", - "treefmt-nix": "treefmt-nix_3" + "treefmt-nix": "treefmt-nix_4" }, "locked": { "lastModified": 1731780782, @@ -1802,6 +2032,28 @@ } }, "parts": { + "inputs": { + "nixpkgs-lib": [ + "idmail", + "nci", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1730504689, + "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "506278e768c2a08bec68eb62932193e341f55c90", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "parts_2": { "inputs": { "nixpkgs-lib": [ "nixp-meta", @@ -1861,12 +2113,12 @@ "lanzaboote", "flake-utils" ], - "gitignore": "gitignore_2", + "gitignore": "gitignore_3", "nixpkgs": [ "lanzaboote", "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable_2" + "nixpkgs-stable": "nixpkgs-stable_3" }, "locked": { "lastModified": 1681413034, @@ -1885,7 +2137,31 @@ "pre-commit-hooks_2": { "inputs": { "flake-compat": "flake-compat_3", - "gitignore": "gitignore_3", + "gitignore": "gitignore_2", + "nixpkgs": [ + "idmail", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable_2" + }, + "locked": { + "lastModified": 1732021966, + "narHash": "sha256-mnTbjpdqF0luOkou8ZFi2asa1N3AA2CchR/RqCNmsGE=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "3308484d1a443fc5bc92012435d79e80458fe43c", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_3": { + "inputs": { + "flake-compat": "flake-compat_5", + "gitignore": "gitignore_4", "nixpkgs": [ "nix-topology", "nixpkgs" @@ -1909,19 +2185,19 @@ "type": "github" } }, - "pre-commit-hooks_3": { + "pre-commit-hooks_4": { "inputs": { - "flake-compat": "flake-compat_4", + "flake-compat": "flake-compat_6", "flake-utils": [ "nixos-extra-modules", "flake-utils" ], - "gitignore": "gitignore_4", + "gitignore": "gitignore_5", "nixpkgs": [ "nixos-extra-modules", "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable_3" + "nixpkgs-stable": "nixpkgs-stable_4" }, "locked": { "lastModified": 1702456155, @@ -1937,12 +2213,12 @@ "type": "github" } }, - "pre-commit-hooks_4": { + "pre-commit-hooks_5": { "inputs": { - "flake-compat": "flake-compat_6", - "gitignore": "gitignore_5", + "flake-compat": "flake-compat_8", + "gitignore": "gitignore_6", "nixpkgs": "nixpkgs_4", - "nixpkgs-stable": "nixpkgs-stable_4" + "nixpkgs-stable": "nixpkgs-stable_5" }, "locked": { "lastModified": 1731363552, @@ -1958,14 +2234,14 @@ "type": "github" } }, - "pre-commit-hooks_5": { + "pre-commit-hooks_6": { "inputs": { - "flake-compat": "flake-compat_9", - "gitignore": "gitignore_7", + "flake-compat": "flake-compat_11", + "gitignore": "gitignore_8", "nixpkgs": [ "nixpkgs" ], - "nixpkgs-stable": "nixpkgs-stable_5" + "nixpkgs-stable": "nixpkgs-stable_6" }, "locked": { "lastModified": 1731363552, @@ -1983,14 +2259,39 @@ }, "purescript-overlay": { "inputs": { - "flake-compat": "flake-compat_5", + "flake-compat": "flake-compat_2", + "nixpkgs": [ + "idmail", + "nci", + "dream2nix", + "nixpkgs" + ], + "slimlock": "slimlock" + }, + "locked": { + "lastModified": 1728546539, + "narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=", + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "type": "github" + } + }, + "purescript-overlay_2": { + "inputs": { + "flake-compat": "flake-compat_7", "nixpkgs": [ "nixp-meta", "nci", "dream2nix", "nixpkgs" ], - "slimlock": "slimlock" + "slimlock": "slimlock_2" }, "locked": { "lastModified": 1728546539, @@ -2023,6 +2324,23 @@ "type": "github" } }, + "pyproject-nix_2": { + "flake": false, + "locked": { + "lastModified": 1702448246, + "narHash": "sha256-hFg5s/hoJFv7tDpiGvEvXP0UfFvFEDgTdyHIjDVHu1I=", + "owner": "davhau", + "repo": "pyproject.nix", + "rev": "5a06a2697b228c04dd2f35659b4b659ca74f7aeb", + "type": "github" + }, + "original": { + "owner": "davhau", + "ref": "dream2nix", + "repo": "pyproject.nix", + "type": "github" + } + }, "root": { "inputs": { "agenix": "agenix", @@ -2031,6 +2349,7 @@ "disko": "disko", "flake-parts": "flake-parts", "home-manager": "home-manager", + "idmail": "idmail", "impermanence": "impermanence", "lanzaboote": "lanzaboote", "microvm": "microvm", @@ -2045,7 +2364,7 @@ "nixpkgs-octoprint": "nixpkgs-octoprint", "nixpkgs-wayland": "nixpkgs-wayland", "nixvim": "nixvim", - "pre-commit-hooks": "pre-commit-hooks_5", + "pre-commit-hooks": "pre-commit-hooks_6", "spicetify-nix": "spicetify-nix", "stylix": "stylix", "systems": "systems_12", @@ -2053,6 +2372,28 @@ } }, "rust-overlay": { + "inputs": { + "nixpkgs": [ + "idmail", + "nci", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1732328983, + "narHash": "sha256-RHt12f/slrzDpSL7SSkydh8wUE4Nr4r23HlpWywed9E=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "ed8aa5b64f7d36d9338eb1d0a3bb60cf52069a72", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "rust-overlay_2": { "inputs": { "flake-utils": [ "lanzaboote", @@ -2077,7 +2418,7 @@ "type": "github" } }, - "rust-overlay_2": { + "rust-overlay_3": { "inputs": { "nixpkgs": [ "nixp-meta", @@ -2100,6 +2441,30 @@ } }, "slimlock": { + "inputs": { + "nixpkgs": [ + "idmail", + "nci", + "dream2nix", + "purescript-overlay", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688756706, + "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=", + "owner": "thomashoneyman", + "repo": "slimlock", + "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "slimlock", + "type": "github" + } + }, + "slimlock_2": { "inputs": { "nixpkgs": [ "nixp-meta", @@ -2141,7 +2506,7 @@ }, "spicetify-nix": { "inputs": { - "flake-compat": "flake-compat_10", + "flake-compat": "flake-compat_12", "nixpkgs": [ "nixpkgs" ] @@ -2166,7 +2531,7 @@ "base16-fish": "base16-fish", "base16-helix": "base16-helix", "base16-vim": "base16-vim", - "flake-compat": "flake-compat_11", + "flake-compat": "flake-compat_13", "flake-utils": "flake-utils_8", "gnome-shell": "gnome-shell", "home-manager": "home-manager_3", @@ -2438,17 +2803,17 @@ "treefmt": { "inputs": { "nixpkgs": [ - "nixp-meta", + "idmail", "nci", "nixpkgs" ] }, "locked": { - "lastModified": 1730321837, - "narHash": "sha256-vK+a09qq19QNu2MlLcvN4qcRctJbqWkX7ahgPZ/+maI=", + "lastModified": 1732292307, + "narHash": "sha256-5WSng844vXt8uytT5djmqBCkopyle6ciFgteuA9bJpw=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "746901bb8dba96d154b66492a29f5db0693dbfcc", + "rev": "705df92694af7093dfbb27109ce16d828a79155f", "type": "github" }, "original": { @@ -2458,6 +2823,27 @@ } }, "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "idmail", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1732292307, + "narHash": "sha256-5WSng844vXt8uytT5djmqBCkopyle6ciFgteuA9bJpw=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "705df92694af7093dfbb27109ce16d828a79155f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, + "treefmt-nix_2": { "inputs": { "nixpkgs": "nixpkgs_5" }, @@ -2475,7 +2861,7 @@ "type": "github" } }, - "treefmt-nix_2": { + "treefmt-nix_3": { "inputs": { "nixpkgs": [ "nixpkgs-wayland", @@ -2497,7 +2883,7 @@ "type": "github" } }, - "treefmt-nix_3": { + "treefmt-nix_4": { "inputs": { "nixpkgs": [ "nixvim", @@ -2517,6 +2903,28 @@ "repo": "treefmt-nix", "type": "github" } + }, + "treefmt_2": { + "inputs": { + "nixpkgs": [ + "nixp-meta", + "nci", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1730321837, + "narHash": "sha256-vK+a09qq19QNu2MlLcvN4qcRctJbqWkX7ahgPZ/+maI=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "746901bb8dba96d154b66492a29f5db0693dbfcc", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index e4301b7..efe4609 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,10 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixp-meta.url = "git+https://forge.lel.lol/patrick/nixp-meta.git"; + idmail = { + url = "github:oddlama/idmail/"; + inputs.nixpkgs.follows = "nixpkgs"; + }; nixpkgs-octoprint.url = "github:patrickdag/nixpkgs/octoprint-update"; nixpkgs-wayland = { diff --git a/hosts/mailnix/default.nix b/hosts/mailnix/default.nix index 427303d..9df86cd 100644 --- a/hosts/mailnix/default.nix +++ b/hosts/mailnix/default.nix @@ -3,6 +3,8 @@ ../../config/basic ../../config/support/initrd-ssh.nix ../../config/support/zfs.nix + ../../config/services/idmail.nix + ../../config/services/stalwart.nix ./net.nix ./fs.nix diff --git a/hosts/mailnix/net.nix b/hosts/mailnix/net.nix index 9fd06dd..b4daf3f 100644 --- a/hosts/mailnix/net.nix +++ b/hosts/mailnix/net.nix @@ -53,14 +53,14 @@ }; networking.nftables.firewall.zones.untrusted.interfaces = [ "lan01" ]; security.acme.certs = { - # mail_public = { - # domain = config.secrets.secrets.global.domains.mail_public; - # extraDomainNames = [ "*.${config.secrets.secrets.global.domains.mail_public}" ]; - # }; - # mail_private = { - # domain = config.secrets.secrets.global.domains.mail_private; - # extraDomainNames = [ "*.${config.secrets.secrets.global.domains.mail_private}" ]; - # }; + "${config.secrets.secrets.global.domains.mail_public}" = { + domain = config.secrets.secrets.global.domains.mail_public; + extraDomainNames = [ "*.${config.secrets.secrets.global.domains.mail_public}" ]; + }; + "${config.secrets.secrets.global.domains.mail_private}" = { + domain = config.secrets.secrets.global.domains.mail_private; + extraDomainNames = [ "*.${config.secrets.secrets.global.domains.mail_private}" ]; + }; }; environment.persistence."/state".directories = [ { diff --git a/hosts/mailnix/secrets/generated/idmail-mailbox-hash_catch-all.age b/hosts/mailnix/secrets/generated/idmail-mailbox-hash_catch-all.age new file mode 100644 index 0000000..03e9d70 --- /dev/null +++ b/hosts/mailnix/secrets/generated/idmail-mailbox-hash_catch-all.age @@ -0,0 +1,17 @@ +age-encryption.org/v1 +-> X25519 7NpA9hDsF1TwTVRvAKHpovHSUCr0Gg11mzsubZemyDs +3eE/PWJizZWIDMr3Dt6012F6db/nlmhpM8y06eLO48s +-> piv-p256 ZFgiIw Alb1ynSHX7YiSZkhDbId9MGoQeRacJJ9Mv4a64WVxXdE +gTRZ2XC+K6bZ9my7B28oXJGfQ2fvHFZHDTGWfjzQMdA +-> piv-p256 XTQkUA AhrEyyEHX3BxETBIWDmbK5pfzmfcFCmTWX2psLAzGhYS +XFR2JtZJikRDATiZ8eflzShfvrrUMLp00s2+0N54tiI +-> piv-p256 ZFgiIw A/MHWK7H85OTk0JLH8y0t7QHcG4xRNwYwEuWPuVBLojT +Vbwyekt8SwUfJzfyualAekCf/MGW+Igs/ZALTydd9Qc +-> piv-p256 5vmPtQ Av/BlH1sZh++RL4fh2NS2HN7yipM9nLfT90OiRh9Flbj +kzi2VBRvIxbBbze4iBahMGROtnSOKznXzCNS0PR9TG0 +-> N"-grease p, Pb+NMCL ^ +BeBdV3XQgNJVO309KZx1hphkECZLRrCqPmEoR3pEzB4I3L8Q6ur+4ALEy2mLjmSp +Mir7Hdy3Pg +--- Klvxozur3RYybVYWbakGVXiTymaTfOoFXcwnj7hsEAY +믰zRx-r@`֫?kaL53ܘ}Bʡ7-6B»th릂$h&B.7O.kgm5 +PڏkѱW41Gy1% X25519 Ud9UzEUeDmMIb90vOTWVkdDvIcebEwSzI4Ii8M5jAUI +4rloQ7OzT0voyVboOaWLvOxvrlYxtcOY91dt1lq6wtg +-> piv-p256 ZFgiIw Aro3d4Lv0WTRa1OiE1f0hROViqhes5elbt5a+uKCS0y7 +UZFViBihW5si4+JbzN1OyzWDuWiFwWfoVls+EH+EUmk +-> piv-p256 XTQkUA A0mE5ni66UlnsafkVu3MK0N6aTX2UtV+jADROmg4M1aN +cYqc/9CCT1PC3inzqfQvK59MCHHNEtIhpvOvqL7E2nA +-> piv-p256 ZFgiIw AnFFxNY3lsY4fsze7Hm4vAmK7zZKGA4qEfSUH5aIkQ4j +1OwdPteTYQCWrt4IkRhflolMXJ+FUMm91n3p7icqnsc +-> piv-p256 5vmPtQ Amg+62BwmCb9ZQmZ74PzT0/FheaK2OzfyGgbHYcyo5Cl +OnlF+hKq6p91i3Jk+iwYQ2ByRTgmZX57mIAIpMRoCD8 +-> >aAO.fE-grease ' 7nl% c#t R]jw \ No newline at end of file diff --git a/hosts/mailnix/secrets/generated/idmail-user-hash_admin.age b/hosts/mailnix/secrets/generated/idmail-user-hash_admin.age new file mode 100644 index 0000000000000000000000000000000000000000..26bffe7b32cde9a4661bb3fccb7633b5b3acf0d7 GIT binary patch literal 865 zcmZwE&CA<#00(dnf)M9(QZIt^pg!P_n)kGEAWzb?Y11ZY)1*yuTGqTYZ%vvsNfQTx zu%p6IoWfvCQFP!%+McG-EPt z#6?j@4w}krG8EyQGu3kLZ0EdBXZk9V$B+Wks8eUSR43zVvr`ne7$_?bFXg?^!!ivw zB}YKOoGhNSbafVLOpB@da4dVfp1M_qs$9>vOM}4!yzEHIRGc?{qgtGzX~R_tAfJmm zMA+ST;YGx%gpo73yHy{IT>XJIEOax><1X21zLFzeXL zk)CMDw2iydbTT7AJ_68gi+Lt8Dp?6#j~Y8#G+TAd_wAKKCCYqDp*R{F)2TYzBTral z8Bce!vkfmIB1#z==%Ej!IdAr`Q<0;ouh~PtGjD2-nw@8{EhwlpS7dS`6KYu@uyeV z;n&ws4o?c?>EGVG2r~~J)=BvGKl*D|51v20{bTao>fbxh1h X25519 wbI8kJZQwT22oltbFeQSWX/Kdb+QPGy/yq5FWi4tURE +QkCRyLTSekF7e+9tFhlr36Rn0vmEZ4Ad7nmYFyDrUIE +-> piv-p256 ZFgiIw AnlFacrcAVmYBv6lWsStu/GyihNjZxqBiRArNYuD6UOk +L8kywowjuGRQ7YshqlitmhvD5DlTKtnhdh0ufeybyEw +-> piv-p256 XTQkUA Anc9FjiKfMpMqjgLBF3gyTdTpdOkmQisBWNSQLPkn7qa +Nc6+wZvNadzIo1pI0Q1xV0LEuWZ5nLMC6e3PwtpviX8 +-> piv-p256 ZFgiIw Aqym6w/aiUpbfE44JfxlV0eAS/naenJCHZNWqzwypHLN +ppB2QXwm0i64LaQnyG9NxjT2MRaI7v6Xj8L3KxB7s8E +-> piv-p256 5vmPtQ ArYm0jb3ONfijVVVWJHVUXfPm+hOJAIVn2+RUAXmIW0e +/rijgC2Iok8VcAbBTeWgQ7KB2u9T7TA03WfE9Ju3UP4 +-> M^g-grease P Jl/S]j +DY4S+rVP2pA38O2f+JWEC5ODoxEXnRdO37COHof6cB85a7a64+FWT1QcAI+P+CEx +Y6FiZ2sYbqwePti5qjUxjg +--- KfDaVg+3cbtTkC/1i2dIORAEjPLrwS5ot4szuOMqMZ0 +u2eN}5Wب>|P)濄XF'l󰘍OQZ![>Yٖ֣c@W;q?'M \ No newline at end of file diff --git a/hosts/mailnix/secrets/generated/resticpasswd.age b/hosts/mailnix/secrets/generated/resticpasswd.age new file mode 100644 index 0000000..ca37fbd --- /dev/null +++ b/hosts/mailnix/secrets/generated/resticpasswd.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> X25519 PdEHSeb3vou1ceHtkrlTbsu5BGWZ2onVCXPCwmW8znk +q8UKSDCiI+oZp+iODHddauFYFbLdc82tEo+Bsu2bgbo +-> piv-p256 ZFgiIw AnZuTRltFip1RHFY1dr+uTJPGbAYFzWpU/HEiZYuMIgz +r0nxJt1eZsXsnCnQ0Ls+kYqyz/PJCUjef9uvziqMqls +-> piv-p256 XTQkUA Au50Oa5SpTyUFjF4W6ETiofTruRqQItE94SmHRPzR4Y2 +T9m1cYYtJr8TQuZYquoJUM+uDeim8llDiMVk3N+kDqk +-> piv-p256 ZFgiIw A/6WS2AnElPTKjwYT6K7CWnL8bolB6HNlQnuqjQ8lKt+ +/0StgIwLSpVT7NyOJLxsPJz9TtfAOZU+qWls8gYkkFE +-> piv-p256 5vmPtQ A92v/hxaXEVRNqrsNhFuKCn5TllPrJCGk1e726IDBVo+ ++yCS8ZD3uO4UWwMhk9xqWSWZ3UGgmBkIAqAtBGKF8Nw +-> a^_IFyLy-grease +smwxe0ZqF7Qc1wsp0rYM20J5FjFiTQV2UpYfUUgt3edM0+iMmBzHG9EPxKjGNmt9 +yogZ0dRKId6mKtaNJeLHUDaCMhIsYAcrhNVGDvG9JOPdhRx9Og0 +--- sG4CDChcMPfQS4gtEDGd+bH/WKNXi5ohWX4NTNkaAi0 +⹎od6?K|$ d(@) ӷ#Eq(!jY`hlL!_ \ No newline at end of file diff --git a/hosts/mailnix/secrets/generated/stalwart-admin-hash.age b/hosts/mailnix/secrets/generated/stalwart-admin-hash.age new file mode 100644 index 0000000000000000000000000000000000000000..a341ba530d35e5b4f3232ee96ad25026592c95ff GIT binary patch literal 860 zcmY+<%gfsY0040DlR2UjMGp=VIV>zbs^3t_B}B+a8~vb-xylIGp!(L9=_2R#WY zf;b(*C-^vcRK$S_6Y=6f+`&0L?J`jm1Z5z2*hNwB@cjipesy>wZ$c@<^p*{le9 zTNvn`15E@)VG?82YYFmkUVsBkf-ykG9<$K{0Sa2NBzV!z>VFv*GZqyPriv ze&fz@ogeIJ4Wg%HhpXnXPR(LsAWuesDe#=pntBi8Gpa3AkLlpk?7}ugDqA5&ev)D+ zDNaZnYK~c^DzCI5I4sIOhY%Sou>swx9zk#vVJR*<6^E9aPIeY7raLTKV}<~f#au;o z%J9l6By4PBjNm!Gub0cZf$DK;`s2{gSV%1W)$ zQYC}Kbgk}%sW{giFDr`FbP5}@%-MtfB9@JJOXGn4( z>^c1`MzqCFH3czxhx2e!_I5}E6 z5h_YnXCH+4mR*coeYu(>eXP%H<`aFfi*Sbz7*og^EpLf-zy^&5lzt5nt0YO`dKUF! zR7O@2>GnFi@s0ty-7c7vm1`?fDRJ{WOEtWc5meBwp;A=SAZ|Q%R3XB^q%EugUg#yK ze*5#%?ZfLQANUaY!#d4A|Lv`#_^0z%Uil#Y;?5gC-uKwu-yb}>IlT8S;rbJwetp@V zfj^vi@8+G?KEJ^*Z#vh;=x6%HSJx-*|MKyVet-Dq@4k9DJNL}Nlij~Q@uXWHBX7O` x`uTUid9i=uo?n=EE X25519 GPymk3LLzkZtbBTHtb5BryUrBoDLImS86IoNS78OqlE +YrRCbTE595ZhRw6VxiBS9lTWB9yP4kijFqSFFdIiUpQ +-> piv-p256 ZFgiIw AsaTyNrw7YuguAOnLv5BFyU2lW61yY++gJmgNq2M+0wq +VGtlEXVaKpzomsLzjEiBtFE3q0emFLHsiWdahPS/WJU +-> piv-p256 XTQkUA A+Jsj+fWxo26HKlA5TOM2nB5WggS6TVRyfhKzNFQxpI2 +RwQp5jlvHByeXPPsov5wMEuZ2pED/iFpVBVXVrKshH4 +-> piv-p256 ZFgiIw A6HBCYbgWEEBsBQpJfiRwu672I9QOI2JF9eSeCztlBKJ +LOcgLvCIGWvs9Vhc1VuvGlYWKbnkJdngVhBDbdoMSLs +-> piv-p256 5vmPtQ A1VVL35NHnMdTROSGAKYG6V32v2D7KVo9eHuRPqejzas +WvdUexTb/Di4mv5owD/3ug2nn8Le/TMgJ+hZYbuED6c +-> M$iT~z2-grease SDOB\mE" Zxfxg kZ\' LB@$4 + +--- 2KhnAceJmwDjVhuEx3saTPzXbDOAjFcpp4DH2lgqsZE +QIu{ʼEv7s^eL%Akih_$r"z0AIq* ScL \ No newline at end of file diff --git a/hosts/mailnix/secrets/generated/stalwartHetznerSshKey.age b/hosts/mailnix/secrets/generated/stalwartHetznerSshKey.age new file mode 100644 index 0000000..e379832 --- /dev/null +++ b/hosts/mailnix/secrets/generated/stalwartHetznerSshKey.age @@ -0,0 +1,16 @@ +age-encryption.org/v1 +-> X25519 rLdYm0p5eFCwaK8u7dz/Qco//mCdnylMwhLo6nX28R4 +0XcnRiSWtCyxn1YISgdt/zVIFKPBPbKOueh+L1f62Fc +-> piv-p256 ZFgiIw A6fWtzhy3ylrbXZG4xjSGRh3Qrk7ZwMS7Fawt0XZvESm +wAMOQQvRnMCJ5DriuLHRsc9zJe5UazJBVvNNy97jJos +-> piv-p256 XTQkUA ArhoKZeRdRGXbHOcLiPcT1AruJEE7hckq7QiGKLfcm9d +YXh4slVMY/U+DfCBW6V/4Uf60Zb8RPyd0PrAHHB8xDE +-> piv-p256 ZFgiIw AsjeXhDC4x+6TPG902gZSlW9qFC0JVoznTVmnpQgip9f +ZxDSKVSBiGCGVE1w+8yZwEJx59DkdFy/6Iq1tbHQ41I +-> piv-p256 5vmPtQ AmlUti+62DpPs4k9HN+ZdKry9pwPjS1HAtnTq9xm1zT1 +zTmFw+xHDQSLkDyVXC8MtlxD5cw/tQ1yK5zlYoDKv8Y +-> w0-grease /mVZ/4hd jq'R +fvJoC6ucvHgsXQysHHQhXQQ3TMUhFIPpSHwOURHSHn/+9qFVd02Ey0DWl9LujA +--- 5VjQP6nmIwBXtA/0+zL+EQt9eZHtyp6oD6u5IPgW1s8 +0s((ىX5UJp}:yAr_ã6 +XDF9֟?1]4{Ehƍꔿ&~SA֭ S dVJeS0/WGp^fB"^ӶԂN:EiB޷lK4I1'K2>^ݽL)x\q.#.Yr8scNRQ66 xZ)عBv)ʞ4" $K d Mp*=Ρ垫Os3CE#;!..4IT{fTlѺ{ձbxHn7^ˏ_%5fqPLl`0?-!uEJ[%bENWC,E11a1{EPcXTsB m]RH"#vaXA(qc`՟&Oap%` \ No newline at end of file diff --git a/secrets/secrets.nix.age b/secrets/secrets.nix.age index 96c9c4e5d520d73ab05b548121eb0f91fe63eedf..2afb9d675338e8dbb9eede253ca5f422025a1667 100644 GIT binary patch literal 8022 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 YC_cl<=nKIFS}LzU6L826)T4f{GISy*3jhEB literal 7968 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