mirror of
https://github.com/gensokyo-zone/infrastructure.git
synced 2026-02-09 12:29:19 -08:00
services/mail: Init
This commit is contained in:
parent
1e869f3579
commit
377d4b45a2
16 changed files with 865 additions and 118 deletions
|
|
@ -5,7 +5,9 @@
|
|||
profiles.network
|
||||
services.nginx
|
||||
services.keycloak
|
||||
services.roundcube
|
||||
services.openldap
|
||||
services.mail
|
||||
services.hedgedoc
|
||||
services.dnscrypt-proxy
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,15 +13,14 @@ with lib;
|
|||
services.filehost
|
||||
services.gitea
|
||||
services.logrotate
|
||||
services.mail
|
||||
# services.nixos-mailserver
|
||||
services.matrix
|
||||
services.murmur
|
||||
services.nginx
|
||||
services.postgres
|
||||
services.prosody
|
||||
services.radicale
|
||||
# services.radicale
|
||||
services.restic
|
||||
services.roundcube
|
||||
services.syncplay
|
||||
services.taskserver
|
||||
services.vaultwarden
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ in
|
|||
type = types.bool;
|
||||
default = options.nixos.ipv4.address.isDefined;
|
||||
};
|
||||
selfaddress = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
|
|
@ -27,6 +30,9 @@ in
|
|||
type = types.bool;
|
||||
default = options.nixos.ipv6.address.isDefined;
|
||||
};
|
||||
selfaddress = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
|
|
@ -145,6 +151,8 @@ in
|
|||
ipv6.address = mkIf (cfg.tf.ipv6_attr != null) (tf.resources.${config.networking.hostName}.refAttr cfg.tf.ipv6_attr);
|
||||
};
|
||||
nixos = {
|
||||
ipv4.selfaddress = mkIf (tf.state.enable && cfg.tf.ipv4_attr != null) (tf.resources.${config.networking.hostName}.getAttr cfg.tf.ipv4_attr);
|
||||
ipv6.selfaddress = mkIf (tf.state.enable && cfg.tf.ipv6_attr != null) (tf.resources.${config.networking.hostName}.getAttr cfg.tf.ipv6_attr);
|
||||
ipv4.address = mkIf (tf.state.resources ? ${tf.resources.${config.networking.hostName}.out.reference} && cfg.tf.ipv4_attr != null) (tf.resources.${config.networking.hostName}.importAttr cfg.tf.ipv4_attr);
|
||||
ipv6.address = mkIf (tf.state.resources ? ${tf.resources.${config.networking.hostName}.out.reference} && cfg.tf.ipv6_attr != null) (tf.resources.${config.networking.hostName}.importAttr cfg.tf.ipv6_attr);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ in
|
|||
{
|
||||
enable = true;
|
||||
nixos.ipv6.address = mkIf tf.state.enable addr_ipv6_nix;
|
||||
nixos.ipv6.selfaddress = mkIf tf.state.enable addr_ipv6_nix;
|
||||
tf.ipv6.address = tf.resources."${config.networking.hostName}_ipv6".refAttr "ip_address";
|
||||
};
|
||||
};
|
||||
|
|
|
|||
43
config/services/mail/autoconfig.nix
Normal file
43
config/services/mail/autoconfig.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{ pkgs, lib, config, ... }:
|
||||
|
||||
let
|
||||
commonHeaders = lib.concatStringsSep "\n" (lib.filter (line: lib.hasPrefix "add_header" line) (lib.splitString "\n" config.services.nginx.commonHttpConfig));
|
||||
in {
|
||||
services.nginx.virtualHosts = {
|
||||
"autoconfig.kittywit.ch" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
serverAliases = [
|
||||
"autoconfig.dork.dev"
|
||||
];
|
||||
locations = {
|
||||
"= /mail/config-v1.1.xml" = {
|
||||
root = pkgs.writeTextDir "mail/config-v1.1.xml" ''
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<clientConfig version="1.1">
|
||||
<emailProvider id="kittywit.ch">
|
||||
<domain>kittywit.ch</domain>
|
||||
<displayName>kittywit.ch Mail</displayName>
|
||||
<displayShortName>em0lar</displayShortName>
|
||||
<incomingServer type="imap">
|
||||
<hostname>${config.network.addresses.public.domain}</hostname>
|
||||
<port>993</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</incomingServer>
|
||||
<outgoingServer type="smtp">
|
||||
<hostname>${config.network.addresses.public.domain}</hostname>
|
||||
<port>465</port>
|
||||
<socketType>SSL</socketType>
|
||||
<authentication>password-cleartext</authentication>
|
||||
<username>%EMAILADDRESS%</username>
|
||||
</outgoingServer>
|
||||
</emailProvider>
|
||||
</clientConfig>
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,101 +1,10 @@
|
|||
{ config, lib, tf, pkgs, sources, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
domains = [ "kittywitch" "dork" ];
|
||||
users = [ "gitea" "kat" "keycloak" "vaultwarden" ];
|
||||
in
|
||||
{
|
||||
imports = [ sources.nixos-mailserver.outPath ];
|
||||
|
||||
kw.secrets.variables = listToAttrs (map
|
||||
(field:
|
||||
nameValuePair "mail-${field}-hash" {
|
||||
path = "secrets/mail-kittywitch";
|
||||
field = "${field}-hash";
|
||||
})
|
||||
users
|
||||
++ map
|
||||
(domain:
|
||||
nameValuePair "mail-domainkey-${domain}" {
|
||||
path = "secrets/mail-${domain}";
|
||||
field = "notes";
|
||||
})
|
||||
domains);
|
||||
|
||||
deploy.tf.dns.records = mkMerge (map
|
||||
(domain:
|
||||
let
|
||||
zoneGet = domain: if domain == "dork" then "dork.dev." else config.network.dns.zone;
|
||||
in
|
||||
{
|
||||
"services_mail_${domain}_mx" = {
|
||||
zone = zoneGet domain;
|
||||
mx = {
|
||||
priority = 10;
|
||||
target = "${config.network.addresses.public.domain}.";
|
||||
};
|
||||
};
|
||||
|
||||
"services_mail_${domain}_spf" = {
|
||||
zone = zoneGet domain;
|
||||
txt.value = "v=spf1 ip4:${config.network.addresses.public.nixos.ipv4.address} ip6:${config.network.addresses.public.nixos.ipv6.address} -all";
|
||||
};
|
||||
|
||||
"services_mail_${domain}_dmarc" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "_dmarc";
|
||||
txt.value = "v=DMARC1; p=none";
|
||||
};
|
||||
|
||||
"services_mail_${domain}_domainkey" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "mail._domainkey";
|
||||
txt.value = tf.variables."mail-domainkey-${domain}".ref;
|
||||
};
|
||||
})
|
||||
domains);
|
||||
|
||||
secrets.files = listToAttrs (map
|
||||
(user:
|
||||
nameValuePair "mail-${user}-hash" {
|
||||
text = ''
|
||||
${tf.variables."mail-${user}-hash".ref}
|
||||
'';
|
||||
})
|
||||
users);
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = config.network.addresses.public.domain;
|
||||
domains = [ "kittywit.ch" "dork.dev" ];
|
||||
certificateScheme = 1;
|
||||
certificateFile = "/var/lib/acme/public_${config.networking.hostName}/cert.pem";
|
||||
keyFile = "/var/lib/acme/public_${config.networking.hostName}/key.pem";
|
||||
enableImap = true;
|
||||
enablePop3 = true;
|
||||
enableImapSsl = true;
|
||||
enablePop3Ssl = true;
|
||||
enableSubmission = false;
|
||||
enableSubmissionSsl = true;
|
||||
enableManageSieve = true;
|
||||
virusScanning = false;
|
||||
|
||||
# nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2
|
||||
loginAccounts = mkMerge [
|
||||
(listToAttrs (map
|
||||
(user:
|
||||
nameValuePair "${user}@kittywit.ch" {
|
||||
hashedPasswordFile = config.secrets.files."mail-${user}-hash".path;
|
||||
})
|
||||
users))
|
||||
{
|
||||
"kat@kittywit.ch" = {
|
||||
aliases = [ "postmaster@kittywit.ch" ];
|
||||
catchAll = [ "kittywit.ch" "dork.dev" ];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
{ ... }: {
|
||||
imports = [
|
||||
./dns.nix
|
||||
./rspamd.nix
|
||||
./postfix.nix
|
||||
./dovecot.nix
|
||||
./opendkim.nix
|
||||
./autoconfig.nix
|
||||
];
|
||||
}
|
||||
|
|
|
|||
51
config/services/mail/dns.nix
Normal file
51
config/services/mail/dns.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{ config, pkgs, lib, tf, ... }: with lib; let
|
||||
domains = [ "dork" "kittywitch" ];
|
||||
in {
|
||||
|
||||
kw.secrets.variables = listToAttrs (map
|
||||
(domain:
|
||||
nameValuePair "mail-domainkey-${domain}" {
|
||||
path = "secrets/mail-${domain}";
|
||||
field = "notes";
|
||||
})
|
||||
domains);
|
||||
|
||||
deploy.tf.dns.records = mkMerge (map
|
||||
(domain:
|
||||
let
|
||||
zoneGet = domain: if domain == "dork" then "dork.dev." else config.network.dns.zone;
|
||||
in
|
||||
{
|
||||
"services_mail_${domain}_autoconfig_cname" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "autoconfig";
|
||||
cname = { inherit (config.network.addresses.public) target; };
|
||||
};
|
||||
|
||||
"services_mail_${domain}_mx" = {
|
||||
zone = zoneGet domain;
|
||||
mx = {
|
||||
priority = 10;
|
||||
inherit (config.network.addresses.public) target;
|
||||
};
|
||||
};
|
||||
|
||||
"services_mail_${domain}_spf" = {
|
||||
zone = zoneGet domain;
|
||||
txt.value = "v=spf1 ip4:${config.network.addresses.public.tf.ipv4.address} ip6:${config.network.addresses.public.tf.ipv6.address} -all";
|
||||
};
|
||||
|
||||
"services_mail_${domain}_dmarc" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "_dmarc";
|
||||
txt.value = "v=DMARC1; p=none";
|
||||
};
|
||||
|
||||
"services_mail_${domain}_domainkey" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "mail._domainkey";
|
||||
txt.value = tf.variables."mail-domainkey-${domain}".ref;
|
||||
};
|
||||
})
|
||||
domains);
|
||||
}
|
||||
177
config/services/mail/dovecot.nix
Normal file
177
config/services/mail/dovecot.nix
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
{ pkgs, config, lib, tf, ... }: with lib;
|
||||
let
|
||||
ldapConfig = pkgs.writeText "dovecot-ldap.conf" ''
|
||||
uris = ldaps://auth.kittywit.ch:636
|
||||
dn = cn=dovecot,dc=mail,dc=kittywit,dc=ch
|
||||
dnpass = "@ldap-password@"
|
||||
auth_bind = no
|
||||
ldap_version = 3
|
||||
base = ou=users,dc=kittywit,dc=ch
|
||||
user_filter = (&(objectClass=mailAccount)(mail=%u))
|
||||
user_attrs = \
|
||||
quota=quota_rule=*:bytes=%$, \
|
||||
=home=/var/vmail/%d/%n/, \
|
||||
=mail=maildir:/var/vmail/%d/%n/Maildir
|
||||
pass_attrs = mail=user,userPassword=password
|
||||
pass_filter = (&(objectClass=mailAccount)(mail=%u))
|
||||
iterate_attrs = =user=%{ldap:mail}
|
||||
iterate_filter = (objectClass=mailAccount)
|
||||
scope = subtree
|
||||
default_pass_scheme = SSHA
|
||||
'';
|
||||
in
|
||||
{
|
||||
security.acme.certs.dovecot_domains = {
|
||||
inherit (config.network.dns) domain;
|
||||
group = "postfix";
|
||||
dnsProvider = "rfc2136";
|
||||
credentialsFile = config.secrets.files.dns_creds.path;
|
||||
postRun = "systemctl restart dovecot2";
|
||||
extraDomainNames =
|
||||
[
|
||||
config.network.dns.domain
|
||||
config.network.addresses.public.domain
|
||||
"dork.dev"
|
||||
];
|
||||
};
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enableLmtp = true;
|
||||
enablePAM = false;
|
||||
mailLocation = "maildir:/var/vmail/%d/%n/Maildir";
|
||||
mailUser = "vmail";
|
||||
mailGroup = "vmail";
|
||||
extraConfig = ''
|
||||
ssl = yes
|
||||
ssl_cert = </var/lib/acme/dovecot_domains/fullchain.pem
|
||||
ssl_key = </var/lib/acme/dovecot_domains/key.pem
|
||||
local_name kittywit.ch {
|
||||
ssl_cert = </var/lib/acme/dovecot_domains/fullchain.pem
|
||||
ssl_key = </var/lib/acme/dovecot_domains/key.pem
|
||||
}
|
||||
local_name dork.dev {
|
||||
ssl_cert = </var/lib/acme/dovecot_domains/fullchain.pem
|
||||
ssl_key = </var/lib/acme/dovecot_domains/key.pem
|
||||
}
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_cipher_list = EECDH+AESGCM:EDH+AESGCM
|
||||
ssl_prefer_server_ciphers = yes
|
||||
ssl_dh=<${config.security.dhparams.params.dovecot2.path}
|
||||
|
||||
mail_plugins = virtual fts fts_lucene
|
||||
|
||||
service lmtp {
|
||||
user = vmail
|
||||
unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service doveadm {
|
||||
inet_listener {
|
||||
port = 4170
|
||||
ssl = yes
|
||||
}
|
||||
}
|
||||
protocol lmtp {
|
||||
postmaster_address=postmaster@kittywit.ch
|
||||
hostname=${config.network.addresses.public.domain}
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
service auth {
|
||||
unix_listener auth-userdb {
|
||||
mode = 0640
|
||||
user = vmail
|
||||
group = vmail
|
||||
}
|
||||
# Postfix smtp-auth
|
||||
unix_listener /var/lib/postfix/queue/private/auth {
|
||||
mode = 0666
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
userdb {
|
||||
args = /run/dovecot2/ldap.conf
|
||||
driver = ldap
|
||||
}
|
||||
passdb {
|
||||
args = /run/dovecot2/ldap.conf
|
||||
driver = ldap
|
||||
}
|
||||
|
||||
service imap-login {
|
||||
client_limit = 1000
|
||||
service_count = 0
|
||||
inet_listener imaps {
|
||||
port = 993
|
||||
}
|
||||
}
|
||||
|
||||
service managesieve-login {
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
}
|
||||
}
|
||||
protocol sieve {
|
||||
managesieve_logout_format = bytes ( in=%i : out=%o )
|
||||
}
|
||||
plugin {
|
||||
sieve_dir = /var/vmail/%d/%n/sieve/scripts/
|
||||
sieve = /var/vmail/%d/%n/sieve/active-script.sieve
|
||||
sieve_extensions = +vacation-seconds
|
||||
sieve_vacation_min_period = 1min
|
||||
|
||||
fts = lucene
|
||||
fts_lucene = whitespace_chars=@.
|
||||
}
|
||||
|
||||
# If you have Dovecot v2.2.8+ you may get a significant performance improvement with fetch-headers:
|
||||
imapc_features = $imapc_features fetch-headers
|
||||
# Read multiple mails in parallel, improves performance
|
||||
mail_prefetch_count = 20
|
||||
'';
|
||||
modules = [
|
||||
pkgs.dovecot_pigeonhole
|
||||
];
|
||||
protocols = [
|
||||
"sieve"
|
||||
];
|
||||
};
|
||||
|
||||
users.users.vmail = {
|
||||
home = "/var/vmail";
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
uid = 1042;
|
||||
shell = "/run/current-system/sw/bin/nologin";
|
||||
};
|
||||
|
||||
security.dhparams = {
|
||||
enable = true;
|
||||
params.dovecot2 = { };
|
||||
};
|
||||
|
||||
kw.secrets.variables."dovecot-ldap-password" = {
|
||||
path = "services/dovecot";
|
||||
field = "password";
|
||||
};
|
||||
|
||||
secrets.files.dovecot-ldap-password.text = ''
|
||||
${tf.variables.dovecot-ldap-password.ref}
|
||||
'';
|
||||
|
||||
systemd.services.dovecot2.preStart = ''
|
||||
sed -e "s!@ldap-password@!$(<${config.secrets.files.dovecot-ldap-password.path})!" ${ldapConfig} > /run/dovecot2/ldap.conf
|
||||
'';
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
143 # imap
|
||||
993 # imaps
|
||||
4190 # sieve
|
||||
];
|
||||
}
|
||||
71
config/services/mail/opendkim.nix
Normal file
71
config/services/mail/opendkim.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
dkimUser = config.services.opendkim.user;
|
||||
dkimGroup = config.services.opendkim.group;
|
||||
dkimKeyDirectory = "/var/dkim";
|
||||
dkimKeyBits = 1024;
|
||||
dkimSelector = "mail";
|
||||
domains = [ "kittywit.ch" "dork.dev" ];
|
||||
|
||||
createDomainDkimCert = dom:
|
||||
let
|
||||
dkim_key = "${dkimKeyDirectory}/${dom}.${dkimSelector}.key";
|
||||
dkim_txt = "${dkimKeyDirectory}/${dom}.${dkimSelector}.txt";
|
||||
in
|
||||
''
|
||||
if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ]
|
||||
then
|
||||
${pkgs.opendkim}/bin/opendkim-genkey -s "${dkimSelector}" \
|
||||
-d "${dom}" \
|
||||
--bits="${toString dkimKeyBits}" \
|
||||
--directory="${dkimKeyDirectory}"
|
||||
mv "${dkimKeyDirectory}/${dkimSelector}.private" "${dkim_key}"
|
||||
mv "${dkimKeyDirectory}/${dkimSelector}.txt" "${dkim_txt}"
|
||||
echo "Generated key for domain ${dom} selector ${dkimSelector}"
|
||||
fi
|
||||
'';
|
||||
createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert domains);
|
||||
|
||||
keyTable = pkgs.writeText "opendkim-KeyTable"
|
||||
(lib.concatStringsSep "\n" (lib.flip map domains
|
||||
(dom: "${dom} ${dom}:${dkimSelector}:${dkimKeyDirectory}/${dom}.${dkimSelector}.key")));
|
||||
signingTable = pkgs.writeText "opendkim-SigningTable"
|
||||
(lib.concatStringsSep "\n" (lib.flip map domains (dom: "${dom} ${dom}")));
|
||||
|
||||
dkim = config.services.opendkim;
|
||||
args = [ "-f" "-l" ] ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ];
|
||||
in
|
||||
{
|
||||
config = {
|
||||
services.opendkim = {
|
||||
enable = true;
|
||||
selector = dkimSelector;
|
||||
keyPath = dkimKeyDirectory;
|
||||
domains = "csl:${builtins.concatStringsSep "," domains}";
|
||||
configFile = pkgs.writeText "opendkim.conf" (''
|
||||
Canonicalization relaxed/simple
|
||||
UMask 0002
|
||||
Socket ${dkim.socket}
|
||||
KeyTable file:${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
'');
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (config.services.postfix.user == "postfix") {
|
||||
postfix.extraGroups = [ "${dkimGroup}" ];
|
||||
};
|
||||
systemd.services.opendkim = {
|
||||
preStart = lib.mkForce createAllCerts;
|
||||
serviceConfig = {
|
||||
ExecStart = lib.mkForce "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}";
|
||||
PermissionsStartOnly = lib.mkForce false;
|
||||
};
|
||||
};
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${dkimKeyDirectory}' - ${dkimUser} ${dkimGroup} - -"
|
||||
];
|
||||
};
|
||||
}
|
||||
200
config/services/mail/postfix.nix
Normal file
200
config/services/mail/postfix.nix
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
{ pkgs, lib, config, tf, ... }:
|
||||
|
||||
let
|
||||
publicCert = "public_${config.networking.hostName}";
|
||||
|
||||
ldaps = "ldaps://auth.${config.network.dns.domain}:636";
|
||||
|
||||
virtualRegex = pkgs.writeText "virtual-regex" ''
|
||||
/^kat\.[^@.]+@kittywit\.ch$/ kat@kittywit.ch
|
||||
/^kat\.[^@.]+@dork\.dev$/ kat@kittywit.ch
|
||||
'';
|
||||
|
||||
helo_access = pkgs.writeText "helo_access" ''
|
||||
${config.network.addresses.public.nixos.ipv4.selfaddress} REJECT Get lost - you're lying about who you are
|
||||
${config.network.addresses.public.nixos.ipv6.selfaddress} REJECT Get lost - you're lying about who you are
|
||||
kittywit.ch REJECT Get lost - you're lying about who you are
|
||||
dork.dev REJECT Get lost - you're lying about who you are
|
||||
'';
|
||||
in {
|
||||
kw.secrets.variables."postfix-ldap-password" = {
|
||||
path = "services/dovecot";
|
||||
field = "password";
|
||||
};
|
||||
|
||||
secrets.files = {
|
||||
domains-ldap = {
|
||||
text = ''
|
||||
server_host = ${ldaps}
|
||||
search_base = dc=domains,dc=mail,dc=kittywit,dc=ch
|
||||
query_filter = (&(dc=%s)(objectClass=mailDomain))
|
||||
result_attribute = postfixTransport
|
||||
bind = yes
|
||||
bind_dn = cn=dovecot,dc=mail,dc=kittywit,dc=ch
|
||||
bind_pw = ${tf.variables.postfix-ldap-password.ref}
|
||||
scope = one
|
||||
'';
|
||||
owner = "postfix";
|
||||
group = "postfix";
|
||||
};
|
||||
|
||||
accountsmap-ldap = {
|
||||
text = ''
|
||||
server_host = ${ldaps}
|
||||
search_base = ou=users,dc=kittywit,dc=ch
|
||||
query_filter = (&(objectClass=mailAccount)(mail=%s))
|
||||
result_attribute = mail
|
||||
bind = yes
|
||||
bind_dn = cn=dovecot,dc=mail,dc=kittywit,dc=ch
|
||||
bind_pw = ${tf.variables.postfix-ldap-password.ref}
|
||||
'';
|
||||
owner = "postfix";
|
||||
group = "postfix";
|
||||
};
|
||||
|
||||
aliases-ldap = {
|
||||
text = ''
|
||||
server_host = ${ldaps}
|
||||
search_base = dc=aliases,dc=mail,dc=kittywit,dc=ch
|
||||
query_filter = (&(objectClass=mailAlias)(mail=%s))
|
||||
result_attribute = maildrop
|
||||
bind = yes
|
||||
bind_dn = cn=dovecot,dc=mail,dc=kittywit,dc=ch
|
||||
bind_pw = ${tf.variables.postfix-ldap-password.ref}
|
||||
'';
|
||||
owner = "postfix";
|
||||
group = "postfix";
|
||||
};
|
||||
};
|
||||
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
enableSubmission = true;
|
||||
hostname = config.network.addresses.public.domain;
|
||||
domain = config.network.dns.domain;
|
||||
|
||||
masterConfig."465" = {
|
||||
type = "inet";
|
||||
private = false;
|
||||
command = "smtpd";
|
||||
args = [
|
||||
"-o smtpd_client_restrictions=permit_sasl_authenticated,reject"
|
||||
"-o syslog_name=postfix/smtps"
|
||||
"-o smtpd_tls_wrappermode=yes"
|
||||
"-o smtpd_sasl_auth_enable=yes"
|
||||
"-o smtpd_tls_security_level=none"
|
||||
"-o smtpd_reject_unlisted_recipient=no"
|
||||
"-o smtpd_recipient_restrictions="
|
||||
"-o smtpd_relay_restrictions=permit_sasl_authenticated,reject"
|
||||
"-o milter_macro_daemon_name=ORIGINATING"
|
||||
];
|
||||
};
|
||||
|
||||
mapFiles."virtual-regex" = virtualRegex;
|
||||
mapFiles."helo_access" = helo_access;
|
||||
|
||||
extraConfig = ''
|
||||
smtp_bind_address = ${if tf.state.enable then tf.resources.${config.networking.hostName}.getAttr "private_ip" else config.network.addresses.public.nixos.ipv4.selfaddress}
|
||||
smtp_bind_address6 = ${config.network.addresses.public.nixos.ipv6.selfaddress}
|
||||
mailbox_transport = lmtp:unix:private/dovecot-lmtp
|
||||
masquerade_domains = ldap:${config.secrets.files.domains-ldap.path}
|
||||
virtual_mailbox_domains = ldap:${config.secrets.files.domains-ldap.path}
|
||||
virtual_alias_maps = ldap:${config.secrets.files.accountsmap-ldap.path},ldap:${config.secrets.files.aliases-ldap.path},regexp:/var/lib/postfix/conf/virtual-regex
|
||||
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
||||
smtpd_milters = unix:/run/opendkim/opendkim.sock,unix:/run/rspamd/rspamd-milter.sock
|
||||
non_smtpd_milters = unix:/run/opendkim/opendkim.sock
|
||||
milter_protocol = 6
|
||||
milter_default_action = accept
|
||||
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}
|
||||
|
||||
# bigger attachement size
|
||||
mailbox_size_limit = 202400000
|
||||
message_size_limit = 51200000
|
||||
smtpd_helo_required = yes
|
||||
smtpd_delay_reject = yes
|
||||
strict_rfc821_envelopes = yes
|
||||
|
||||
# send Limit
|
||||
smtpd_error_sleep_time = 1s
|
||||
smtpd_soft_error_limit = 10
|
||||
smtpd_hard_error_limit = 20
|
||||
|
||||
smtpd_use_tls = yes
|
||||
smtp_tls_note_starttls_offer = yes
|
||||
smtpd_tls_security_level = may
|
||||
smtpd_tls_auth_only = yes
|
||||
|
||||
smtpd_tls_cert_file = /var/lib/acme/${publicCert}/full.pem
|
||||
smtpd_tls_key_file = /var/lib/acme/${publicCert}/key.pem
|
||||
smtpd_tls_CAfile = /var/lib/acme/${publicCert}/fullchain.pem
|
||||
|
||||
smtpd_tls_dh512_param_file = ${config.security.dhparams.params.postfix512.path}
|
||||
smtpd_tls_dh1024_param_file = ${config.security.dhparams.params.postfix2048.path}
|
||||
|
||||
smtpd_tls_session_cache_database = btree:''${data_directory}/smtpd_scache
|
||||
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
|
||||
smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
|
||||
smtpd_tls_mandatory_ciphers = medium
|
||||
tls_medium_cipherlist = AES128+EECDH:AES128+EDH
|
||||
|
||||
# authentication
|
||||
smtpd_sasl_auth_enable = yes
|
||||
smtpd_sasl_local_domain = $mydomain
|
||||
smtpd_sasl_security_options = noanonymous
|
||||
smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
|
||||
smtpd_sasl_type = dovecot
|
||||
smtpd_sasl_path = /var/lib/postfix/queue/private/auth
|
||||
smtpd_relay_restrictions = permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
defer_unauth_destination
|
||||
smtpd_client_restrictions = permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_invalid_hostname,
|
||||
reject_unknown_client,
|
||||
permit
|
||||
smtpd_helo_restrictions = permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_unauth_pipelining,
|
||||
reject_non_fqdn_hostname,
|
||||
reject_invalid_hostname,
|
||||
warn_if_reject reject_unknown_hostname,
|
||||
permit
|
||||
smtpd_recipient_restrictions = permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_non_fqdn_sender,
|
||||
reject_non_fqdn_recipient,
|
||||
reject_non_fqdn_hostname,
|
||||
reject_invalid_hostname,
|
||||
reject_unknown_sender_domain,
|
||||
reject_unknown_recipient_domain,
|
||||
reject_unknown_client_hostname,
|
||||
reject_unauth_pipelining,
|
||||
reject_unknown_client,
|
||||
permit
|
||||
smtpd_sender_restrictions = permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_non_fqdn_sender,
|
||||
reject_unknown_sender_domain,
|
||||
reject_unknown_client_hostname,
|
||||
reject_unknown_address
|
||||
|
||||
smtpd_etrn_restrictions = permit_mynetworks, reject
|
||||
smtpd_data_restrictions = reject_unauth_pipelining, reject_multi_recipient_bounce, permit
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.postfix.wants = [ "openldap.service" "acme-${publicCert}.service" ];
|
||||
systemd.services.postfix.after = [ "openldap.service" "acme-${publicCert}.service" "network.target" ];
|
||||
|
||||
security.dhparams = {
|
||||
enable = true;
|
||||
params.postfix512.bits = 512;
|
||||
params.postfix2048.bits = 1024;
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
25 # smtp
|
||||
465 # stmps
|
||||
587 # submission
|
||||
];
|
||||
}
|
||||
85
config/services/mail/rspamd.nix
Normal file
85
config/services/mail/rspamd.nix
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
postfixCfg = config.services.postfix;
|
||||
rspamdCfg = config.services.rspamd;
|
||||
rspamdSocket = "rspamd.service";
|
||||
in
|
||||
{
|
||||
config = {
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
locals = {
|
||||
"milter_headers.conf" = { text = ''
|
||||
extended_spam_headers = yes;
|
||||
''; };
|
||||
"redis.conf" = { text = ''
|
||||
servers = "127.0.0.1:${toString config.services.redis.port}";
|
||||
''; };
|
||||
"classifier-bayes.conf" = { text = ''
|
||||
cache {
|
||||
backend = "redis";
|
||||
}
|
||||
''; };
|
||||
"dkim_signing.conf" = { text = ''
|
||||
# Disable outbound email signing, we use opendkim for this
|
||||
enabled = false;
|
||||
''; };
|
||||
};
|
||||
|
||||
overrides = {
|
||||
"milter_headers.conf" = {
|
||||
text = ''
|
||||
extended_spam_headers = true;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
workers.rspamd_proxy = {
|
||||
type = "rspamd_proxy";
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/rspamd-milter.sock";
|
||||
mode = "0664";
|
||||
}];
|
||||
count = 1; # Do not spawn too many processes of this type
|
||||
extraConfig = ''
|
||||
milter = yes; # Enable milter mode
|
||||
timeout = 120s; # Needed for Milter usually
|
||||
|
||||
upstream "local" {
|
||||
default = yes; # Self-scan upstreams are always default
|
||||
self_scan = yes; # Enable self-scan
|
||||
}
|
||||
'';
|
||||
};
|
||||
workers.controller = {
|
||||
type = "controller";
|
||||
count = 1;
|
||||
bindSockets = [{
|
||||
socket = "/run/rspamd/worker-controller.sock";
|
||||
mode = "0666";
|
||||
}];
|
||||
includes = [];
|
||||
extraConfig = ''
|
||||
static_dir = "''${WWWDIR}"; # Serve the web UI static assets
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
services.redis.enable = true;
|
||||
|
||||
systemd.services.rspamd = {
|
||||
requires = [ "redis.service" ];
|
||||
after = [ "redis.service" ];
|
||||
};
|
||||
|
||||
systemd.services.postfix = {
|
||||
after = [ rspamdSocket ];
|
||||
requires = [ rspamdSocket ];
|
||||
};
|
||||
|
||||
users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ];
|
||||
};
|
||||
}
|
||||
|
||||
101
config/services/nixos-mailserver/default.nix
Normal file
101
config/services/nixos-mailserver/default.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{ config, lib, tf, pkgs, sources, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
domains = [ "kittywitch" "dork" ];
|
||||
users = [ "gitea" "kat" "keycloak" "vaultwarden" ];
|
||||
in
|
||||
{
|
||||
imports = [ sources.nixos-mailserver.outPath ];
|
||||
|
||||
kw.secrets.variables = listToAttrs (map
|
||||
(field:
|
||||
nameValuePair "mail-${field}-hash" {
|
||||
path = "secrets/mail-kittywitch";
|
||||
field = "${field}-hash";
|
||||
})
|
||||
users
|
||||
++ map
|
||||
(domain:
|
||||
nameValuePair "mail-domainkey-${domain}" {
|
||||
path = "secrets/mail-${domain}";
|
||||
field = "notes";
|
||||
})
|
||||
domains);
|
||||
|
||||
deploy.tf.dns.records = mkMerge (map
|
||||
(domain:
|
||||
let
|
||||
zoneGet = domain: if domain == "dork" then "dork.dev." else config.network.dns.zone;
|
||||
in
|
||||
{
|
||||
"services_mail_${domain}_mx" = {
|
||||
zone = zoneGet domain;
|
||||
mx = {
|
||||
priority = 10;
|
||||
target = "${config.network.addresses.public.domain}.";
|
||||
};
|
||||
};
|
||||
|
||||
"services_mail_${domain}_spf" = {
|
||||
zone = zoneGet domain;
|
||||
txt.value = "v=spf1 ip4:${config.network.addresses.public.nixos.ipv4.address} ip6:${config.network.addresses.public.nixos.ipv6.address} -all";
|
||||
};
|
||||
|
||||
"services_mail_${domain}_dmarc" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "_dmarc";
|
||||
txt.value = "v=DMARC1; p=none";
|
||||
};
|
||||
|
||||
"services_mail_${domain}_domainkey" = {
|
||||
zone = zoneGet domain;
|
||||
domain = "mail._domainkey";
|
||||
txt.value = tf.variables."mail-domainkey-${domain}".ref;
|
||||
};
|
||||
})
|
||||
domains);
|
||||
|
||||
secrets.files = listToAttrs (map
|
||||
(user:
|
||||
nameValuePair "mail-${user}-hash" {
|
||||
text = ''
|
||||
${tf.variables."mail-${user}-hash".ref}
|
||||
'';
|
||||
})
|
||||
users);
|
||||
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = config.network.addresses.public.domain;
|
||||
domains = [ "kittywit.ch" "dork.dev" ];
|
||||
certificateScheme = 1;
|
||||
certificateFile = "/var/lib/acme/public_${config.networking.hostName}/cert.pem";
|
||||
keyFile = "/var/lib/acme/public_${config.networking.hostName}/key.pem";
|
||||
enableImap = true;
|
||||
enablePop3 = true;
|
||||
enableImapSsl = true;
|
||||
enablePop3Ssl = true;
|
||||
enableSubmission = false;
|
||||
enableSubmissionSsl = true;
|
||||
enableManageSieve = true;
|
||||
virusScanning = false;
|
||||
|
||||
# nix run nixpkgs.apacheHttpd -c htpasswd -nbB "" "super secret password" | cut -d: -f2
|
||||
loginAccounts = mkMerge [
|
||||
(listToAttrs (map
|
||||
(user:
|
||||
nameValuePair "${user}@kittywit.ch" {
|
||||
hashedPasswordFile = config.secrets.files."mail-${user}-hash".path;
|
||||
})
|
||||
users))
|
||||
{
|
||||
"kat@kittywit.ch" = {
|
||||
aliases = [ "postmaster@kittywit.ch" ];
|
||||
catchAll = [ "kittywit.ch" "dork.dev" ];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -56,18 +56,70 @@
|
|||
olcRootDN = "cn=root,dc=kittywit,dc=ch";
|
||||
olcRootPW.path = config.secrets.files.openldap-root-password-file.path;
|
||||
olcAccess = [
|
||||
"{0}to attrs=userPassword
|
||||
''{0}to attrs=userPassword
|
||||
by anonymous auth
|
||||
by dn.base="cn=dovecot,dc=mail,dc=kittywit,dc=ch" read
|
||||
by self write
|
||||
by * none"
|
||||
"{1}to *
|
||||
by dn.children=\"ou=users,dc=kittywit,dc=ch\" write
|
||||
by self read by * none"
|
||||
"{2}to dn.subtree=\"dc=kittywit,dc=ch\"
|
||||
by dn.exact=\"cn=root,dc=kittywit,dc=ch\" manage"
|
||||
by * none''
|
||||
''{1}to dn.subtree="dc=kittywit,dc=ch"
|
||||
by dn.exact="cn=root,dc=kittywit,dc=ch" manage
|
||||
by dn.base="cn=dovecot,dc=mail,dc=kittywit,dc=ch" read''
|
||||
''{2}to dn.subtree="ou=users,dc=kittywit,dc=ch"
|
||||
by dn.base="cn=dovecot,dc=mail,dc=kittywit,dc=ch" read
|
||||
by dn.subtree="ou=users,dc=kittywit,dc=ch" read
|
||||
by * none''
|
||||
''{3}to * by * read''
|
||||
];
|
||||
};
|
||||
};
|
||||
"cn={2}postfix,cn=schema".attrs = {
|
||||
cn = "{2}postfix";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcAttributeTypes = [
|
||||
''( 1.3.6.1.4.1.4203.666.1.200 NAME 'mailAcceptingGeneralId'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} )''
|
||||
''(1.3.6.1.4.1.12461.1.1.1 NAME 'postfixTransport'
|
||||
DESC 'A string directing postfix which transport to use'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{20} SINGLE-VALUE)''
|
||||
''(1.3.6.1.4.1.12461.1.1.5 NAME 'mailbox'
|
||||
DESC 'The absolute path to the mailbox for a mail account in a non-default location'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)''
|
||||
''(1.3.6.1.4.1.12461.1.1.6 NAME 'quota'
|
||||
DESC 'A string that represents the quota on a mailbox'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)''
|
||||
''(1.3.6.1.4.1.12461.1.1.8 NAME 'maildrop'
|
||||
DESC 'RFC822 Mailbox - mail alias'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256})''
|
||||
];
|
||||
olcObjectClasses = [
|
||||
''(1.3.6.1.4.1.12461.1.2.1 NAME 'mailAccount'
|
||||
SUP top AUXILIARY
|
||||
DESC 'Mail account objects'
|
||||
MUST ( mail $ userPassword )
|
||||
MAY ( cn $ description $ quota))''
|
||||
''(1.3.6.1.4.1.12461.1.2.2 NAME 'mailAlias'
|
||||
SUP top STRUCTURAL
|
||||
DESC 'Mail aliasing/forwarding entry'
|
||||
MUST ( mail $ mailAcceptingGeneralId $ maildrop )
|
||||
MAY ( cn $ description ))''
|
||||
''(1.3.6.1.4.1.12461.1.2.3 NAME 'mailDomain'
|
||||
SUP domain STRUCTURAL
|
||||
DESC 'Virtual Domain entry to be used with postfix transport maps'
|
||||
MUST ( dc )
|
||||
MAY ( postfixTransport $ description ))''
|
||||
''(1.3.6.1.4.1.12461.1.2.4 NAME 'mailPostmaster'
|
||||
SUP top AUXILIARY
|
||||
DESC 'Added to a mailAlias to create a postmaster entry'
|
||||
MUST roleOccupant)''
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
48
config/services/openldap/mail.ldif
Normal file
48
config/services/openldap/mail.ldif
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
dn: dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: dcObject
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
dc: mail
|
||||
ou: mail
|
||||
|
||||
dn: cn=dovecot,dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: organizationalRole
|
||||
objectClass: simpleSecurityObject
|
||||
objectClass: top
|
||||
cn: dovecot
|
||||
userPassword: {SSHA}GenerateYourOwn
|
||||
|
||||
dn: dc=aliases,dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: dcObject
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
dc: aliases
|
||||
ou: aliases
|
||||
|
||||
dn: dc=domains,dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: dcObject
|
||||
objectClass: organizationalUnit
|
||||
objectClass: top
|
||||
dc: domains
|
||||
ou: domains
|
||||
|
||||
dn: dc=kittywit.ch,dc=domains,dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: mailDomain
|
||||
objectClass: top
|
||||
dc: kittywit.ch
|
||||
postfixTransport: kittywit.ch
|
||||
|
||||
dn: dc=dork.dev,dc=domains,dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: top
|
||||
objectClass: mailDomain
|
||||
dc: dork.dev
|
||||
postfixTransport: virtual:
|
||||
|
||||
dn: mail=kat@kittywit.ch,dc=aliases,dc=mail,dc=kittywit,dc=ch
|
||||
objectClass: top
|
||||
objectClass: mailAlias
|
||||
mailAcceptingGeneralId: kittywit.ch
|
||||
mailAcceptingGeneralId: @kittywit.ch
|
||||
maildrop: kat@kittywit.ch
|
||||
|
||||
|
||||
|
|
@ -7,27 +7,27 @@ let rinnosuke = config.network.nodes.rinnosuke; in
|
|||
node_public_rinnosuke_v4 = {
|
||||
inherit (rinnosuke.network.dns) zone;
|
||||
domain = rinnosuke.networking.hostName;
|
||||
a.address = rinnosuke.network.addresses.public.nixos.ipv4.address;
|
||||
a.address = rinnosuke.network.addresses.public.tf.ipv4.address;
|
||||
};
|
||||
node_public_rinnosuke_v6 = {
|
||||
inherit (rinnosuke.network.dns) zone;
|
||||
domain = rinnosuke.networking.hostName;
|
||||
aaaa.address = rinnosuke.network.addresses.public.nixos.ipv6.address;
|
||||
aaaa.address = rinnosuke.network.addresses.public.tf.ipv6.address;
|
||||
};
|
||||
node_wireguard_rinnosuke_v4 = {
|
||||
inherit (rinnosuke.network.dns) zone;
|
||||
domain = rinnosuke.network.addresses.wireguard.subdomain;
|
||||
a.address = rinnosuke.network.addresses.wireguard.nixos.ipv4.address;
|
||||
a.address = rinnosuke.network.addresses.wireguard.tf.ipv4.address;
|
||||
};
|
||||
node_wireguard_rinnosuke_v6 = {
|
||||
inherit (rinnosuke.network.dns) zone;
|
||||
domain = rinnosuke.network.addresses.wireguard.subdomain;
|
||||
aaaa.address = rinnosuke.network.addresses.wireguard.nixos.ipv6.address;
|
||||
aaaa.address = rinnosuke.network.addresses.wireguard.tf.ipv6.address;
|
||||
};
|
||||
node_yggdrasil_rinnosuke_v6 = {
|
||||
inherit (rinnosuke.network.dns) zone;
|
||||
domain = rinnosuke.network.addresses.yggdrasil.subdomain;
|
||||
aaaa.address = rinnosuke.network.addresses.yggdrasil.nixos.ipv6.address;
|
||||
aaaa.address = rinnosuke.network.addresses.yggdrasil.tf.ipv6.address;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@
|
|||
boxes = [ "Inbox" ];
|
||||
onNotifyPost = "${pkgs.notmuch}/bin/notmuch new && ${pkgs.libnotify}/bin/notify-send 'New mail arrived'";
|
||||
};
|
||||
imap.host = "kyouko.kittywit.ch";
|
||||
smtp.host = "kyouko.kittywit.ch";
|
||||
passwordCommand = "bitw get services/email/kittywitch -f password";
|
||||
imap.host = "daiyousei.kittywit.ch";
|
||||
smtp.host = "daiyousei.kittywit.ch";
|
||||
passwordCommand = "bitw get services/kittywitch -f password";
|
||||
gpg = {
|
||||
signByDefault = true;
|
||||
key = "01F50A29D4AA91175A11BDB17248991EFA8EFBEE";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue