diff --git a/configs/common.nix b/configs/common.nix index f7ad2d4..f02a143 100644 --- a/configs/common.nix +++ b/configs/common.nix @@ -70,6 +70,8 @@ in neovim ripgrep wget + + ghostty.terminfo ]; hm = { diff --git a/global/utils.nix b/global/utils.nix index d89af09..6946e12 100644 --- a/global/utils.nix +++ b/global/utils.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +{ lib, pkgs, ... }: { setupSecrets = _config: @@ -96,4 +96,20 @@ } ]; }; + + # shamelessly stolen from soopyc's gensokyo + mkNginxFile = + { + filename ? "index.html", + content, + status ? 200, + }: + { + # gets the store path of the directory in which the file is contained + # we have to use writeTextDir because we don't want to expose the whole nix store to nginx + # and because you can't just return an absolute path to a file + alias = builtins.toString (pkgs.writeTextDir filename content) + "/"; + tryFiles = "${filename} =${builtins.toString status}"; + }; + } diff --git a/justfile b/justfile index b5d6a80..dc7acaa 100644 --- a/justfile +++ b/justfile @@ -19,7 +19,7 @@ boot *args: deploy system user="leo": #!/usr/bin/env bash set -euxo pipefail - flake=$(nix eval --impure --raw --expr "(builtins.getFlake \"$PWD\").outPath") + flake=$(nix eval --impure --raw --expr "(builtins.getFlake \"git+file://$PWD\").outPath") nix copy "$flake" --to "ssh://{{user}}@{{system}}" # -R/--bypass-root-check is needed because of a Git CVE regression in Nix 2.20 # See NixOS/nix#10202, viperML/nh#200 diff --git a/programs/games.nix b/programs/games.nix index 1f08d5d..4cee1ff 100644 --- a/programs/games.nix +++ b/programs/games.nix @@ -22,6 +22,7 @@ steam = { enable = true; gamescopeSession.enable = true; + extraCompatPackages = [ pkgs.proton-ge-bin ]; }; gamemode.enable = true; diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 32c3e24..295e53f 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -41,4 +41,6 @@ in "etna/vmauthEnv.age".publicKeys = main ++ [ etna ]; "etna/upsdUserPass.age".publicKeys = main ++ [ etna ]; "etna/cobaltTokens.age".publicKeys = main ++ [ etna ]; + + "vesuvio/maddyEnv.age".publicKeys = main ++ [ vesuvio ]; } diff --git a/secrets/vesuvio/maddyEnv.age b/secrets/vesuvio/maddyEnv.age new file mode 100644 index 0000000..b892d73 --- /dev/null +++ b/secrets/vesuvio/maddyEnv.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3cUNLWjYzZnV2eHFoT3cr +ZjVnd1ZiTVN6UXNGMmZvOVhoVW9VUFUxc2xRCkFtWjVPWGJRaDliWWZJbHZGU3k0 +TmVFZWdKcUpDeVNRcHk2ZXkvNENrcGsKLT4gWDI1NTE5IFgyenhNdWZkcnpPa2pW +ekN5bXRWVDdIUk41c01uNHJFMVlkR1lBenM1QmMKZnBOeERqMWxHU25qVklWYjlW +WElLcHVkMDlyRk5iSUd0NnVoeGpEZFZsVQotPiBYMjU1MTkgbCtsUHpjWUQrZFdL +ckZKN0ozNlBrUmprV0drbElDMGNSaEdnU0swV1JsSQpGVVBHQVgwd2tiaC9wOXNH +dXN6a1I4U0IwdnR1Mmh1OVo2Z2Nka2tBTXBnCi0+IFgyNTUxOSBtQWdSVUJBRzhz +dHZhYVMyUUZPUG1sbm1Nbk1Tb2RpbURnQVI0WUFzUlNFCnJ0ekpvdjBxaXRyR2pl +amE1VFl0SkxCUEF6SzhCN2JRcWY2OWpMWkFsNjAKLS0tIFNpT21weEQwUjQ3VzVj +ZjVyaUZHOVRYU0lrYlVORDROM2tJbGlJbTdxYjQKsk04W9FOBWj2it7o+ecEM72l +ezacJhtUWObiYe5PiMAaumhINJVr8GN7HYRgzTAAIlsn05aTT5WkYgwJQ9XRJphb +cSx24vSfrZ5BGoizwg== +-----END AGE ENCRYPTED FILE----- diff --git a/systems/default.nix b/systems/default.nix index 92a0646..c50ebdc 100644 --- a/systems/default.nix +++ b/systems/default.nix @@ -1,10 +1,11 @@ { lib, + pkgs, inputs, ... }: let - _utils = import ../global/utils.nix { inherit lib; }; + _utils = import ../global/utils.nix { inherit lib pkgs; }; toSystem = name: diff --git a/systems/etna/default.nix b/systems/etna/default.nix index 2a51dbd..4f96daa 100644 --- a/systems/etna/default.nix +++ b/systems/etna/default.nix @@ -1,6 +1,5 @@ { lib, - pkgs, config, _utils, ... @@ -28,6 +27,10 @@ in secrets.generate cfTunnelSecret.generate + # essential configs, do not remove + ./postgresql.nix + + # services ./cobalt.nix ./dendrite.nix ./forgejo.nix @@ -59,11 +62,6 @@ in openssh.openFirewall = true; nginx.enable = true; - postgresql = { - enable = true; - package = pkgs.postgresql_16; - }; - frp = { enable = true; role = "client"; diff --git a/systems/etna/nextcloud.nix b/systems/etna/nextcloud.nix index 0dbf787..d48e1ca 100644 --- a/systems/etna/nextcloud.nix +++ b/systems/etna/nextcloud.nix @@ -28,6 +28,7 @@ in config = { adminpassFile = adminPass.path; + dbtype = "sqlite"; }; }; } diff --git a/systems/etna/postgresql.nix b/systems/etna/postgresql.nix new file mode 100644 index 0000000..7f1f1e2 --- /dev/null +++ b/systems/etna/postgresql.nix @@ -0,0 +1,27 @@ +{ pkgs, ... }: +{ + services = { + postgresql = { + enable = true; + package = pkgs.postgresql_16; + + settings.port = 5432; + enableTCPIP = true; + + ensureDatabases = [ + "maddy" + ]; + + authentication = '' + host maddy maddy vesuvio.fossa-macaroni.ts.net scram-sha-256 + ''; + }; + + postgresqlBackup = { + enable = true; + backupAll = true; + compression = "zstd"; + location = "/data/backups/postgresql"; + }; + }; +} diff --git a/systems/vesuvio/mail.nix b/systems/vesuvio/mail.nix index 7d40abd..e16b024 100644 --- a/systems/vesuvio/mail.nix +++ b/systems/vesuvio/mail.nix @@ -1,14 +1,32 @@ -{ config, ... }: +{ config, _utils, ... }: let certName = "mail.c.uku3lig.net"; certLocation = config.security.acme.certs.${certName}.directory; + + env = _utils.setupSingleSecret config "maddyEnv" { }; in { + imports = [ env.generate ]; + security.acme.certs.${certName} = { group = config.services.maddy.group; extraLegoRenewFlags = [ "--reuse-key" ]; # soopyc said its more secure }; + services.nginx.virtualHosts."mta-sts.uku3lig.net" = { + forceSSL = true; + useACMEHost = certName; + locations."/.well-known/" = _utils.mkNginxFile { + filename = "mta-sts.txt"; + content = '' + version: STSv1 + mode: enforce + mx: mx1.uku3lig.net + max_age: 604800 + ''; + }; + }; + services.maddy = { enable = true; hostname = "mx1.uku3lig.net"; @@ -29,7 +47,171 @@ in }; config = '' + ## common stuff + auth.pass_table local_authdb { + table sql_table { + driver postgres + dsn "host=etna password={env:POSTGRES_PASSWORD} dbname=maddy sslmode=disable" + table_name passwords + } + } + + storage.imapsql local_mailboxes { + driver postgres + dsn "host=etna password={env:POSTGRES_PASSWORD} dbname=maddy sslmode=disable" + # TODO: imap_filter https://maddy.email/reference/storage/imap-filters/ + } + + # chain of steps applied to recipients + # each step is a lookup table + table.chain local_rewrites { + # this removes the +suffix part from the address + optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3" + optional_step static { + entry postmaster postmaster@$(primary_domain) + } + } + + ## message reception + + msgpipeline local_routing { + # TODO: checks (rspamd) + + modify { + replace_rcpt &local_rewrites + } + + # catch-all setup inspired by https://github.com/foxcpp/maddy/issues/243#issuecomment-1406567636 + # we don't have a destination_in clause because there is only one imap account + destination $(local_domains) { + modify { + replace_rcpt regexp ".*" "hi@uku.moe" + } + + deliver_to &local_mailboxes + } + + default_destination { + reject 550 5.1.1 "User doesn't exist" + } + } + + smtp tcp://0.0.0.0:25 { + limits { + # Up to 20 msgs/sec across max. 10 SMTP connections. + all rate 20 1s + all concurrency 10 + } + + dmarc yes + checks { + require_mx_record + dkim + spf + } + + source $(local_domains) { + reject 501 5.1.8 "Use internal submission port for outgoing SMTP" + } + + default_source { + destination postmaster $(local_domains) { + deliver_to &local_routing + } + default_destination { + reject 550 5.1.1 "User doesn't exist" + } + } + } + + ## message sending + + target.remote &outbound_delivery { + limits { + # Up to 20 msgs/sec across max. 10 SMTP connections for each recipient domain. + destination rate 20 1s + destination concurrency 10 + } + + mx_auth { + dane + mtasts + local_policy { + min_tls_level encrypted + min_mx_level none + } + } + } + + target.queue remote_queue { + target &outbound_delivery + + # sender domain for DSNs (delivery status notifications) + autogenerated_msg_domain $(primary_domain) + + # pipeline to know where to send DSNs + bounce { + destination postmaster $(local_domains) { + deliver_to &local_routing + } + default_destination { + reject 550 5.0.0 "Refusing to send DSNs to non-local addresses" + } + } + } + + submission tls://0.0.0.0:465 tcp://0.0.0.0:587 { + limits { + # Up to 50 msgs/sec across any amount of SMTP connections. + all rate 50 1s + } + + auth &local_authdb + + source $(local_domains) { + # make sure the sender is allowed to send from this server + # local_rewrites allows us to use aliases as sender + check { + authorize_sender { + prepare_email &local_rewrites + user_to_email identity + } + } + + # just loop back if we are sending an email to ourselves + destination postmaster $(local_domains) { + deliver_to &local_routing + } + + default_destination { + modify { + dkim $(primary_domain) $(local_domains) default + } + + deliver_to &remote_queue + } + } + + default_source { + reject 501 5.1.8 "Non-local sender domain" + } + } + + ## IMAP + imap tls://0.0.0.0:993 { + auth &local_authdb + storage &local_mailboxes + } ''; }; + + systemd.services.maddy.serviceConfig.EnvironmentFile = env.path; + + networking.firewall.allowedTCPPorts = [ + 25 # smtp + 465 # submissions + 587 # submission (starttls) + 993 # imaps + ]; } diff --git a/systems/vesuvio/nginx.nix b/systems/vesuvio/nginx.nix index 8b0f506..4272d5e 100644 --- a/systems/vesuvio/nginx.nix +++ b/systems/vesuvio/nginx.nix @@ -1,5 +1,13 @@ { services.nginx.virtualHosts = { + # default server + "vps.uku3lig.net" = { + default = true; + addSSL = true; + enableACME = true; + locations."/".return = "404"; + }; + # immich "im.uku.moe" = { forceSSL = true;