move mailserver into its own container

This commit is contained in:
Chaos 2023-08-09 15:11:04 +01:00
parent 1f317b2e15
commit abe8e2c156
No known key found for this signature in database
13 changed files with 276 additions and 27 deletions

View file

@ -0,0 +1,144 @@
{
tree,
lib,
inputs,
config,
pkgs,
...
}: let
container-addresses = import ../../data/container-addresses.nix {};
hostIP = container-addresses.host;
containerIP = container-addresses.containers.mail;
ports = [
# SMTP
25
# Submission
587
# Submission w/ SSL
465
# IMAP
143
# IMAP w/ SSL
993
# Sieve
4190
];
# Using secrets from Host
secrets = config.services.secrets.secrets;
secrets_list = [
"mail_restic_password"
"mail_restic_env"
"private_mail_aliases"
"chaos_mail_passwd"
"system_mail_passwd"
];
shared_files = [
"/var/lib/acme/mail.owo.monster/fullchain.pem"
"/var/lib/acme/mail.owo.monster/key.pem"
];
in {
containers.mail = {
autoStart = true;
privateNetwork = true;
hostAddress = hostIP;
localAddress = containerIP;
bindMounts = lib.mkMerge [
(lib.mkMerge (lib.forEach secrets_list (secret_name: let
path = "${secrets.${secret_name}.path}";
in {
"${path}" = {
hostPath = "${path}";
};
})))
(lib.mkMerge (lib.forEach shared_files (file: {
"${file}" = {
hostPath = "${file}";
};
})))
];
config = {
config,
pkgs,
...
}: {
_module.args = {
inherit inputs;
inherit tree;
host_secrets = secrets;
};
imports = with tree;
[
profiles.base
inputs.home-manager-unstable.nixosModules.home-manager
hosts.hetzner-vm.modules.mailserver
profiles.sshd
modules.nixos.secrets
users.root
]
++ (with hosts.hetzner-vm.containers.mail; [
profiles.mailserver
profiles.restic
]);
# For Shared Secrets
systemd.tmpfiles.rules = [
"d ${config.services.secrets.secretsDir} - root root"
"d /var/lib/acme - root root"
"d /var/lib/acme/mail.owo.monster - root root"
];
networking.firewall = {
enable = true;
allowedTCPPorts = [22] ++ ports;
};
home-manager.users.root = {
imports = with tree; [home.base home.dev.small];
home.stateVersion = "23.05";
};
# Manually configure nameserver. Using resolved inside the container seems to fail
# currently
environment.etc."resolv.conf".text = "nameserver 8.8.8.8";
system.stateVersion = "23.05";
};
};
# users for secrets
users.users."dovecot2" = {
uid = config.ids.uids.dovecot2;
group = "dovecot2";
};
users.groups."dovecot2".gid = config.ids.gids.dovecot2;
# ssl for mail
services.nginx = {
enable = true;
virtualHosts."mail.owo.monster" = {
serverName = "mail.owo.monster";
serverAliases = ["owo.monster"];
forceSSL = true;
enableACME = true;
acmeRoot = "/var/lib/acme/acme-challenge";
# also being used for webmail
locations."/" = {
proxyPass = "http://unix:/var/lib/nixos-containers/mail/var/sockets/roundcube.sock";
};
};
};
networking.nat.forwardPorts = lib.forEach ports (
port: {
sourcePort = port;
destination = "${containerIP}\:${toString port}";
}
);
}

View file

@ -0,0 +1,61 @@
{host_secrets, ...}: let
secrets = host_secrets;
in {
config.mailserver = {
enable = true;
fqdn = "mail.owo.monster";
domains = ["owo.monster"];
ssl_config = {
useACME = false;
cert = "/var/lib/acme/mail.owo.monster/fullchain.pem";
key = "/var/lib/acme/mail.owo.monster/key.pem";
};
enable_roundcube = true;
force_roundcube_ssl = false;
force_roundcube_acme = false;
debug_mode = true;
extra_roundcube_config = ''
$config['session_lifetime'] = (60 * 24 * 7 * 2); # 2 Weeks
$config['product_name'] = 'Chaos Mail';
$config['username_domain'] = "owo.monster";
$config['username_domain_forced'] = true;
'';
extra_aliases_file = "${secrets.private_mail_aliases.path}";
accounts = {
"chaos@owo.monster" = {
name = "chaos@owo.monster";
passwordFile = "${secrets.chaos_mail_passwd.path}";
aliases = [
"all@owo.monster"
"chaoticryptidz@owo.monster"
];
sieveScript = null;
};
"system@owo.monster" = {
name = "system@owo.monster";
passwordFile = "${secrets.system_mail_passwd.path}";
aliases = [];
sieveScript = null;
};
};
};
config.systemd.tmpfiles.rules = [
"d /var/sockets - nginx nginx"
];
config.systemd.services.nginx.serviceConfig.ReadWritePaths = [
"/var/sockets"
];
config.services.nginx.virtualHosts."mail.owo.monster" = {
extraConfig = "listen unix:/var/sockets/roundcube.sock;";
};
}

View file

@ -1,43 +1,33 @@
{
lib,
config,
pkgs,
config,
lib,
host_secrets,
...
}: let
secrets = config.services.secrets.secrets;
secrets = host_secrets;
mail_config = config.mailserver;
backupPrepareCommand = "${
(pkgs.writeShellScriptBin "backupPrepareCommand" ''
systemctl start ${
lib.concatStringsSep " "
(lib.forEach config.services.postgresqlBackup.databases
(db: "postgresqlBackup-${db}"))
} --wait
systemctl start postgresqlBackup-roundcube --wait
'')
}/bin/backupPrepareCommand";
in {
environment.systemPackages = with pkgs; [
restic
(pkgs.writeShellScriptBin "restic-hetzner-vm" ''
(pkgs.writeShellScriptBin "restic-mail" ''
env \
RESTIC_PASSWORD_FILE=${secrets.restic_password.path} \
$(cat ${secrets.restic_env.path}) \
RESTIC_PASSWORD_FILE=${secrets.mail_restic_password.path} \
$(cat ${secrets.mail_restic_env.path}) \
${pkgs.restic}/bin/restic $@
'')
];
services.restic.backups.hetzner-vm = {
services.restic.backups.mail = {
user = "root";
paths = [
"/var/lib/acme"
# Quassel & Invidious
"/var/backup/postgresql"
"/home/quassel/.config/quassel-irc.org"
# mail
mail_config.vmail_config.directory
mail_config.sieve_directory
mail_config.dkim_directory
@ -46,12 +36,12 @@ in {
# repository is overrided in environmentFile to contain auth
# make sure to keep up to date when changing repository
repository = "rest:https://storage-restic.owo.monster/HetznerVM";
passwordFile = "${secrets.restic_password.path}";
environmentFile = "${secrets.restic_env.path}";
repository = "rest:https://storage-restic.owo.monster/Mail";
passwordFile = "${secrets.mail_restic_password.path}";
environmentFile = "${secrets.mail_restic_env.path}";
pruneOpts = [
"--keep-last 20"
"--keep-last 5"
];
timerConfig = {
@ -62,10 +52,11 @@ in {
inherit backupPrepareCommand;
};
services.postgresql.enable = true;
services.postgresqlBackup = {
enable = true;
backupAll = false;
databases = ["postgres" "quassel" "roundcube"];
databases = ["roundcube"];
compression = "zstd";
};
}

View file

@ -9,6 +9,7 @@
rclone_serve_restic_social = 4213;
rclone_serve_restic_quassel = 4214;
rclone_serve_restic_piped = 4215;
rclone_serve_restic_mail = 4216;
rclone_serve_http_music = 4220;
rclone_serve_http_public = 4221;

View file

@ -143,6 +143,17 @@ in {
];
inherit serviceConfig;
}
{
user = "storage";
remote = "StorageBox:Backups/Restic/Mail";
type = "restic";
extraArgs = [
"--addr=0.0.0.0:${toString ports.rclone_serve_restic_mail}"
"--htpasswd=${secrets.restic_mail_htpasswd.path}"
"--baseurl=/Mail/"
];
inherit serviceConfig;
}
];
};
}

View file

@ -101,6 +101,16 @@
'';
};
restic_mail_htpasswd = {
user = "storage";
group = "storage";
fetchScript = ''
username=$(simple_get "/api-keys/storage/restic/Mail" .username)
password=$(simple_get "/api-keys/storage/restic/Mail" .password)
htpasswd -bc "$secretFile" "$username" "$password" 2>/dev/null
'';
};
webdav_main_htpasswd = {
user = "storage";
group = "storage";

View file

@ -101,6 +101,7 @@ in {
"/Social/".proxyPass = "http://${containerIP}:${toString ports.rclone_serve_restic_social}";
"/Quassel/".proxyPass = "http://${containerIP}:${toString ports.rclone_serve_restic_quassel}";
"/Piped/".proxyPass = "http://${containerIP}:${toString ports.rclone_serve_restic_piped}";
"/Mail/".proxyPass = "http://${containerIP}:${toString ports.rclone_serve_restic_mail}";
};
};
}

View file

@ -6,5 +6,6 @@
music = "192.168.100.13";
quassel = "192.168.100.14";
piped = "192.168.100.15";
mail = "192.168.100.16";
};
}

View file

@ -20,9 +20,10 @@
./containers/music/music.nix
./containers/quassel/quassel.nix
./containers/piped/piped.nix
./containers/mail/mail.nix
hosts.hetzner-vm.profiles.restic
hosts.hetzner-vm.profiles.mailserver
#hosts.hetzner-vm.profiles.restic
#hosts.hetzner-vm.profiles.mailserver
hosts.hetzner-vm.profiles.gitlab-static-sites
hosts.hetzner-vm.profiles.wireguard
hosts.hetzner-vm.profiles.nginx-misc

View file

@ -48,6 +48,16 @@ in {
default = "${cfg.fqdn}";
};
force_roundcube_ssl = mkOption {
type = types.bool;
default = true;
};
force_roundcube_acme = mkOption {
type = types.bool;
default = true;
};
accounts = mkOption {
# where name = email for login
type = types.attrsOf (types.submodule ({name, ...}: {

View file

@ -18,5 +18,10 @@ in {
${mail_config.extra_roundcube_config}
'';
};
services.nginx.virtualHosts."${mail_config.roundcube_url}" = {
forceSSL = mail_config.force_roundcube_ssl;
enableACME = mail_config.force_roundcube_acme;
};
};
}

View file

@ -114,6 +114,19 @@
'';
};
mail_restic_password = {
fetchScript = ''
simple_get "/private-public-keys/restic/Mail" .password > $secretFile
'';
};
mail_restic_env = {
fetchScript = ''
RESTIC_USERNAME=$(simple_get "/api-keys/storage/restic/Mail" .username)
RESTIC_PASSWORD=$(simple_get "/api-keys/storage/restic/Mail" .password)
echo "RESTIC_REPOSITORY=rest:https://$RESTIC_USERNAME:$RESTIC_PASSWORD@storage-restic.owo.monster/Mail" > $secretFile
'';
};
restic_password = {
fetchScript = ''
simple_get "/private-public-keys/restic/HetznerVM" .password > $secretFile

View file

@ -37,7 +37,7 @@
networking.firewall.allowedTCPPorts = [8088];
networking.hostName = "lappy-t495";
time.timeZone = "Europe/Germany";
time.timeZone = "Europe/London";
system.stateVersion = "23.05";
}