garuda-mail (Netcup VPS)
General
This system mainly consists of the simple-nixos-mailserver. Its only purpose is providing a mail service to team members. The current config looks like this. In case of issues, the documentation can be consulted.
Mail server setup
The mail server details are as follows:
- host:
mail.garudalinux.net
- username: full email address
- password: given password
- incoming: IMAP via
993
(SSL) - outgoing: SMTP via
587/465
(STARTTLS/SSL)
Additionally, it is possible to make use of the Roundcube-powered web interface.
Roundcube
Roundcube is used to provide a web interface for our mail accounts. It features a few plugins to enhance the general user experience.
Plugins
- attachment_reminder - reminds about forgotten attachments
- authres_status - checks for whether SPF/DKIM/DMARC match the sending domain
- carddav - allows adding a CardDAV contact book as source (eg. Nextcloud)
- contextmenu - adds a right click context menu to the most pages
- custom_from - allows customizing from address
- managesieve - allows managing Sieve rules, which automatically sort incoming mails based on rules
- newmail_notifier - new mail notifier for desktops
- persistent_login - allows storing a persistent login cookie for no more login prompts
- thunderbird_labels - shows Thunderbird labels
- zipdownload - allows downloading all attachments at once
Backups
Backups are happening daily via Borg. A Hetzner storage box is used to store multiple generations of backups.
Creating a new user
A new user can be created be adding a new loginAccounts
value and supplying the password via secrets
.
We make use of hashedPasswordFile
, therefore, new hashes can be generated by
running nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
. Add it to the secrets
, then execute deploy
and apply
.
Remember to commit both changes.
Issues and their solution
Local DNS resolver failing to start
Simple NixOS mail server runs a local DNS server to prevent the log filling up with junk (source). There can be cases of the persisted files need to be deleted in order for the service to recover from dumping core. See this issue for reference.
Nix expression
{ config
, lib
, pkgs
, ...
}:
let
authres_status = pkgs.roundcubePlugins.roundcubePlugin rec {
pname = "authres_status";
version = "0.6.3";
src = pkgs.fetchzip {
url = "https://github.com/pimlie/authres_status/archive/refs/tags/${version}.zip";
hash = "sha256-WebJiN0vRkvc0AKvMm+inK3FY37R04q3y/0rFoiUW6A=";
};
};
in
{
imports = [
../modules
./garuda-mail/hardware-configuration.nix
];
# Base configuration
networking.interfaces.ens3.ipv4.addresses = [{
address = "94.16.112.218";
prefixLength = 22;
}];
networking.hostName = "garuda-mail";
networking.defaultGateway = "94.16.112.3";
# GRUB
boot.loader.grub.devices = [ "/dev/vda" ];
# Backup configurations to Hetzner storage box
programs.ssh.macs = [ "hmac-sha2-512" ];
services.borgbackup.jobs = {
backupToHetzner = {
compression = "auto,zstd";
doInit = true;
encryption = {
mode = "repokey-blake2";
passCommand = "cat /var/garuda/secrets/backup/repo_key";
};
environment = {
BORG_RSH = "ssh -i /var/garuda/secrets/backup/ssh_garuda-mail -p 23";
};
paths = [ config.mailserver.mailDirectory "/var/dkim" ];
prune.keep = {
within = "1d";
daily = 5;
weekly = 2;
monthly = 1;
};
repo = "[email protected]:./garuda-mail";
startAt = "daily";
};
};
# NixOS Mailserver
mailserver = {
certificateScheme = "acme-nginx";
dmarcReporting = {
domain = "garudalinux.org";
enable = true;
organizationName = "Garuda Linux";
};
domains = [ "garudalinux.org" "chaotic.cx" "dr460nf1r3.org" ];
enable = true;
enableManageSieve = true;
# Forwards (mostly chaotic.cx only)
forwards =
{
"[email protected]" = "[email protected]";
"[email protected]" = "[email protected]";
"[email protected]" = "[email protected]";
"[email protected]" = "[email protected]";
};
fqdn = "mail.garudalinux.net";
fullTextSearch = {
enable = true;
enforced = "body";
indexAttachments = true;
memoryLimit = 512;
};
# To create the password hashes, use nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
loginAccounts = {
# garudalinux.org
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/cloudatgl";
sendOnly = true;
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/complaintsatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/dr460nf1r3atgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/filoatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/gitlabatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/mastodonatgl";
sendOnly = true;
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/namanatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/noreplyatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/rohitatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/securityatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/sgsatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/spam-reportsatgl";
};
"[email protected]" = {
aliases = [
"[email protected]"
"[email protected]"
"[email protected]"
"[email protected]"
];
hashedPasswordFile = "/var/garuda/secrets/mail/teamatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/tneatgl";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/yorperatgl";
};
# chaotic.cx
"[email protected]" = {
aliases = [ "[email protected]" ];
hashedPasswordFile = "/var/garuda/secrets/mail/actionsatcx";
};
"[email protected]" = {
aliases = [
"[email protected]"
"[email protected]"
"[email protected]"
];
hashedPasswordFile = "/var/garuda/secrets/mail/nicoatcx";
};
# dr460nf1r3.org
"[email protected]" = {
aliases = [ "@dr460nf1r3.org" ];
catchAll = [ "dr460nf1r3.org" ];
hashedPasswordFile = "/var/garuda/secrets/mail/nicoatdf";
};
"[email protected]" = {
hashedPasswordFile = "/var/garuda/secrets/mail/testatdf";
};
};
indexDir = "/var/lib/dovecot/indices";
monitoring = {
alertAddress = "[email protected]";
enable = true;
};
rebootAfterKernelUpgrade.enable = true;
};
# Fix dovecot errors caused by failed scudo allocations
environment.memoryAllocator.provider = lib.mkForce "libc";
# Postmaster alias
services.postfix.postmasterAlias = "[email protected]";
# Web UI
services.roundcube = {
enable = true;
# this is the url of the vhost, not necessarily the same as the fqdn of
# the mailserver
hostName = "mail.garudalinux.net";
extraConfig = ''
# starttls needed for authentication, so the fqdn required to match
# the certificate
$config['smtp_server'] = "tls://${config.mailserver.fqdn}";
$config['smtp_user'] = "%u";
$config['smtp_pass'] = "%p";
'';
package = pkgs.roundcube.withPlugins (
plugins: [
authres_status
plugins.carddav
plugins.contextmenu
plugins.custom_from
plugins.persistent_login
plugins.thunderbird_labels
]
);
plugins = [
"attachment_reminder" # Roundcube internal plugin
"authres_status"
"carddav"
"contextmenu"
"custom_from"
"managesieve" # Roundcube internal plugin
"newmail_notifier" # Roundcube internal plugin
"persistent_login"
"thunderbird_labels"
"zipdownload" # Roundcube internal plugin
];
};
# At least try to prevent the insane spam of login attempts
services.openssh.ports = [ 1022 ];
# This mostly sends annoying notifications because SSH port is non-default
services.monit.enable = lib.mkForce false;
system.stateVersion = "22.05";
}