move mailserver into its own container
This commit is contained in:
parent
1f317b2e15
commit
abe8e2c156
144
hosts/hetzner-vm/containers/mail/mail.nix
Normal file
144
hosts/hetzner-vm/containers/mail/mail.nix
Normal 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}";
|
||||
}
|
||||
);
|
||||
}
|
61
hosts/hetzner-vm/containers/mail/profiles/mailserver.nix
Normal file
61
hosts/hetzner-vm/containers/mail/profiles/mailserver.nix
Normal 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;";
|
||||
};
|
||||
}
|
|
@ -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";
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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}";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
music = "192.168.100.13";
|
||||
quassel = "192.168.100.14";
|
||||
piped = "192.168.100.15";
|
||||
mail = "192.168.100.16";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ...}: {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
networking.firewall.allowedTCPPorts = [8088];
|
||||
|
||||
networking.hostName = "lappy-t495";
|
||||
time.timeZone = "Europe/Germany";
|
||||
time.timeZone = "Europe/London";
|
||||
|
||||
system.stateVersion = "23.05";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue