first import of mailserver config
This commit is contained in:
parent
d4cdbc8ccd
commit
292215f770
19 changed files with 811 additions and 0 deletions
20
README.md
Normal file
20
README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
hamburg.freifunk.net Mailserver
|
||||
===============================
|
||||
|
||||
Initiales Setup
|
||||
-----
|
||||
1. System starten
|
||||
2. URL anpassen und aufrufen: http://127.0.0.1/setup.php?lostpw=1
|
||||
3. Neues Setup-Passwort vergeben und den Hash generieren.
|
||||
4. Hash in der Datei variables.nix ersetzen und das System neu bauen und starten.
|
||||
5. URL anpassen und aufrufen: http://127.0.0.1/setup.php
|
||||
6. Admin-Account über die Website anlegen
|
||||
7. URL anpassen und aufrufen: http://127.0.0.1/
|
||||
8. Mail konfigurieren.
|
||||
|
||||
|
||||
Development
|
||||
-----
|
||||
Starten des Systems:
|
||||
QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:8080-:80,hostfwd=tcp:127.0.0.1:2525-:25" nixos-shell
|
||||
Zugriff dann per SSH über 127.0.0.1:2222 und HTTP über 127.0.0.1:8080.
|
|
@ -8,8 +8,51 @@
|
|||
imports =
|
||||
[ # Include the results of the hardware scan.
|
||||
./hardware-configuration.nix
|
||||
./variables.nix
|
||||
./mailserver.nix
|
||||
];
|
||||
|
||||
# Use the staging environment of Let's encrypt and accept their certificates as well...
|
||||
security.acme.production = false;
|
||||
security.pki.certificates = [
|
||||
''
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFATCCAumgAwIBAgIRAKc9ZKBASymy5TLOEp57N98wDQYJKoZIhvcNAQELBQAw
|
||||
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDMyMzIyNTM0NloXDTM2
|
||||
MDMyMzIyNTM0NlowGjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMIICIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA+pYHvQw5iU3v2b3iNuYNKYgsWD6KU7aJ
|
||||
diddtZQxSWYzUI3U0I1UsRPTxnhTifs/M9NW4ZlV13ZfB7APwC8oqKOIiwo7IwlP
|
||||
xg0VKgyz+kT8RJfYr66PPIYP0fpTeu42LpMJ+CKo9sbpgVNDZN2z/qiXrRNX/VtG
|
||||
TkPV7a44fZ5bHHVruAxvDnylpQxJobtCBWlJSsbIRGFHMc2z88eUz9NmIOWUKGGj
|
||||
EmP76x8OfRHpIpuxRSCjn0+i9+hR2siIOpcMOGd+40uVJxbRRP5ZXnUFa2fF5FWd
|
||||
O0u0RPI8HON0ovhrwPJY+4eWKkQzyC611oLPYGQ4EbifRsTsCxUZqyUuStGyp8oa
|
||||
aoSKfF6X0+KzGgwwnrjRTUpIl19A92KR0Noo6h622OX+4sZiO/JQdkuX5w/HupK0
|
||||
A0M0WSMCvU6GOhjGotmh2VTEJwHHY4+TUk0iQYRtv1crONklyZoAQPD76hCrC8Cr
|
||||
IbgsZLfTMC8TWUoMbyUDgvgYkHKMoPm0VGVVuwpRKJxv7+2wXO+pivrrUl2Q9fPe
|
||||
Kk055nJLMV9yPUdig8othUKrRfSxli946AEV1eEOhxddfEwBE3Lt2xn0hhiIedbb
|
||||
Ftf/5kEWFZkXyUmMJK8Ra76Kus2ABueUVEcZ48hrRr1Hf1N9n59VbTUaXgeiZA50
|
||||
qXf2bymE6F8CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
|
||||
Af8wHQYDVR0OBBYEFMEmdKSKRKDm+iAo2FwjmkWIGHngMA0GCSqGSIb3DQEBCwUA
|
||||
A4ICAQBCPw74M9X/Xx04K1VAES3ypgQYH5bf9FXVDrwhRFSVckria/7dMzoF5wln
|
||||
uq9NGsjkkkDg17AohcQdr8alH4LvPdxpKr3BjpvEcmbqF8xH+MbbeUEnmbSfLI8H
|
||||
sefuhXF9AF/9iYvpVNC8FmJ0OhiVv13VgMQw0CRKkbtjZBf8xaEhq/YqxWVsgOjm
|
||||
dm5CAQ2X0aX7502x8wYRgMnZhA5goC1zVWBVAi8yhhmlhhoDUfg17cXkmaJC5pDd
|
||||
oenZ9NVhW8eDb03MFCrWNvIh89DDeCGWuWfDltDq0n3owyL0IeSn7RfpSclpxVmV
|
||||
/53jkYjwIgxIG7Gsv0LKMbsf6QdBcTjhvfZyMIpBRkTe3zuHd2feKzY9lEkbRvRQ
|
||||
zbh4Ps5YBnG6CKJPTbe2hfi3nhnw/MyEmF3zb0hzvLWNrR9XW3ibb2oL3424XOwc
|
||||
VjrTSCLzO9Rv6s5wi03qoWvKAQQAElqTYRHhynJ3w6wuvKYF5zcZF3MDnrVGLbh1
|
||||
Q9ePRFBCiXOQ6wPLoUhrrbZ8LpFUFYDXHMtYM7P9sc9IAWoONXREJaO08zgFtMp4
|
||||
8iyIYUyQAbsvx8oD2M8kRvrIRSrRJSl6L957b4AFiLIQ/GgV2curs0jje7Edx34c
|
||||
idWw1VrejtwclobqNMVtG3EiPUIpJGpbMcJgbiLSmKkrvQtGng==
|
||||
-----END CERTIFICATE-----
|
||||
''
|
||||
];
|
||||
|
||||
# Configuration options for the mailserver
|
||||
variables = {
|
||||
mailAdmin = "postmaster@mail2.hamburg.freifunk.net";
|
||||
useSSL = true;
|
||||
};
|
||||
# Use the GRUB 2 boot loader.
|
||||
boot.loader.grub.enable = true;
|
||||
boot.loader.grub.version = 2;
|
||||
|
|
136
dovecot.nix
Normal file
136
dovecot.nix
Normal file
|
@ -0,0 +1,136 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
dovecotSQL = pkgs.writeText "dovecot-sql.conf" ''
|
||||
driver = sqlite
|
||||
connect = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
password_query = SELECT username AS user, password FROM mailbox WHERE username = '%Lu' AND active='1'
|
||||
user_query = SELECT username AS user FROM mailbox WHERE username = '%Lu' AND active='1'
|
||||
'';
|
||||
dovecotConf = pkgs.writeText "dovecot.conf" ''
|
||||
default_internal_user = dovecot2
|
||||
default_internal_group = dovecot2
|
||||
protocols = imap lmtp pop3 sieve
|
||||
|
||||
${lib.optionalString (config.variables.useSSL) ''
|
||||
ssl = yes
|
||||
ssl_cert = </var/lib/acme/dovecot2.${config.variables.myFQDN}/fullchain.pem
|
||||
ssl_key = </var/lib/acme/dovecot2.${config.variables.myFQDN}/key.pem
|
||||
''
|
||||
}
|
||||
|
||||
disable_plaintext_auth = no
|
||||
auth_mechanisms = plain login
|
||||
|
||||
userdb {
|
||||
driver = sql
|
||||
args = ${dovecotSQL}
|
||||
}
|
||||
passdb {
|
||||
driver = sql
|
||||
args = ${dovecotSQL}
|
||||
}
|
||||
mail_home = ${config.variables.vmailBaseDir}/%Lu/
|
||||
mail_location = maildir:${config.variables.vmailBaseDir}/%Lu/Maildir
|
||||
mail_uid = ${toString config.variables.vmailUID}
|
||||
mail_gid = ${toString config.variables.vmailGID}
|
||||
|
||||
service auth {
|
||||
unix_listener ${config.variables.dovecotAuthSocket} {
|
||||
user = ${config.services.postfix.user}
|
||||
group = ${config.services.postfix.group}
|
||||
mode = 0600
|
||||
}
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener ${config.variables.dovecotLmtpSocket} {
|
||||
user = ${config.services.postfix.user}
|
||||
group = ${config.services.postfix.group}
|
||||
mode = 0600
|
||||
}
|
||||
}
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = sieve
|
||||
}
|
||||
|
||||
protocol imap {
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
namespace inbox {
|
||||
inbox = yes
|
||||
location =
|
||||
mailbox Drafts {
|
||||
special_use = \Drafts
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Junk {
|
||||
special_use = \Junk
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Sent {
|
||||
special_use = \Sent
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Trash {
|
||||
special_use = \Trash
|
||||
auto = subscribe
|
||||
}
|
||||
mailbox Archive {
|
||||
special_use = \Archive
|
||||
auto = subscribe
|
||||
}
|
||||
prefix =
|
||||
}
|
||||
|
||||
plugin {
|
||||
sieve_after = ${(pkgs.callPackage ./sieve-after.nix {}) }
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:${(pkgs.callPackage ./sieve-report-spam-ham.nix {})}/report-spam.sieve
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:${(pkgs.callPackage ./sieve-report-spam-ham.nix {})}/report-ham.sieve
|
||||
sieve_pipe_bin_dir = ${(pkgs.callPackage ./sieve-pipe-bin-dir.nix {})}
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Configure certificates...
|
||||
security = lib.mkIf config.variables.useSSL {
|
||||
acme.certs."dovecot2.${config.variables.myFQDN}" = {
|
||||
domain = "${config.variables.myFQDN}";
|
||||
user = config.services.nginx.user;
|
||||
group = config.services.dovecot2.group;
|
||||
allowKeysForGroup = true;
|
||||
postRun = "systemctl restart dovecot2.service";
|
||||
# cheat by getting the webroot from another certificate configured through nginx.
|
||||
webroot = config.security.acme.certs."${config.variables.myFQDN}".webroot;
|
||||
};
|
||||
};
|
||||
# Make sure at least the self-signed certs are available before trying to start postfix
|
||||
systemd.services.dovecot2.after = lib.mkIf config.variables.useSSL [ "acme-selfsigned-certificates.target" ];
|
||||
# Setup dovecot
|
||||
networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ];
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
configFile = "${dovecotConf}";
|
||||
modules = [ pkgs.dovecot_pigeonhole ];
|
||||
};
|
||||
systemd.services."vmail-setup" = {
|
||||
serviceConfig.Type = "oneshot";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = ''
|
||||
mkdir -p ${config.variables.vmailBaseDir}
|
||||
chown -c ${config.variables.vmailUser}:${config.variables.vmailGroup} ${config.variables.vmailBaseDir}
|
||||
chmod -c 0700 ${config.variables.vmailBaseDir}
|
||||
'';
|
||||
};
|
||||
}
|
18
mailserver.nix
Normal file
18
mailserver.nix
Normal file
|
@ -0,0 +1,18 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
# Import some configuration as they are too long to be easily readable here
|
||||
imports = [
|
||||
./dovecot.nix
|
||||
./postfix.nix
|
||||
./postfixadmin.nix
|
||||
./roundcube.nix
|
||||
./rspamd.nix
|
||||
];
|
||||
users.groups."${config.variables.vmailGroup}" = { gid = config.variables.vmailGID; };
|
||||
users.users."${config.variables.vmailUser}" = {
|
||||
uid = config.variables.vmailUID;
|
||||
group = config.variables.vmailGroup;
|
||||
hashedPassword = "!";
|
||||
};
|
||||
}
|
35
pkg-postfixadmin.nix
Normal file
35
pkg-postfixadmin.nix
Normal file
|
@ -0,0 +1,35 @@
|
|||
{ stdenv, lib, fetchFromGitHub, config ? null, cacheDir ? null }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
name = "postfixadmin-${version}";
|
||||
version = "3.2.2";
|
||||
rev = "${name}";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
inherit rev;
|
||||
owner = "postfixadmin";
|
||||
repo = "postfixadmin";
|
||||
sha256 = "0bkjdmn63yinf217fnn3wq13pc0yklmnsbrgxjv22vpync42f9vh";
|
||||
};
|
||||
|
||||
phases = [ "unpackPhase" "installPhase" ];
|
||||
|
||||
installPhase = ''
|
||||
cp -Rp ./ $out/
|
||||
${lib.optionalString (config != null) ''
|
||||
ln -s ${config} $out/config.local.php
|
||||
''}
|
||||
${lib.optionalString (cacheDir != null) ''
|
||||
ln -s ${cacheDir}/templates_c $out/templates_c
|
||||
''}
|
||||
'';
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
description = "Postfix Admin";
|
||||
homepage = http://postfixadmin.sourceforge.net/;
|
||||
license = licenses.gpl2;
|
||||
maintainers = with maintainers; [ tokudan ];
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
|
32
pkg-roundcube.nix
Normal file
32
pkg-roundcube.nix
Normal file
|
@ -0,0 +1,32 @@
|
|||
{ stdenv, lib, fetchurl, acl, librsync, ncurses, openssl, zlib, conf ? null, temp ? null, logs ? null }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
name = "roundcube-${version}";
|
||||
version = "1.3.9";
|
||||
url = "https://github.com/roundcube/roundcubemail/releases/download/${version}/roundcubemail-${version}-complete.tar.gz";
|
||||
|
||||
src = fetchurl {
|
||||
inherit url;
|
||||
curlOpts = "--location";
|
||||
sha256 = "1b91amcpzb7935hpm67iqw92bl5r1a0rkfrc8gfm8w9sngzv8vbj";
|
||||
};
|
||||
|
||||
phases = [ "unpackPhase" "installPhase" ];
|
||||
|
||||
installPhase = ''
|
||||
cp -Rp ./ $out/
|
||||
cd "$out"
|
||||
${lib.optionalString (conf != null) "ln -s ${conf} $out/config/config.inc.php"}
|
||||
${lib.optionalString (temp != null) "mv temp temp.dist; ln -s ${temp} $out/temp"}
|
||||
${lib.optionalString (logs != null) "mv logs logs.dist; ln -s ${logs} $out/logs"}
|
||||
'';
|
||||
|
||||
meta = with stdenv.lib; {
|
||||
description = "Roundcube";
|
||||
homepage = https://roundcube.net/;
|
||||
license = licenses.agpl3;
|
||||
maintainers = with maintainers; [ tokudan ];
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
|
80
postfix.nix
Normal file
80
postfix.nix
Normal file
|
@ -0,0 +1,80 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
pfvirtual_mailbox_domains = pkgs.writeText "virtual_mailbox_domains.cf" ''
|
||||
dbpath = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
|
||||
'';
|
||||
pfvirtual_alias_maps = pkgs.writeText "virtual_alias_maps.cf" ''
|
||||
dbpath = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
|
||||
'';
|
||||
pfvirtual_alias_domain_maps = pkgs.writeText "virtual_alias_domain_maps.cf" ''
|
||||
dbpath = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = ('%u' || '@' || alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
|
||||
'';
|
||||
pfvirtual_alias_domain_catchall_maps = pkgs.writeText "virtual_alias_domain_catchall_maps.cf" ''
|
||||
dbpath = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = ('@' || alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1'
|
||||
'';
|
||||
pfvirtual_mailbox_maps = pkgs.writeText "virtual_mailbox_maps.cf" ''
|
||||
dbpath = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
|
||||
'';
|
||||
pfvirtual_alias_domain_mailbox_maps = pkgs.writeText "virtual_alias_domain_mailbox_maps.cf" ''
|
||||
dbpath = ${config.variables.pfadminDataDir}/postfixadmin.db
|
||||
query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = ('%u' || '@' || alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1'
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Configure Postfix to support SQLite
|
||||
nixpkgs.config.packageOverrides = pkgs: { postfix = pkgs.postfix.override { withSQLite = true; }; };
|
||||
# SSL/TLS specific configuration
|
||||
security = lib.mkIf config.variables.useSSL {
|
||||
# Configure the certificates...
|
||||
acme.certs."postfix.${config.variables.myFQDN}" = {
|
||||
domain = "${config.variables.myFQDN}";
|
||||
group = config.services.postfix.group;
|
||||
allowKeysForGroup = true;
|
||||
postRun = "systemctl restart postfix.service";
|
||||
# cheat by getting some settings from another certificate configured through nginx.
|
||||
user = config.security.acme.certs."${config.variables.myFQDN}".user;
|
||||
webroot = config.security.acme.certs."${config.variables.myFQDN}".webroot;
|
||||
};
|
||||
};
|
||||
systemd = lib.mkIf config.variables.useSSL {
|
||||
# Make sure at least the self-signed certs are available before trying to start postfix
|
||||
services.postfix.after = [ "acme-selfsigned-certificates.target" ];
|
||||
};
|
||||
|
||||
# Setup Postfix
|
||||
networking.firewall.allowedTCPPorts = [ 25 587 ];
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
enableSmtp = true;
|
||||
enableSubmission = true;
|
||||
config = {
|
||||
myhostname = config.variables.myFQDN;
|
||||
mynetworks_style = "host";
|
||||
recipient_delimiter = "+";
|
||||
relay_domains = "";
|
||||
smtpd_milters = "unix:${config.variables.rspamdMilterSocket}";
|
||||
non_smtpd_milters = "unix:${config.variables.rspamdMilterSocket}";
|
||||
smtpd_sasl_path = config.variables.dovecotAuthSocket;
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_tls_auth_only = "yes";
|
||||
smtpd_tls_chain_files = lib.mkIf config.variables.useSSL "/var/lib/acme/postfix.${config.variables.myFQDN}/full.pem";
|
||||
smtpd_tls_loglevel = "1";
|
||||
smtpd_tls_received_header = "yes";
|
||||
smtpd_tls_security_level = "may";
|
||||
smtp_tls_loglevel = "1";
|
||||
smtp_tls_security_level = "may";
|
||||
virtual_alias_maps = "proxy:sqlite:${pfvirtual_alias_maps}, proxy:sqlite:${pfvirtual_alias_domain_maps}, proxy:sqlite:${pfvirtual_alias_domain_catchall_maps}";
|
||||
virtual_mailbox_domains = "proxy:sqlite:${pfvirtual_mailbox_domains}";
|
||||
virtual_mailbox_maps = "proxy:sqlite:${pfvirtual_mailbox_maps}, proxy:sqlite:${pfvirtual_alias_domain_mailbox_maps}";
|
||||
virtual_transport = "lmtp:unix:${config.variables.dovecotLmtpSocket}";
|
||||
};
|
||||
rootAlias = config.variables.mailAdmin;
|
||||
postmasterAlias = config.variables.mailAdmin;
|
||||
};
|
||||
}
|
102
postfixadmin.nix
Normal file
102
postfixadmin.nix
Normal file
|
@ -0,0 +1,102 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
phppoolName = "postfixadmin_pool";
|
||||
pfaGroup = config.variables.pfaGroup;
|
||||
pfaUser = config.variables.pfaUser;
|
||||
postfixadminpkg = config.variables.postfixadminpkg;
|
||||
pfadminDataDir = config.variables.pfadminDataDir;
|
||||
cacheDir = config.variables.postfixadminpkgCacheDir;
|
||||
phpfpmHostPort = config.variables.pfaPhpfpmHostPort;
|
||||
in
|
||||
{
|
||||
# Setup the user and group
|
||||
users.groups."${pfaGroup}" = { };
|
||||
users.users."${pfaUser}" = {
|
||||
isSystemUser = true;
|
||||
group = "${pfaGroup}";
|
||||
description = "PHP User for postfixadmin";
|
||||
};
|
||||
|
||||
# Setup nginx
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."${config.variables.pfaDomain}" = {
|
||||
forceSSL = config.variables.useSSL;
|
||||
enableACME = config.variables.useSSL;
|
||||
default = true;
|
||||
root = "${postfixadminpkg}/public";
|
||||
extraConfig = ''
|
||||
access_log /tmp/nginx/log/$host combined;
|
||||
charset utf-8;
|
||||
|
||||
etag off;
|
||||
add_header etag "\"${builtins.substring 11 32 postfixadminpkg}\"";
|
||||
|
||||
index index.php;
|
||||
|
||||
location ~* \.php$ {
|
||||
# Zero-day exploit defense.
|
||||
# http://forum.nginx.org/read.php?2,88845,page=3
|
||||
# Won't work properly (404 error) if the file is not stored on this
|
||||
# server, which is entirely possible with php-fpm/php-fcgi.
|
||||
# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on
|
||||
# another machine. And then cross your fingers that you won't get hacked.
|
||||
try_files $uri =404;
|
||||
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
# With php5-cgi alone:
|
||||
fastcgi_pass ${phpfpmHostPort};
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||
fastcgi_param SERVER_SOFTWARE nginx;
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
fastcgi_param REQUEST_METHOD $request_method;
|
||||
fastcgi_param CONTENT_TYPE $content_type;
|
||||
fastcgi_param CONTENT_LENGTH $content_length;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_param DOCUMENT_URI $document_uri;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||
fastcgi_param REMOTE_PORT $remote_port;
|
||||
fastcgi_param SERVER_ADDR $server_addr;
|
||||
fastcgi_param SERVER_PORT $server_port;
|
||||
fastcgi_param SERVER_NAME $server_name;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
}
|
||||
'';
|
||||
};
|
||||
systemd.services."postfixadmin-setup" = {
|
||||
serviceConfig.Type = "oneshot";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = ''
|
||||
# Setup the data directory with the database and the cache directory
|
||||
mkdir -p ${pfadminDataDir}
|
||||
chmod -c 751 ${pfadminDataDir}
|
||||
chown -c ${pfaUser}:${pfaGroup} ${pfadminDataDir}
|
||||
|
||||
mkdir -p ${cacheDir}/templates_c
|
||||
chown -Rc ${pfaUser}:${pfaGroup} ${cacheDir}/templates_c
|
||||
chmod -Rc 751 ${cacheDir}/templates_c
|
||||
'';
|
||||
};
|
||||
services.phpfpm.pools."${phppoolName}" = {
|
||||
listen = phpfpmHostPort;
|
||||
extraConfig = ''
|
||||
user = ${pfaUser}
|
||||
pm = dynamic
|
||||
pm.max_children = 75
|
||||
pm.min_spare_servers = 5
|
||||
pm.max_spare_servers = 20
|
||||
pm.max_requests = 10
|
||||
catch_workers_output = 1
|
||||
php_admin_value[upload_max_filesize] = 42M
|
||||
php_admin_value[post_max_size] = 42M
|
||||
php_admin_value[memory_limit] = 128M
|
||||
php_admin_value[cgi.fix_pathinfo] = 1
|
||||
'';
|
||||
};
|
||||
}
|
109
roundcube.nix
Normal file
109
roundcube.nix
Normal file
|
@ -0,0 +1,109 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
poolName = "roundcube_pool";
|
||||
|
||||
roundcube = (pkgs.callPackage ./pkg-roundcube.nix {
|
||||
conf = pkgs.writeText "roundcube-config.inc.php" ''
|
||||
<?php
|
||||
$config = array();
|
||||
$config['db_dsnw'] = 'sqlite:///${config.variables.roundcubeDataDir}/roundcube.sqlite?mode=0600';
|
||||
$config['default_host'] = 'tls://${config.variables.myFQDN}';
|
||||
$config['smtp_server'] = 'tls://${config.variables.myFQDN}';
|
||||
$config['smtp_port'] = 587;
|
||||
$config['smtp_user'] = '%u';
|
||||
$config['smtp_pass'] = '%p';
|
||||
$config['product_name'] = 'Webmail';
|
||||
$config['des_key'] = 'JQgS7JcnFMNcU3cHKrr880wO';
|
||||
$config['plugins'] = array(
|
||||
'archive',
|
||||
'managesieve',
|
||||
'zipdownload',
|
||||
);
|
||||
$config['skin'] = 'larry';
|
||||
'';
|
||||
temp = "${config.variables.roundcubeDataDir}/temp";
|
||||
logs = "${config.variables.roundcubeDataDir}/logs";
|
||||
} );
|
||||
in
|
||||
{
|
||||
services.nginx.virtualHosts."${config.variables.roundcubeFQDN}" = {
|
||||
forceSSL = config.variables.useSSL;
|
||||
enableACME = config.variables.useSSL;
|
||||
root = "${roundcube}/public_html";
|
||||
locations."~ ^/favicon.ico/.*$" = {
|
||||
extraConfig = "try_files $uri kins/larry/images/$uri;";
|
||||
};
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
index index.php;
|
||||
try_files $uri /public/$uri /index.php$is_args$args;
|
||||
|
||||
etag off;
|
||||
add_header etag "\"${builtins.substring 11 32 roundcube}\"";
|
||||
'';
|
||||
};
|
||||
locations."~ [^/]\.php(/|$)" = {
|
||||
extraConfig = ''
|
||||
etag off;
|
||||
add_header etag "\"${builtins.substring 11 32 roundcube}\"";
|
||||
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
fastcgi_pass ${config.variables.roundcubePhpfpmHostPort};
|
||||
fastcgi_index index.php;
|
||||
|
||||
fastcgi_param QUERY_STRING $query_string;
|
||||
fastcgi_param REQUEST_METHOD $request_method;
|
||||
fastcgi_param CONTENT_TYPE $content_type;
|
||||
fastcgi_param CONTENT_LENGTH $content_length;
|
||||
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
|
||||
fastcgi_param REQUEST_URI $request_uri;
|
||||
fastcgi_param DOCUMENT_URI $document_uri;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||
|
||||
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
|
||||
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||
fastcgi_param REMOTE_PORT $remote_port;
|
||||
fastcgi_param SERVER_ADDR $server_addr;
|
||||
fastcgi_param SERVER_PORT $server_port;
|
||||
fastcgi_param SERVER_NAME $server_name;
|
||||
|
||||
fastcgi_param HTTPS $https;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
'';
|
||||
};
|
||||
};
|
||||
systemd.services.roundcube-install = {
|
||||
serviceConfig.Type = "oneshot";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
script = ''
|
||||
mkdir -p ${config.variables.roundcubeDataDir}/temp ${config.variables.roundcubeDataDir}/logs
|
||||
chown -Rc ${config.variables.roundcubeUser} ${config.variables.roundcubeDataDir}
|
||||
chmod -c 700 ${config.variables.roundcubeDataDir}
|
||||
'';
|
||||
};
|
||||
services.phpfpm.pools."${poolName}" = {
|
||||
listen = config.variables.roundcubePhpfpmHostPort;
|
||||
extraConfig = ''
|
||||
user = ${config.variables.roundcubeUser}
|
||||
pm = dynamic
|
||||
pm.max_children = 75
|
||||
pm.min_spare_servers = 5
|
||||
pm.max_spare_servers = 20
|
||||
pm.max_requests = 10
|
||||
catch_workers_output = 1
|
||||
'';
|
||||
};
|
||||
users.extraUsers."${config.variables.roundcubeUser}" = { };
|
||||
}
|
71
rspamd.nix
Normal file
71
rspamd.nix
Normal file
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
rspamdExtraConfig = pkgs.writeText "rspamd-extra.conf" ''
|
||||
secure_ip = [::1]
|
||||
actions {
|
||||
reject = null;
|
||||
}
|
||||
options {
|
||||
filters: "chartable,dkim,dkim_signing,spf,surbl,regexp,fuzzy_check"
|
||||
}
|
||||
milter_headers {
|
||||
extended_spam_headers = true;
|
||||
}
|
||||
classifier {
|
||||
bayes {
|
||||
autolearn = true;
|
||||
}
|
||||
}
|
||||
dkim_signing {
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
check_pubkey = true;
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
#networking.firewall.allowedTCPPorts = [ 110 143 993 995 ];
|
||||
environment.systemPackages = [
|
||||
(pkgs.writeShellScriptBin "dkim-generate" ''
|
||||
if [ $# -ne 1 ]; then
|
||||
echo Usage: dkim-generate DOMAIN >&2
|
||||
exit 1
|
||||
fi
|
||||
rspamd=${pkgs.rspamd}/bin/rspamadm
|
||||
mkdir -p /var/lib/rspamd/dkim
|
||||
$rspamd dkim_keygen -b 2048 -d "$1" -s dkim | ${pkgs.gawk}/bin/awk '/^-/ {KEY= ! KEY; print; next} KEY {print} !KEY {print > "/dev/stderr"}' >/var/lib/rspamd/dkim/"$1".dkim.key 2>/var/lib/rspamd/dkim/"$1".dkim.dns
|
||||
ls -l /var/lib/rspamd/dkim/"$1".dkim.key /var/lib/rspamd/dkim/"$1".dkim.dns
|
||||
'') ];
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
# Just shove our own configuration up rspamd's rear end with high prio as the default configuration structure is a mess
|
||||
extraConfig = ''
|
||||
.include(try=true,priority=10,duplicate=merge) "${rspamdExtraConfig}"
|
||||
'';
|
||||
workers = {
|
||||
controller = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
secure_ip = [::1]
|
||||
'';
|
||||
bindSockets = [
|
||||
"[::1]:11334"
|
||||
{ mode = "0666"; owner = config.variables.vmailUser; socket = "/run/rspamd/worker-controller.socket"; }
|
||||
];
|
||||
};
|
||||
rspamd_proxy = {
|
||||
enable = true;
|
||||
type = "rspamd_proxy";
|
||||
count = 5; # TODO: match with postfix limits
|
||||
extraConfig = ''
|
||||
upstream "local" {
|
||||
self_scan = yes; # Enable self-scan
|
||||
}
|
||||
'';
|
||||
bindSockets = [
|
||||
{ socket = config.variables.rspamdMilterSocket; mode = "0600"; owner = config.services.postfix.user; group = config.services.rspamd.group; }
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
20
sieve-after.nix
Normal file
20
sieve-after.nix
Normal file
|
@ -0,0 +1,20 @@
|
|||
{ stdenv, dovecot_pigeonhole}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
name = "sieve-after";
|
||||
|
||||
src = ./sieve-after;
|
||||
|
||||
phases = [ "copyPhase" "compilePhase" ];
|
||||
|
||||
copyPhase = ''
|
||||
cd $src
|
||||
mkdir $out
|
||||
cp -Rv $src/. $out/
|
||||
find $out -type d -exec chmod -c 0755 {} \;
|
||||
set +x
|
||||
'';
|
||||
compilePhase = ''
|
||||
find $out -iname '*.sieve' -print0 | xargs -t -0 -n1 ${dovecot_pigeonhole}/bin/sievec -c /dev/null
|
||||
'';
|
||||
}
|
6
sieve-after/junk-folder.sieve
Normal file
6
sieve-after/junk-folder.sieve
Normal file
|
@ -0,0 +1,6 @@
|
|||
require ["fileinto","mailbox"];
|
||||
|
||||
if header :contains "X-Spam" "Yes" {
|
||||
fileinto :create "Junk";
|
||||
stop;
|
||||
}
|
14
sieve-pipe-bin-dir.nix
Normal file
14
sieve-pipe-bin-dir.nix
Normal file
|
@ -0,0 +1,14 @@
|
|||
{ stdenv}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
name = "sieve-pipe-bin-dir";
|
||||
|
||||
src = ./sieve-pipe-bin-dir;
|
||||
|
||||
phases = [ "copyPhase" "fixupPhase" ];
|
||||
|
||||
copyPhase = ''
|
||||
mkdir $out
|
||||
cp -Rv $src/. $out/
|
||||
'';
|
||||
}
|
4
sieve-pipe-bin-dir/learn-ham.sh
Executable file
4
sieve-pipe-bin-dir/learn-ham.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
exec /run/current-system/sw/bin/rspamc -h /run/rspamd/worker-controller.socket learn_ham
|
4
sieve-pipe-bin-dir/learn-spam.sh
Executable file
4
sieve-pipe-bin-dir/learn-spam.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
exec /run/current-system/sw/bin/rspamc -h /run/rspamd/worker-controller.socket learn_spam
|
30
sieve-report-spam-ham.nix
Normal file
30
sieve-report-spam-ham.nix
Normal file
|
@ -0,0 +1,30 @@
|
|||
{ stdenv, dovecot_pigeonhole}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
name = "sieve-report-spam-ham";
|
||||
|
||||
src = ./sieve-report-spam-ham;
|
||||
|
||||
phases = [ "copyPhase" "compilePhase" ];
|
||||
|
||||
copyPhase = ''
|
||||
mkdir $out
|
||||
cp -Rv $src/. $out/
|
||||
find $out -type d -exec chmod -c 0755 {} \;
|
||||
set +x
|
||||
'';
|
||||
|
||||
# Yeah, need a specific dovecot.conf to enable the necessary plugins...
|
||||
# taking the one used by the dovecot that actually executes the sieve scripts should
|
||||
# work as well, but passing it through isn't worth my time.
|
||||
compilePhase = ''
|
||||
dc=$(pwd)/dovecot.conf
|
||||
cat > $dc <<-EOF
|
||||
plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
||||
}
|
||||
EOF
|
||||
find $out -iname '*.sieve' -print0 | xargs -t -0 -n1 ${dovecot_pigeonhole}/bin/sievec -c $dc
|
||||
'';
|
||||
}
|
15
sieve-report-spam-ham/report-ham.sieve
Normal file
15
sieve-report-spam-ham/report-ham.sieve
Normal file
|
@ -0,0 +1,15 @@
|
|||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.mailbox" "*" {
|
||||
set "mailbox" "${1}";
|
||||
}
|
||||
|
||||
if string "${mailbox}" "Trash" {
|
||||
stop;
|
||||
}
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "learn-ham.sh" [ "${username}" ];
|
7
sieve-report-spam-ham/report-spam.sieve
Normal file
7
sieve-report-spam-ham/report-spam.sieve
Normal file
|
@ -0,0 +1,7 @@
|
|||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "learn-spam.sh" [ "${username}" ];
|
65
variables.nix
Normal file
65
variables.nix
Normal file
|
@ -0,0 +1,65 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
options = {
|
||||
variables = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
config.variables = {
|
||||
dovecotGroup = "dovecot2";
|
||||
dovecotUser = "dovecot2";
|
||||
dovecotAuthSocket = "/run/dovecot2/dovecot-auth";
|
||||
dovecotLmtpSocket = "/run/dovecot2/dovecot-lmtp";
|
||||
rspamdMilterSocket = "/run/rspamd/milter";
|
||||
myFQDN = "${config.networking.hostName}.${config.networking.domain}";
|
||||
pfadminDataDir = "/var/lib/postfixadmin";
|
||||
pfaGroup = "pfadmin";
|
||||
pfaPhpfpmHostPort = "127.0.0.1:9000";
|
||||
pfaUser = "pfadmin";
|
||||
pfaDomain = "pfa.${config.variables.myFQDN}";
|
||||
roundcubeFQDN = config.variables.myFQDN;
|
||||
roundcubeDataDir = "/var/lib/roundcube";
|
||||
roundcubePhpfpmHostPort = "127.0.0.1:9001";
|
||||
roundcubeUser = "roundcube";
|
||||
useSSL = false;
|
||||
vmailBaseDir = "/var/vmail";
|
||||
vmailGID = 10000;
|
||||
vmailGroup = "vmail";
|
||||
vmailUID = 10000;
|
||||
vmailUser = "vmail";
|
||||
postfixadminpkgCacheDir = "/var/cache/postfixadmin";
|
||||
postfixadminpkg = (pkgs.callPackage ./pkg-postfixadmin.nix {
|
||||
config = (pkgs.writeText "postfixadmin-config.local.php" ''
|
||||
<?php
|
||||
$CONF['configured'] = true;
|
||||
$CONF['setup_password'] = '!';
|
||||
$CONF['database_type'] = 'sqlite';
|
||||
$CONF['database_name'] = '${config.variables.pfadminDataDir}/postfixadmin.db';
|
||||
$CONF['password_expiration'] = 'NO';
|
||||
$CONF['encrypt'] = 'dovecot:BLF-CRYPT';
|
||||
$CONF['dovecotpw'] = "${pkgs.dovecot}/bin/doveadm pw -r 12";
|
||||
$CONF['generate_password'] = 'YES';
|
||||
$CONF['show_password'] = 'NO';
|
||||
$CONF['quota'] = 'NO';
|
||||
$CONF['fetchmail'] = 'NO';
|
||||
$CONF['recipient_delimiter'] = "+";
|
||||
$CONF['forgotten_user_password_reset'] = false;
|
||||
$CONF['forgotten_admin_password_reset'] = false;
|
||||
$CONF['aliases'] = '0';
|
||||
$CONF['mailboxes'] = '0';
|
||||
$CONF['default_aliases'] = array (
|
||||
'abuse' => '${config.variables.mailAdmin}',
|
||||
'hostmaster' => '${config.variables.mailAdmin}',
|
||||
'postmaster' => '${config.variables.mailAdmin}',
|
||||
'webmaster' => '${config.variables.mailAdmin}'
|
||||
);
|
||||
$CONF['footer_text'] = "";
|
||||
$CONF['footer_link'] = "";
|
||||
?>
|
||||
'');
|
||||
cacheDir = config.variables.postfixadminpkgCacheDir;
|
||||
} );
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue