diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2019-10-18 19:43:39 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2019-10-18 19:43:39 +0200 |
commit | 8415083eb6acc343dfa404dbbc12fa0171a48a20 (patch) | |
tree | d83f54c99763ae49076bf3071449595b6ccae133 | |
parent | 8fa7ff2c63fb0722144bc90837512d9f8b8c929d (diff) | |
download | Nix-8415083eb6acc343dfa404dbbc12fa0171a48a20.tar.gz Nix-8415083eb6acc343dfa404dbbc12fa0171a48a20.tar.zst Nix-8415083eb6acc343dfa404dbbc12fa0171a48a20.zip |
Add new machine to nixops
24 files changed, 1222 insertions, 694 deletions
diff --git a/modules/private/backup.nix b/modules/private/backup.nix deleted file mode 100644 index 6911750..0000000 --- a/modules/private/backup.nix +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | { ... }: | ||
2 | { | ||
3 | config = { | ||
4 | services.backup.enable = true; | ||
5 | }; | ||
6 | } | ||
diff --git a/modules/private/certificates.nix b/modules/private/certificates.nix index cb284fc..9de3e6d 100644 --- a/modules/private/certificates.nix +++ b/modules/private/certificates.nix | |||
@@ -1,6 +1,7 @@ | |||
1 | { lib, pkgs, config, ... }: | 1 | { lib, pkgs, config, ... }: |
2 | { | 2 | { |
3 | options.services.myCertificates = { | 3 | options.myServices.certificates = { |
4 | enable = lib.mkEnableOption "enable certificates"; | ||
4 | certConfig = lib.mkOption { | 5 | certConfig = lib.mkOption { |
5 | default = { | 6 | default = { |
6 | webroot = "${config.security.acme.directory}/acme-challenge"; | 7 | webroot = "${config.security.acme.directory}/acme-challenge"; |
@@ -14,18 +15,18 @@ | |||
14 | }; | 15 | }; |
15 | }; | 16 | }; |
16 | 17 | ||
17 | config = { | 18 | config = lib.mkIf config.myServices.certificates.enable { |
18 | services.backup.profiles.system.excludeFile = '' | 19 | services.backup.profiles.system.excludeFile = '' |
19 | + ${config.security.acme.directory} | 20 | + ${config.security.acme.directory} |
20 | ''; | 21 | ''; |
21 | services.websites.certs = config.services.myCertificates.certConfig; | 22 | services.websites.certs = config.myServices.certificates.certConfig; |
22 | myServices.databasesCerts = config.services.myCertificates.certConfig; | 23 | myServices.databasesCerts = config.myServices.certificates.certConfig; |
23 | myServices.ircCerts = config.services.myCertificates.certConfig; | 24 | myServices.ircCerts = config.myServices.certificates.certConfig; |
24 | 25 | ||
25 | security.acme.preliminarySelfsigned = true; | 26 | security.acme.preliminarySelfsigned = true; |
26 | 27 | ||
27 | security.acme.certs = { | 28 | security.acme.certs = { |
28 | "eldiron" = config.services.myCertificates.certConfig // { | 29 | "eldiron" = config.myServices.certificates.certConfig // { |
29 | domain = "eldiron.immae.eu"; | 30 | domain = "eldiron.immae.eu"; |
30 | }; | 31 | }; |
31 | }; | 32 | }; |
diff --git a/modules/private/databases/mariadb.nix b/modules/private/databases/mariadb.nix index a7239c0..4293f02 100644 --- a/modules/private/databases/mariadb.nix +++ b/modules/private/databases/mariadb.nix | |||
@@ -5,7 +5,7 @@ in { | |||
5 | options.myServices.databases = { | 5 | options.myServices.databases = { |
6 | mariadb = { | 6 | mariadb = { |
7 | enable = lib.mkOption { | 7 | enable = lib.mkOption { |
8 | default = cfg.enable; | 8 | default = false; |
9 | example = true; | 9 | example = true; |
10 | description = "Whether to enable mariadb database"; | 10 | description = "Whether to enable mariadb database"; |
11 | type = lib.types.bool; | 11 | type = lib.types.bool; |
diff --git a/modules/private/databases/openldap/default.nix b/modules/private/databases/openldap/default.nix index f09113a..9f72b29 100644 --- a/modules/private/databases/openldap/default.nix +++ b/modules/private/databases/openldap/default.nix | |||
@@ -48,7 +48,7 @@ in | |||
48 | options.myServices.databases = { | 48 | options.myServices.databases = { |
49 | openldap = { | 49 | openldap = { |
50 | enable = lib.mkOption { | 50 | enable = lib.mkOption { |
51 | default = cfg.enable; | 51 | default = false; |
52 | example = true; | 52 | example = true; |
53 | description = "Whether to enable ldap"; | 53 | description = "Whether to enable ldap"; |
54 | type = lib.types.bool; | 54 | type = lib.types.bool; |
diff --git a/modules/private/databases/postgresql.nix b/modules/private/databases/postgresql.nix index 911a6d1..6d1901d 100644 --- a/modules/private/databases/postgresql.nix +++ b/modules/private/databases/postgresql.nix | |||
@@ -5,7 +5,7 @@ in { | |||
5 | options.myServices.databases = { | 5 | options.myServices.databases = { |
6 | postgresql = { | 6 | postgresql = { |
7 | enable = lib.mkOption { | 7 | enable = lib.mkOption { |
8 | default = cfg.enable; | 8 | default = false; |
9 | example = true; | 9 | example = true; |
10 | description = "Whether to enable postgresql database"; | 10 | description = "Whether to enable postgresql database"; |
11 | type = lib.types.bool; | 11 | type = lib.types.bool; |
diff --git a/modules/private/databases/redis.nix b/modules/private/databases/redis.nix index 1ba6eed..c23ffec 100644 --- a/modules/private/databases/redis.nix +++ b/modules/private/databases/redis.nix | |||
@@ -4,7 +4,7 @@ let | |||
4 | in { | 4 | in { |
5 | options.myServices.databases.redis = { | 5 | options.myServices.databases.redis = { |
6 | enable = lib.mkOption { | 6 | enable = lib.mkOption { |
7 | default = cfg.enable; | 7 | default = false; |
8 | example = true; | 8 | example = true; |
9 | description = "Whether to enable redis database"; | 9 | description = "Whether to enable redis database"; |
10 | type = lib.types.bool; | 10 | type = lib.types.bool; |
diff --git a/modules/private/default.nix b/modules/private/default.nix index 6dd7358..c418795 100644 --- a/modules/private/default.nix +++ b/modules/private/default.nix | |||
@@ -50,10 +50,6 @@ set = { | |||
50 | mailTool = ./websites/tools/mail; | 50 | mailTool = ./websites/tools/mail; |
51 | 51 | ||
52 | mail = ./mail; | 52 | mail = ./mail; |
53 | mailMilters = ./mail/milters.nix; | ||
54 | mailPostfix = ./mail/postfix.nix; | ||
55 | mailDovecot = ./mail/dovecot.nix; | ||
56 | mailRspamd = ./mail/rspamd.nix; | ||
57 | 53 | ||
58 | buildbot = ./buildbot; | 54 | buildbot = ./buildbot; |
59 | certificates = ./certificates.nix; | 55 | certificates = ./certificates.nix; |
@@ -65,7 +61,6 @@ set = { | |||
65 | ftp = ./ftp.nix; | 61 | ftp = ./ftp.nix; |
66 | mpd = ./mpd.nix; | 62 | mpd = ./mpd.nix; |
67 | ssh = ./ssh; | 63 | ssh = ./ssh; |
68 | backup = ./backup.nix; | ||
69 | monitoring = ./monitoring; | 64 | monitoring = ./monitoring; |
70 | 65 | ||
71 | system = ./system.nix; | 66 | system = ./system.nix; |
diff --git a/modules/private/dns.nix b/modules/private/dns.nix index f0a3a5b..b4772fc 100644 --- a/modules/private/dns.nix +++ b/modules/private/dns.nix | |||
@@ -1,5 +1,6 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | 1 | { lib, pkgs, config, myconfig, ... }: |
2 | { | 2 | { |
3 | options.myServices.dns.enable = lib.mkEnableOption "enable DNS resolver"; | ||
3 | config = let | 4 | config = let |
4 | cfg = config.services.bind; | 5 | cfg = config.services.bind; |
5 | configFile = pkgs.writeText "named.conf" '' | 6 | configFile = pkgs.writeText "named.conf" '' |
@@ -49,8 +50,7 @@ | |||
49 | '') | 50 | '') |
50 | cfg.zones } | 51 | cfg.zones } |
51 | ''; | 52 | ''; |
52 | in | 53 | in lib.mkIf config.myServices.dns.enable { |
53 | { | ||
54 | networking.firewall.allowedUDPPorts = [ 53 ]; | 54 | networking.firewall.allowedUDPPorts = [ 53 ]; |
55 | networking.firewall.allowedTCPPorts = [ 53 ]; | 55 | networking.firewall.allowedTCPPorts = [ 53 ]; |
56 | services.bind = { | 56 | services.bind = { |
diff --git a/modules/private/ftp.nix b/modules/private/ftp.nix index c6d7fbe..a1da32f 100644 --- a/modules/private/ftp.nix +++ b/modules/private/ftp.nix | |||
@@ -17,7 +17,7 @@ in | |||
17 | services.backup.profiles.ftp = { | 17 | services.backup.profiles.ftp = { |
18 | rootDir = "/var/lib/ftp"; | 18 | rootDir = "/var/lib/ftp"; |
19 | }; | 19 | }; |
20 | security.acme.certs."ftp" = config.services.myCertificates.certConfig // { | 20 | security.acme.certs."ftp" = config.myServices.certificates.certConfig // { |
21 | domain = "eldiron.immae.eu"; | 21 | domain = "eldiron.immae.eu"; |
22 | postRun = '' | 22 | postRun = '' |
23 | systemctl restart pure-ftpd.service | 23 | systemctl restart pure-ftpd.service |
diff --git a/modules/private/mail/default.nix b/modules/private/mail/default.nix index ac8ad8c..d3b2a25 100644 --- a/modules/private/mail/default.nix +++ b/modules/private/mail/default.nix | |||
@@ -1,21 +1,31 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | 1 | { lib, pkgs, config, myconfig, ... }: |
2 | { | 2 | { |
3 | config.security.acme.certs."mail" = config.services.myCertificates.certConfig // { | 3 | imports = [ |
4 | domain = "eldiron.immae.eu"; | 4 | ./milters.nix |
5 | extraDomains = let | 5 | ./postfix.nix |
6 | zonesWithMx = builtins.filter (zone: | 6 | ./dovecot.nix |
7 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 | 7 | ./rspamd.nix |
8 | ) myconfig.env.dns.masterZones; | 8 | ]; |
9 | mxs = map (zone: "mx-1.${zone.name}") zonesWithMx; | 9 | options.myServices.mail.enable = lib.mkEnableOption "enable Mail services"; |
10 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | 10 | |
11 | }; | 11 | config = lib.mkIf config.myServices.mail.enable { |
12 | config.services.backup.profiles = { | 12 | security.acme.certs."mail" = config.myServices.certificates.certConfig // { |
13 | mail = { | 13 | domain = "eldiron.immae.eu"; |
14 | rootDir = "/var/lib"; | 14 | extraDomains = let |
15 | excludeFile = lib.mkAfter '' | 15 | zonesWithMx = builtins.filter (zone: |
16 | + /var/lib/vhost | 16 | lib.attrsets.hasAttr "withEmail" zone && lib.lists.length zone.withEmail > 0 |
17 | - /var/lib | 17 | ) myconfig.env.dns.masterZones; |
18 | ''; | 18 | mxs = map (zone: "mx-1.${zone.name}") zonesWithMx; |
19 | in builtins.listToAttrs (map (mx: lib.attrsets.nameValuePair mx null) mxs); | ||
20 | }; | ||
21 | services.backup.profiles = { | ||
22 | mail = { | ||
23 | rootDir = "/var/lib"; | ||
24 | excludeFile = lib.mkAfter '' | ||
25 | + /var/lib/vhost | ||
26 | - /var/lib | ||
27 | ''; | ||
28 | }; | ||
19 | }; | 29 | }; |
20 | }; | 30 | }; |
21 | } | 31 | } |
diff --git a/modules/private/mail/dovecot.nix b/modules/private/mail/dovecot.nix index 0d13a7b..dc75e0f 100644 --- a/modules/private/mail/dovecot.nix +++ b/modules/private/mail/dovecot.nix | |||
@@ -12,239 +12,241 @@ let | |||
12 | ''; | 12 | ''; |
13 | in | 13 | in |
14 | { | 14 | { |
15 | config.services.backup.profiles.mail.excludeFile = '' | 15 | config = lib.mkIf config.myServices.mail.enable { |
16 | + /var/lib/dhparams | 16 | services.backup.profiles.mail.excludeFile = '' |
17 | + /var/lib/dovecot | 17 | + /var/lib/dhparams |
18 | ''; | 18 | + /var/lib/dovecot |
19 | config.secrets.keys = [ | 19 | ''; |
20 | { | 20 | secrets.keys = [ |
21 | dest = "dovecot/ldap"; | 21 | { |
22 | user = config.services.dovecot2.user; | 22 | dest = "dovecot/ldap"; |
23 | group = config.services.dovecot2.group; | 23 | user = config.services.dovecot2.user; |
24 | permissions = "0400"; | 24 | group = config.services.dovecot2.group; |
25 | text = '' | 25 | permissions = "0400"; |
26 | hosts = ${myconfig.env.mail.dovecot.ldap.host} | 26 | text = '' |
27 | tls = yes | 27 | hosts = ${myconfig.env.mail.dovecot.ldap.host} |
28 | 28 | tls = yes | |
29 | dn = ${myconfig.env.mail.dovecot.ldap.dn} | ||
30 | dnpass = ${myconfig.env.mail.dovecot.ldap.password} | ||
31 | 29 | ||
32 | auth_bind = yes | 30 | dn = ${myconfig.env.mail.dovecot.ldap.dn} |
31 | dnpass = ${myconfig.env.mail.dovecot.ldap.password} | ||
33 | 32 | ||
34 | ldap_version = 3 | 33 | auth_bind = yes |
35 | 34 | ||
36 | base = ${myconfig.env.mail.dovecot.ldap.base} | 35 | ldap_version = 3 |
37 | scope = subtree | ||
38 | 36 | ||
39 | user_filter = ${myconfig.env.mail.dovecot.ldap.filter} | 37 | base = ${myconfig.env.mail.dovecot.ldap.base} |
40 | pass_filter = ${myconfig.env.mail.dovecot.ldap.filter} | 38 | scope = subtree |
41 | 39 | ||
42 | user_attrs = ${myconfig.env.mail.dovecot.ldap.user_attrs} | 40 | user_filter = ${myconfig.env.mail.dovecot.ldap.filter} |
43 | pass_attrs = ${myconfig.env.mail.dovecot.ldap.pass_attrs} | 41 | pass_filter = ${myconfig.env.mail.dovecot.ldap.filter} |
44 | ''; | ||
45 | } | ||
46 | ]; | ||
47 | 42 | ||
48 | config.users.users.vhost = { | 43 | user_attrs = ${myconfig.env.mail.dovecot.ldap.user_attrs} |
49 | group = "vhost"; | 44 | pass_attrs = ${myconfig.env.mail.dovecot.ldap.pass_attrs} |
50 | uid = config.ids.uids.vhost; | 45 | ''; |
51 | }; | 46 | } |
52 | config.users.groups.vhost.gid = config.ids.gids.vhost; | ||
53 | |||
54 | # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous....... | ||
55 | config.services.dovecot2 = { | ||
56 | enable = true; | ||
57 | enablePAM = false; | ||
58 | enablePop3 = true; | ||
59 | enableImap = true; | ||
60 | enableLmtp = true; | ||
61 | protocols = [ "sieve" ]; | ||
62 | modules = [ | ||
63 | pkgs.dovecot_pigeonhole | ||
64 | pkgs.dovecot_fts-xapian | ||
65 | ]; | ||
66 | mailUser = "vhost"; | ||
67 | mailGroup = "vhost"; | ||
68 | createMailUser = false; | ||
69 | mailboxes = [ | ||
70 | { name = "Trash"; auto = "subscribe"; specialUse = "Trash"; } | ||
71 | { name = "Junk"; auto = "subscribe"; specialUse = "Junk"; } | ||
72 | { name = "Sent"; auto = "subscribe"; specialUse = "Sent"; } | ||
73 | { name = "Drafts"; auto = "subscribe"; specialUse = "Drafts"; } | ||
74 | ]; | 47 | ]; |
75 | mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap"; | 48 | |
76 | sslServerCert = "/var/lib/acme/mail/fullchain.pem"; | 49 | users.users.vhost = { |
77 | sslServerKey = "/var/lib/acme/mail/key.pem"; | 50 | group = "vhost"; |
78 | sslCACert = "/var/lib/acme/mail/fullchain.pem"; | 51 | uid = config.ids.uids.vhost; |
79 | extraConfig = builtins.concatStringsSep "\n" [ | 52 | }; |
80 | '' | 53 | users.groups.vhost.gid = config.ids.gids.vhost; |
81 | postmaster_address = postmaster@immae.eu | 54 | |
82 | mail_attribute_dict = file:%h/dovecot-attributes | 55 | # https://blog.zeninc.net/index.php?post/2018/04/01/Un-annuaire-pour-les-gouverner-tous....... |
83 | imap_idle_notify_interval = 20 mins | 56 | services.dovecot2 = { |
84 | namespace inbox { | 57 | enable = true; |
85 | type = private | 58 | enablePAM = false; |
86 | separator = / | 59 | enablePop3 = true; |
87 | inbox = yes | 60 | enableImap = true; |
88 | list = yes | 61 | enableLmtp = true; |
89 | } | 62 | protocols = [ "sieve" ]; |
90 | '' | 63 | modules = [ |
91 | 64 | pkgs.dovecot_pigeonhole | |
92 | # Full text search | 65 | pkgs.dovecot_fts-xapian |
93 | '' | 66 | ]; |
94 | # needs to be bigger than any mailbox size | 67 | mailUser = "vhost"; |
95 | default_vsz_limit = 2GB | 68 | mailGroup = "vhost"; |
96 | mail_plugins = $mail_plugins fts fts_xapian | 69 | createMailUser = false; |
70 | mailboxes = [ | ||
71 | { name = "Trash"; auto = "subscribe"; specialUse = "Trash"; } | ||
72 | { name = "Junk"; auto = "subscribe"; specialUse = "Junk"; } | ||
73 | { name = "Sent"; auto = "subscribe"; specialUse = "Sent"; } | ||
74 | { name = "Drafts"; auto = "subscribe"; specialUse = "Drafts"; } | ||
75 | ]; | ||
76 | mailLocation = "mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap"; | ||
77 | sslServerCert = "/var/lib/acme/mail/fullchain.pem"; | ||
78 | sslServerKey = "/var/lib/acme/mail/key.pem"; | ||
79 | sslCACert = "/var/lib/acme/mail/fullchain.pem"; | ||
80 | extraConfig = builtins.concatStringsSep "\n" [ | ||
81 | '' | ||
82 | postmaster_address = postmaster@immae.eu | ||
83 | mail_attribute_dict = file:%h/dovecot-attributes | ||
84 | imap_idle_notify_interval = 20 mins | ||
85 | namespace inbox { | ||
86 | type = private | ||
87 | separator = / | ||
88 | inbox = yes | ||
89 | list = yes | ||
90 | } | ||
91 | '' | ||
92 | |||
93 | # Full text search | ||
94 | '' | ||
95 | # needs to be bigger than any mailbox size | ||
96 | default_vsz_limit = 2GB | ||
97 | mail_plugins = $mail_plugins fts fts_xapian | ||
98 | plugin { | ||
99 | plugin = fts fts_xapian | ||
100 | fts = xapian | ||
101 | fts_xapian = partial=2 full=20 | ||
102 | fts_autoindex = yes | ||
103 | fts_autoindex_exclude = \Junk | ||
104 | fts_autoindex_exclude2 = \Trash | ||
105 | fts_autoindex_exclude3 = Virtual/* | ||
106 | } | ||
107 | '' | ||
108 | |||
109 | # Antispam | ||
110 | # https://docs.iredmail.org/dovecot.imapsieve.html | ||
111 | '' | ||
112 | # imap_sieve plugin added below | ||
113 | |||
97 | plugin { | 114 | plugin { |
98 | plugin = fts fts_xapian | 115 | sieve_plugins = sieve_imapsieve sieve_extprograms |
99 | fts = xapian | 116 | imapsieve_url = sieve://127.0.0.1:4190 |
100 | fts_xapian = partial=2 full=20 | 117 | |
101 | fts_autoindex = yes | 118 | # From elsewhere to Junk folder |
102 | fts_autoindex_exclude = \Junk | 119 | imapsieve_mailbox1_name = Junk |
103 | fts_autoindex_exclude2 = \Trash | 120 | imapsieve_mailbox1_causes = COPY APPEND |
104 | fts_autoindex_exclude3 = Virtual/* | 121 | imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin |
105 | } | 122 | |
106 | '' | 123 | # From Junk folder to elsewhere |
107 | 124 | imapsieve_mailbox2_name = * | |
108 | # Antispam | 125 | imapsieve_mailbox2_from = Junk |
109 | # https://docs.iredmail.org/dovecot.imapsieve.html | 126 | imapsieve_mailbox2_causes = COPY |
110 | '' | 127 | imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin |
111 | # imap_sieve plugin added below | 128 | |
112 | 129 | sieve_pipe_bin_dir = ${sieve_bin} | |
113 | plugin { | 130 | |
114 | sieve_plugins = sieve_imapsieve sieve_extprograms | 131 | sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment |
115 | imapsieve_url = sieve://127.0.0.1:4190 | ||
116 | |||
117 | # From elsewhere to Junk folder | ||
118 | imapsieve_mailbox1_name = Junk | ||
119 | imapsieve_mailbox1_causes = COPY APPEND | ||
120 | imapsieve_mailbox1_before = file:${./sieve_scripts}/report_spam.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
121 | |||
122 | # From Junk folder to elsewhere | ||
123 | imapsieve_mailbox2_name = * | ||
124 | imapsieve_mailbox2_from = Junk | ||
125 | imapsieve_mailbox2_causes = COPY | ||
126 | imapsieve_mailbox2_before = file:${./sieve_scripts}/report_ham.sieve;bindir=/var/lib/vhost/.imapsieve_bin | ||
127 | |||
128 | sieve_pipe_bin_dir = ${sieve_bin} | ||
129 | |||
130 | sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment | ||
131 | } | ||
132 | '' | ||
133 | # Services to listen | ||
134 | '' | ||
135 | service imap-login { | ||
136 | inet_listener imap { | ||
137 | } | 132 | } |
138 | inet_listener imaps { | 133 | '' |
134 | # Services to listen | ||
135 | '' | ||
136 | service imap-login { | ||
137 | inet_listener imap { | ||
138 | } | ||
139 | inet_listener imaps { | ||
140 | } | ||
139 | } | 141 | } |
140 | } | 142 | service pop3-login { |
141 | service pop3-login { | 143 | inet_listener pop3 { |
142 | inet_listener pop3 { | 144 | } |
145 | inet_listener pop3s { | ||
146 | } | ||
143 | } | 147 | } |
144 | inet_listener pop3s { | 148 | service imap { |
145 | } | 149 | } |
146 | } | 150 | service pop3 { |
147 | service imap { | ||
148 | } | ||
149 | service pop3 { | ||
150 | } | ||
151 | service auth { | ||
152 | unix_listener auth-userdb { | ||
153 | } | 151 | } |
154 | unix_listener ${config.services.postfix.config.queue_directory}/private/auth { | 152 | service auth { |
155 | mode = 0666 | 153 | unix_listener auth-userdb { |
154 | } | ||
155 | unix_listener ${config.services.postfix.config.queue_directory}/private/auth { | ||
156 | mode = 0666 | ||
157 | } | ||
156 | } | 158 | } |
157 | } | 159 | service auth-worker { |
158 | service auth-worker { | ||
159 | } | ||
160 | service dict { | ||
161 | unix_listener dict { | ||
162 | } | 160 | } |
163 | } | 161 | service dict { |
164 | service stats { | 162 | unix_listener dict { |
165 | unix_listener stats-reader { | 163 | } |
166 | user = vhost | ||
167 | group = vhost | ||
168 | mode = 0660 | ||
169 | } | 164 | } |
170 | unix_listener stats-writer { | 165 | service stats { |
171 | user = vhost | 166 | unix_listener stats-reader { |
172 | group = vhost | 167 | user = vhost |
173 | mode = 0660 | 168 | group = vhost |
169 | mode = 0660 | ||
170 | } | ||
171 | unix_listener stats-writer { | ||
172 | user = vhost | ||
173 | group = vhost | ||
174 | mode = 0660 | ||
175 | } | ||
174 | } | 176 | } |
175 | } | 177 | '' |
176 | '' | 178 | |
177 | 179 | # Authentification | |
178 | # Authentification | 180 | '' |
179 | '' | 181 | first_valid_uid = ${toString config.ids.uids.vhost} |
180 | first_valid_uid = ${toString config.ids.uids.vhost} | 182 | disable_plaintext_auth = yes |
181 | disable_plaintext_auth = yes | 183 | passdb { |
182 | passdb { | 184 | driver = ldap |
183 | driver = ldap | 185 | args = ${config.secrets.fullPaths."dovecot/ldap"} |
184 | args = ${config.secrets.fullPaths."dovecot/ldap"} | 186 | } |
185 | } | 187 | userdb { |
186 | userdb { | 188 | driver = static |
187 | driver = static | 189 | args = user=%u uid=vhost gid=vhost home=/var/lib/vhost/%d/%n/ mail=mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap |
188 | args = user=%u uid=vhost gid=vhost home=/var/lib/vhost/%d/%n/ mail=mbox:~/Mail:INBOX=~/Mail/Inbox:INDEX=~/.imap | 190 | } |
189 | } | 191 | '' |
190 | '' | ||
191 | |||
192 | # Zlib | ||
193 | '' | ||
194 | mail_plugins = $mail_plugins zlib | ||
195 | plugin { | ||
196 | zlib_save_level = 6 | ||
197 | zlib_save = gz | ||
198 | } | ||
199 | '' | ||
200 | 192 | ||
201 | # Sieve | 193 | # Zlib |
202 | '' | 194 | '' |
203 | plugin { | 195 | mail_plugins = $mail_plugins zlib |
204 | sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve | 196 | plugin { |
205 | } | 197 | zlib_save_level = 6 |
206 | service managesieve-login { | 198 | zlib_save = gz |
207 | } | 199 | } |
208 | service managesieve { | 200 | '' |
209 | } | ||
210 | '' | ||
211 | |||
212 | # Virtual mailboxes | ||
213 | '' | ||
214 | mail_plugins = $mail_plugins virtual | ||
215 | namespace Virtual { | ||
216 | prefix = Virtual/ | ||
217 | location = virtual:~/Virtual | ||
218 | } | ||
219 | '' | ||
220 | 201 | ||
221 | # Protocol specific configuration | 202 | # Sieve |
222 | # Needs to come last if there are mail_plugins entries | 203 | '' |
223 | '' | 204 | plugin { |
224 | protocol imap { | 205 | sieve = file:~/sieve;bindir=~/.sieve-bin;active=~/.dovecot.sieve |
225 | mail_plugins = $mail_plugins imap_sieve | 206 | } |
226 | } | 207 | service managesieve-login { |
227 | protocol lda { | 208 | } |
228 | mail_plugins = $mail_plugins sieve | 209 | service managesieve { |
229 | } | 210 | } |
230 | '' | 211 | '' |
231 | ]; | 212 | |
232 | }; | 213 | # Virtual mailboxes |
233 | config.networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ]; | 214 | '' |
234 | config.system.activationScripts.dovecot = { | 215 | mail_plugins = $mail_plugins virtual |
235 | deps = [ "users" ]; | 216 | namespace Virtual { |
236 | text ='' | 217 | prefix = Virtual/ |
237 | install -m 0755 -o vhost -g vhost -d /var/lib/vhost | 218 | location = virtual:~/Virtual |
238 | ''; | 219 | } |
239 | }; | 220 | '' |
240 | 221 | ||
241 | config.security.acme.certs."mail" = { | 222 | # Protocol specific configuration |
242 | postRun = '' | 223 | # Needs to come last if there are mail_plugins entries |
243 | systemctl restart dovecot2.service | 224 | '' |
244 | ''; | 225 | protocol imap { |
245 | extraDomains = { | 226 | mail_plugins = $mail_plugins imap_sieve |
246 | "imap.immae.eu" = null; | 227 | } |
247 | "pop3.immae.eu" = null; | 228 | protocol lda { |
229 | mail_plugins = $mail_plugins sieve | ||
230 | } | ||
231 | '' | ||
232 | ]; | ||
233 | }; | ||
234 | networking.firewall.allowedTCPPorts = [ 110 143 993 995 4190 ]; | ||
235 | system.activationScripts.dovecot = { | ||
236 | deps = [ "users" ]; | ||
237 | text ='' | ||
238 | install -m 0755 -o vhost -g vhost -d /var/lib/vhost | ||
239 | ''; | ||
240 | }; | ||
241 | |||
242 | security.acme.certs."mail" = { | ||
243 | postRun = '' | ||
244 | systemctl restart dovecot2.service | ||
245 | ''; | ||
246 | extraDomains = { | ||
247 | "imap.immae.eu" = null; | ||
248 | "pop3.immae.eu" = null; | ||
249 | }; | ||
248 | }; | 250 | }; |
249 | }; | 251 | }; |
250 | } | 252 | } |
diff --git a/modules/private/mail/milters.nix b/modules/private/mail/milters.nix index c4bd990..123af4a 100644 --- a/modules/private/mail/milters.nix +++ b/modules/private/mail/milters.nix | |||
@@ -12,112 +12,114 @@ | |||
12 | milters sockets | 12 | milters sockets |
13 | ''; | 13 | ''; |
14 | }; | 14 | }; |
15 | config.secrets.keys = [ | 15 | config = lib.mkIf config.myServices.mail.enable { |
16 | { | 16 | secrets.keys = [ |
17 | dest = "opendkim/eldiron.private"; | 17 | { |
18 | user = config.services.opendkim.user; | 18 | dest = "opendkim/eldiron.private"; |
19 | group = config.services.opendkim.group; | 19 | user = config.services.opendkim.user; |
20 | permissions = "0400"; | 20 | group = config.services.opendkim.group; |
21 | text = myconfig.env.mail.dkim.eldiron.private; | 21 | permissions = "0400"; |
22 | } | 22 | text = myconfig.env.mail.dkim.eldiron.private; |
23 | { | 23 | } |
24 | dest = "opendkim/eldiron.txt"; | 24 | { |
25 | user = config.services.opendkim.user; | 25 | dest = "opendkim/eldiron.txt"; |
26 | group = config.services.opendkim.group; | 26 | user = config.services.opendkim.user; |
27 | permissions = "0444"; | 27 | group = config.services.opendkim.group; |
28 | text = '' | 28 | permissions = "0444"; |
29 | eldiron._domainkey IN TXT ${myconfig.env.mail.dkim.eldiron.public}''; | 29 | text = '' |
30 | } | 30 | eldiron._domainkey IN TXT ${myconfig.env.mail.dkim.eldiron.public}''; |
31 | { | 31 | } |
32 | dest = "opendmarc/ignore.hosts"; | 32 | { |
33 | user = config.services.opendmarc.user; | 33 | dest = "opendmarc/ignore.hosts"; |
34 | group = config.services.opendmarc.group; | 34 | user = config.services.opendmarc.user; |
35 | permissions = "0400"; | 35 | group = config.services.opendmarc.group; |
36 | text = myconfig.env.mail.dmarc.ignore_hosts; | 36 | permissions = "0400"; |
37 | } | 37 | text = myconfig.env.mail.dmarc.ignore_hosts; |
38 | ]; | 38 | } |
39 | config.users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ]; | ||
40 | config.services.opendkim = { | ||
41 | enable = true; | ||
42 | socket = "local:${config.myServices.mail.milters.sockets.opendkim}"; | ||
43 | domains = builtins.concatStringsSep "," (lib.flatten (map | ||
44 | (zone: map | ||
45 | (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}") | ||
46 | (zone.withEmail or []) | ||
47 | ) | ||
48 | myconfig.env.dns.masterZones | ||
49 | )); | ||
50 | keyPath = "${config.secrets.location}/opendkim"; | ||
51 | selector = "eldiron"; | ||
52 | configFile = pkgs.writeText "opendkim.conf" '' | ||
53 | SubDomains yes | ||
54 | UMask 002 | ||
55 | ''; | ||
56 | group = config.services.postfix.group; | ||
57 | }; | ||
58 | config.systemd.services.opendkim.preStart = lib.mkBefore '' | ||
59 | # Skip the prestart script as keys are handled in secrets | ||
60 | exit 0 | ||
61 | ''; | ||
62 | config.services.filesWatcher.opendkim = { | ||
63 | restart = true; | ||
64 | paths = [ | ||
65 | config.secrets.fullPaths."opendkim/eldiron.private" | ||
66 | ]; | 39 | ]; |
67 | }; | 40 | users.users."${config.services.opendkim.user}".extraGroups = [ "keys" ]; |
68 | 41 | services.opendkim = { | |
69 | config.users.users."${config.services.opendmarc.user}".extraGroups = [ "keys" ]; | 42 | enable = true; |
70 | config.services.opendmarc = { | 43 | socket = "local:${config.myServices.mail.milters.sockets.opendkim}"; |
71 | enable = true; | 44 | domains = builtins.concatStringsSep "," (lib.flatten (map |
72 | socket = "local:${config.myServices.mail.milters.sockets.opendmarc}"; | 45 | (zone: map |
73 | configFile = pkgs.writeText "opendmarc.conf" '' | 46 | (e: "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}") |
74 | AuthservID HOSTNAME | 47 | (zone.withEmail or []) |
75 | FailureReports false | 48 | ) |
76 | FailureReportsBcc postmaster@localhost.immae.eu | 49 | myconfig.env.dns.masterZones |
77 | FailureReportsOnNone true | 50 | )); |
78 | FailureReportsSentBy postmaster@immae.eu | 51 | keyPath = "${config.secrets.location}/opendkim"; |
79 | IgnoreAuthenticatedClients true | 52 | selector = "eldiron"; |
80 | IgnoreHosts ${config.secrets.fullPaths."opendmarc/ignore.hosts"} | 53 | configFile = pkgs.writeText "opendkim.conf" '' |
81 | SoftwareHeader true | 54 | SubDomains yes |
82 | SPFSelfValidate true | 55 | UMask 002 |
83 | TrustedAuthservIDs HOSTNAME, immae.eu, nef2.ens.fr | 56 | ''; |
84 | UMask 002 | 57 | group = config.services.postfix.group; |
58 | }; | ||
59 | systemd.services.opendkim.preStart = lib.mkBefore '' | ||
60 | # Skip the prestart script as keys are handled in secrets | ||
61 | exit 0 | ||
85 | ''; | 62 | ''; |
86 | group = config.services.postfix.group; | 63 | services.filesWatcher.opendkim = { |
87 | }; | 64 | restart = true; |
88 | config.services.filesWatcher.opendmarc = { | 65 | paths = [ |
89 | restart = true; | 66 | config.secrets.fullPaths."opendkim/eldiron.private" |
90 | paths = [ | 67 | ]; |
91 | config.secrets.fullPaths."opendmarc/ignore.hosts" | 68 | }; |
92 | ]; | 69 | |
93 | }; | 70 | users.users."${config.services.opendmarc.user}".extraGroups = [ "keys" ]; |
71 | services.opendmarc = { | ||
72 | enable = true; | ||
73 | socket = "local:${config.myServices.mail.milters.sockets.opendmarc}"; | ||
74 | configFile = pkgs.writeText "opendmarc.conf" '' | ||
75 | AuthservID HOSTNAME | ||
76 | FailureReports false | ||
77 | FailureReportsBcc postmaster@localhost.immae.eu | ||
78 | FailureReportsOnNone true | ||
79 | FailureReportsSentBy postmaster@immae.eu | ||
80 | IgnoreAuthenticatedClients true | ||
81 | IgnoreHosts ${config.secrets.fullPaths."opendmarc/ignore.hosts"} | ||
82 | SoftwareHeader true | ||
83 | SPFSelfValidate true | ||
84 | TrustedAuthservIDs HOSTNAME, immae.eu, nef2.ens.fr | ||
85 | UMask 002 | ||
86 | ''; | ||
87 | group = config.services.postfix.group; | ||
88 | }; | ||
89 | services.filesWatcher.opendmarc = { | ||
90 | restart = true; | ||
91 | paths = [ | ||
92 | config.secrets.fullPaths."opendmarc/ignore.hosts" | ||
93 | ]; | ||
94 | }; | ||
94 | 95 | ||
95 | config.services.openarc = { | 96 | services.openarc = { |
96 | enable = true; | 97 | enable = true; |
97 | user = "opendkim"; | 98 | user = "opendkim"; |
98 | socket = "local:${config.myServices.mail.milters.sockets.openarc}"; | 99 | socket = "local:${config.myServices.mail.milters.sockets.openarc}"; |
99 | group = config.services.postfix.group; | 100 | group = config.services.postfix.group; |
100 | configFile = pkgs.writeText "openarc.conf" '' | 101 | configFile = pkgs.writeText "openarc.conf" '' |
101 | AuthservID mail.immae.eu | 102 | AuthservID mail.immae.eu |
102 | Domain mail.immae.eu | 103 | Domain mail.immae.eu |
103 | KeyFile ${config.secrets.fullPaths."opendkim/eldiron.private"} | 104 | KeyFile ${config.secrets.fullPaths."opendkim/eldiron.private"} |
104 | Mode sv | 105 | Mode sv |
105 | Selector eldiron | 106 | Selector eldiron |
106 | SoftwareHeader yes | 107 | SoftwareHeader yes |
107 | Syslog Yes | 108 | Syslog Yes |
109 | ''; | ||
110 | }; | ||
111 | systemd.services.openarc.postStart = lib.optionalString | ||
112 | (lib.strings.hasPrefix "local:" config.services.openarc.socket) '' | ||
113 | while [ ! -S ${lib.strings.removePrefix "local:" config.services.openarc.socket} ]; do | ||
114 | sleep 0.5 | ||
115 | done | ||
116 | chmod g+w ${lib.strings.removePrefix "local:" config.services.openarc.socket} | ||
108 | ''; | 117 | ''; |
109 | }; | 118 | services.filesWatcher.openarc = { |
110 | config.systemd.services.openarc.postStart = lib.optionalString | 119 | restart = true; |
111 | (lib.strings.hasPrefix "local:" config.services.openarc.socket) '' | 120 | paths = [ |
112 | while [ ! -S ${lib.strings.removePrefix "local:" config.services.openarc.socket} ]; do | 121 | config.secrets.fullPaths."opendkim/eldiron.private" |
113 | sleep 0.5 | 122 | ]; |
114 | done | 123 | }; |
115 | chmod g+w ${lib.strings.removePrefix "local:" config.services.openarc.socket} | ||
116 | ''; | ||
117 | config.services.filesWatcher.openarc = { | ||
118 | restart = true; | ||
119 | paths = [ | ||
120 | config.secrets.fullPaths."opendkim/eldiron.private" | ||
121 | ]; | ||
122 | }; | 124 | }; |
123 | } | 125 | } |
diff --git a/modules/private/mail/postfix.nix b/modules/private/mail/postfix.nix index edfd196..9fdc7bd 100644 --- a/modules/private/mail/postfix.nix +++ b/modules/private/mail/postfix.nix | |||
@@ -1,267 +1,269 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | 1 | { lib, pkgs, config, myconfig, ... }: |
2 | { | 2 | { |
3 | config.services.backup.profiles.mail.excludeFile = '' | 3 | config = lib.mkIf config.myServices.mail.enable { |
4 | + /var/lib/postfix | 4 | services.backup.profiles.mail.excludeFile = '' |
5 | ''; | 5 | + /var/lib/postfix |
6 | config.secrets.keys = [ | 6 | ''; |
7 | { | 7 | secrets.keys = [ |
8 | dest = "postfix/mysql_alias_maps"; | 8 | { |
9 | user = config.services.postfix.user; | 9 | dest = "postfix/mysql_alias_maps"; |
10 | group = config.services.postfix.group; | 10 | user = config.services.postfix.user; |
11 | permissions = "0440"; | 11 | group = config.services.postfix.group; |
12 | text = '' | 12 | permissions = "0440"; |
13 | # We need to specify that option to trigger ssl connection | 13 | text = '' |
14 | tls_ciphers = TLSv1.2 | 14 | # We need to specify that option to trigger ssl connection |
15 | user = ${myconfig.env.mail.postfix.mysql.user} | 15 | tls_ciphers = TLSv1.2 |
16 | password = ${myconfig.env.mail.postfix.mysql.password} | 16 | user = ${myconfig.env.mail.postfix.mysql.user} |
17 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | 17 | password = ${myconfig.env.mail.postfix.mysql.password} |
18 | dbname = ${myconfig.env.mail.postfix.mysql.database} | 18 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} |
19 | query = SELECT DISTINCT destination | 19 | dbname = ${myconfig.env.mail.postfix.mysql.database} |
20 | FROM forwardings_merge | 20 | query = SELECT DISTINCT destination |
21 | WHERE | 21 | FROM forwardings_merge |
22 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | 22 | WHERE |
23 | AND active = 1 | 23 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) |
24 | AND '%s' NOT IN | 24 | AND active = 1 |
25 | ( | 25 | AND '%s' NOT IN |
26 | SELECT source | 26 | ( |
27 | SELECT source | ||
28 | FROM forwardings_blacklisted | ||
29 | WHERE source = '%s' | ||
30 | ) UNION | ||
31 | SELECT 'devnull@immae.eu' | ||
27 | FROM forwardings_blacklisted | 32 | FROM forwardings_blacklisted |
28 | WHERE source = '%s' | 33 | WHERE source = '%s' |
29 | ) UNION | 34 | ''; |
30 | SELECT 'devnull@immae.eu' | 35 | } |
31 | FROM forwardings_blacklisted | 36 | { |
32 | WHERE source = '%s' | 37 | dest = "postfix/mysql_mailbox_maps"; |
33 | ''; | 38 | user = config.services.postfix.user; |
34 | } | 39 | group = config.services.postfix.group; |
35 | { | 40 | permissions = "0440"; |
36 | dest = "postfix/mysql_mailbox_maps"; | 41 | text = '' |
37 | user = config.services.postfix.user; | 42 | # We need to specify that option to trigger ssl connection |
38 | group = config.services.postfix.group; | 43 | tls_ciphers = TLSv1.2 |
39 | permissions = "0440"; | 44 | user = ${myconfig.env.mail.postfix.mysql.user} |
40 | text = '' | 45 | password = ${myconfig.env.mail.postfix.mysql.password} |
41 | # We need to specify that option to trigger ssl connection | 46 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} |
42 | tls_ciphers = TLSv1.2 | 47 | dbname = ${myconfig.env.mail.postfix.mysql.database} |
43 | user = ${myconfig.env.mail.postfix.mysql.user} | 48 | result_format = /%d/%u |
44 | password = ${myconfig.env.mail.postfix.mysql.password} | 49 | query = SELECT DISTINCT '%s' |
45 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | 50 | FROM mailboxes |
46 | dbname = ${myconfig.env.mail.postfix.mysql.database} | 51 | WHERE active = 1 |
47 | result_format = /%d/%u | 52 | AND ( |
48 | query = SELECT DISTINCT '%s' | 53 | (domain = '%d' AND user = '%u' AND regex = 0) |
49 | FROM mailboxes | 54 | OR ( |
50 | WHERE active = 1 | 55 | regex = 1 |
51 | AND ( | 56 | AND '%d' REGEXP CONCAT('^',domain,'$') |
52 | (domain = '%d' AND user = '%u' AND regex = 0) | 57 | AND '%u' REGEXP CONCAT('^',user,'$') |
53 | OR ( | 58 | ) |
54 | regex = 1 | ||
55 | AND '%d' REGEXP CONCAT('^',domain,'$') | ||
56 | AND '%u' REGEXP CONCAT('^',user,'$') | ||
57 | ) | 59 | ) |
58 | ) | 60 | LIMIT 1 |
59 | LIMIT 1 | ||
60 | ''; | ||
61 | } | ||
62 | { | ||
63 | dest = "postfix/mysql_sender_login_maps"; | ||
64 | user = config.services.postfix.user; | ||
65 | group = config.services.postfix.group; | ||
66 | permissions = "0440"; | ||
67 | text = '' | ||
68 | # We need to specify that option to trigger ssl connection | ||
69 | tls_ciphers = TLSv1.2 | ||
70 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
71 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
72 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
73 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
74 | query = SELECT DISTINCT destination | ||
75 | FROM forwardings_merge | ||
76 | WHERE | ||
77 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
78 | AND active = 1 | ||
79 | UNION SELECT '%s' AS destination | ||
80 | ''; | 61 | ''; |
81 | } | 62 | } |
82 | ]; | 63 | { |
64 | dest = "postfix/mysql_sender_login_maps"; | ||
65 | user = config.services.postfix.user; | ||
66 | group = config.services.postfix.group; | ||
67 | permissions = "0440"; | ||
68 | text = '' | ||
69 | # We need to specify that option to trigger ssl connection | ||
70 | tls_ciphers = TLSv1.2 | ||
71 | user = ${myconfig.env.mail.postfix.mysql.user} | ||
72 | password = ${myconfig.env.mail.postfix.mysql.password} | ||
73 | hosts = unix:${myconfig.env.mail.postfix.mysql.socket} | ||
74 | dbname = ${myconfig.env.mail.postfix.mysql.database} | ||
75 | query = SELECT DISTINCT destination | ||
76 | FROM forwardings_merge | ||
77 | WHERE | ||
78 | ((regex = 1 AND '%s' REGEXP CONCAT('^',source,'$') ) OR (regex = 0 AND source = '%s')) | ||
79 | AND active = 1 | ||
80 | UNION SELECT '%s' AS destination | ||
81 | ''; | ||
82 | } | ||
83 | ]; | ||
83 | 84 | ||
84 | config.networking.firewall.allowedTCPPorts = [ 25 465 587 ]; | 85 | networking.firewall.allowedTCPPorts = [ 25 465 587 ]; |
85 | 86 | ||
86 | config.nixpkgs.overlays = [ (self: super: { | 87 | nixpkgs.overlays = [ (self: super: { |
87 | postfix = super.postfix.override { withMySQL = true; }; | 88 | postfix = super.postfix.override { withMySQL = true; }; |
88 | }) ]; | 89 | }) ]; |
89 | config.users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; | 90 | users.users."${config.services.postfix.user}".extraGroups = [ "keys" ]; |
90 | config.services.filesWatcher.postfix = { | 91 | services.filesWatcher.postfix = { |
91 | restart = true; | 92 | restart = true; |
92 | paths = [ | 93 | paths = [ |
93 | config.secrets.fullPaths."postfix/mysql_alias_maps" | 94 | config.secrets.fullPaths."postfix/mysql_alias_maps" |
94 | config.secrets.fullPaths."postfix/mysql_mailbox_maps" | 95 | config.secrets.fullPaths."postfix/mysql_mailbox_maps" |
95 | config.secrets.fullPaths."postfix/mysql_sender_login_maps" | 96 | config.secrets.fullPaths."postfix/mysql_sender_login_maps" |
96 | ]; | 97 | ]; |
97 | }; | 98 | }; |
98 | config.services.postfix = { | 99 | services.postfix = { |
99 | mapFiles = let | 100 | mapFiles = let |
100 | recipient_maps = let | 101 | recipient_maps = let |
101 | name = n: i: "relay_${n}_${toString i}"; | 102 | name = n: i: "relay_${n}_${toString i}"; |
102 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( | 103 | pair = n: i: m: lib.attrsets.nameValuePair (name n i) ( |
103 | if m.type == "hash" | 104 | if m.type == "hash" |
104 | then pkgs.writeText (name n i) m.content | 105 | then pkgs.writeText (name n i) m.content |
105 | else null | ||
106 | ); | ||
107 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; | ||
108 | in lib.attrsets.filterAttrs (k: v: v != null) ( | ||
109 | lib.attrsets.listToAttrs (lib.flatten ( | ||
110 | lib.attrsets.mapAttrsToList pairs myconfig.env.mail.postfix.backup_domains | ||
111 | )) | ||
112 | ); | ||
113 | relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( | ||
114 | lib.attrsets.mapAttrs' (n: v: | ||
115 | lib.attrsets.nameValuePair "recipient_access_${n}" ( | ||
116 | if lib.attrsets.hasAttr "relay_restrictions" v | ||
117 | then pkgs.writeText "recipient_access_${n}" v.relay_restrictions | ||
118 | else null | 106 | else null |
119 | ) | 107 | ); |
120 | ) myconfig.env.mail.postfix.backup_domains | 108 | pairs = n: v: lib.imap1 (i: m: pair n i m) v.recipient_maps; |
121 | ); | 109 | in lib.attrsets.filterAttrs (k: v: v != null) ( |
122 | in | 110 | lib.attrsets.listToAttrs (lib.flatten ( |
123 | recipient_maps // relay_restrictions; | 111 | lib.attrsets.mapAttrsToList pairs myconfig.env.mail.postfix.backup_domains |
124 | config = { | 112 | )) |
125 | ### postfix module overrides | 113 | ); |
126 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | 114 | relay_restrictions = lib.attrsets.filterAttrs (k: v: v != null) ( |
127 | smtp_tls_CAfile = lib.mkForce ""; | 115 | lib.attrsets.mapAttrs' (n: v: |
128 | smtp_tls_cert_file = lib.mkForce ""; | 116 | lib.attrsets.nameValuePair "recipient_access_${n}" ( |
129 | smtp_tls_key_file = lib.mkForce ""; | 117 | if lib.attrsets.hasAttr "relay_restrictions" v |
118 | then pkgs.writeText "recipient_access_${n}" v.relay_restrictions | ||
119 | else null | ||
120 | ) | ||
121 | ) myconfig.env.mail.postfix.backup_domains | ||
122 | ); | ||
123 | in | ||
124 | recipient_maps // relay_restrictions; | ||
125 | config = { | ||
126 | ### postfix module overrides | ||
127 | readme_directory = "${pkgs.postfix}/share/postfix/doc"; | ||
128 | smtp_tls_CAfile = lib.mkForce ""; | ||
129 | smtp_tls_cert_file = lib.mkForce ""; | ||
130 | smtp_tls_key_file = lib.mkForce ""; | ||
130 | 131 | ||
131 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" | 132 | message_size_limit = "1073741824"; # Don't put 0 here, it's not equivalent to "unlimited" |
132 | alias_database = "\$alias_maps"; | 133 | alias_database = "\$alias_maps"; |
133 | 134 | ||
134 | ### Virtual mailboxes config | 135 | ### Virtual mailboxes config |
135 | virtual_alias_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"; | 136 | virtual_alias_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_alias_maps"}"; |
136 | virtual_mailbox_domains = myconfig.env.mail.postfix.additional_mailbox_domains | 137 | virtual_mailbox_domains = myconfig.env.mail.postfix.additional_mailbox_domains |
137 | ++ lib.remove "localhost.immae.eu" (lib.remove null (lib.flatten (map | 138 | ++ lib.remove "localhost.immae.eu" (lib.remove null (lib.flatten (map |
138 | (zone: map | 139 | (zone: map |
139 | (e: if e.receive | 140 | (e: if e.receive |
140 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" | 141 | then "${e.domain}${lib.optionalString (e.domain != "") "."}${zone.name}" |
141 | else null | 142 | else null |
143 | ) | ||
144 | (zone.withEmail or []) | ||
142 | ) | 145 | ) |
143 | (zone.withEmail or []) | 146 | myconfig.env.dns.masterZones |
144 | ) | 147 | ))); |
145 | myconfig.env.dns.masterZones | 148 | virtual_mailbox_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"; |
146 | ))); | 149 | dovecot_destination_recipient_limit = "1"; |
147 | virtual_mailbox_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_mailbox_maps"}"; | 150 | virtual_transport = "dovecot"; |
148 | dovecot_destination_recipient_limit = "1"; | ||
149 | virtual_transport = "dovecot"; | ||
150 | 151 | ||
151 | ### Relay domains | 152 | ### Relay domains |
152 | relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) myconfig.env.mail.postfix.backup_domains); | 153 | relay_domains = lib.flatten (lib.attrsets.mapAttrsToList (n: v: v.domains or []) myconfig.env.mail.postfix.backup_domains); |
153 | relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: | 154 | relay_recipient_maps = lib.flatten (lib.attrsets.mapAttrsToList (n: v: |
154 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps | 155 | lib.imap1 (i: m: "${m.type}:/etc/postfix/relay_${n}_${toString i}") v.recipient_maps |
155 | ) myconfig.env.mail.postfix.backup_domains); | 156 | ) myconfig.env.mail.postfix.backup_domains); |
156 | smtpd_relay_restrictions = [ | 157 | smtpd_relay_restrictions = [ |
157 | "permit_mynetworks" | 158 | "permit_mynetworks" |
158 | "permit_sasl_authenticated" | 159 | "permit_sasl_authenticated" |
159 | "defer_unauth_destination" | 160 | "defer_unauth_destination" |
160 | ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: | 161 | ] ++ lib.flatten (lib.attrsets.mapAttrsToList (n: v: |
161 | if lib.attrsets.hasAttr "relay_restrictions" v | 162 | if lib.attrsets.hasAttr "relay_restrictions" v |
162 | then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] | 163 | then [ "check_recipient_access hash:/etc/postfix/recipient_access_${n}" ] |
163 | else [] | 164 | else [] |
164 | ) myconfig.env.mail.postfix.backup_domains); | 165 | ) myconfig.env.mail.postfix.backup_domains); |
165 | 166 | ||
166 | ### Additional smtpd configuration | 167 | ### Additional smtpd configuration |
167 | smtpd_tls_received_header = "yes"; | 168 | smtpd_tls_received_header = "yes"; |
168 | smtpd_tls_loglevel = "1"; | 169 | smtpd_tls_loglevel = "1"; |
169 | 170 | ||
170 | ### Email sending configuration | 171 | ### Email sending configuration |
171 | smtp_tls_security_level = "may"; | 172 | smtp_tls_security_level = "may"; |
172 | smtp_tls_loglevel = "1"; | 173 | smtp_tls_loglevel = "1"; |
173 | 174 | ||
174 | ### Force ip bind for smtp | 175 | ### Force ip bind for smtp |
175 | smtp_bind_address = myconfig.env.servers.eldiron.ips.main.ip4; | 176 | smtp_bind_address = myconfig.env.servers.eldiron.ips.main.ip4; |
176 | smtp_bind_address6 = builtins.head myconfig.env.servers.eldiron.ips.main.ip6; | 177 | smtp_bind_address6 = builtins.head myconfig.env.servers.eldiron.ips.main.ip6; |
177 | 178 | ||
178 | # #Unneeded if postfix can only send e-mail from "self" domains | 179 | # #Unneeded if postfix can only send e-mail from "self" domains |
179 | # #smtp_sasl_auth_enable = "yes"; | 180 | # #smtp_sasl_auth_enable = "yes"; |
180 | # #smtp_sasl_password_maps = "hash:/etc/postfix/relay_creds"; | 181 | # #smtp_sasl_password_maps = "hash:/etc/postfix/relay_creds"; |
181 | # #smtp_sasl_security_options = "noanonymous"; | 182 | # #smtp_sasl_security_options = "noanonymous"; |
182 | # #smtp_sender_dependent_authentication = "yes"; | 183 | # #smtp_sender_dependent_authentication = "yes"; |
183 | # #sender_dependent_relayhost_maps = "hash:/etc/postfix/sender_relay"; | 184 | # #sender_dependent_relayhost_maps = "hash:/etc/postfix/sender_relay"; |
184 | 185 | ||
185 | ### opendkim, opendmarc, openarc milters | 186 | ### opendkim, opendmarc, openarc milters |
186 | non_smtpd_milters = [ | 187 | non_smtpd_milters = [ |
187 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | 188 | "unix:${config.myServices.mail.milters.sockets.opendkim}" |
188 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | 189 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" |
189 | "unix:${config.myServices.mail.milters.sockets.openarc}" | 190 | "unix:${config.myServices.mail.milters.sockets.openarc}" |
190 | ]; | 191 | ]; |
191 | smtpd_milters = [ | 192 | smtpd_milters = [ |
192 | "unix:${config.myServices.mail.milters.sockets.opendkim}" | 193 | "unix:${config.myServices.mail.milters.sockets.opendkim}" |
193 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" | 194 | "unix:${config.myServices.mail.milters.sockets.opendmarc}" |
194 | "unix:${config.myServices.mail.milters.sockets.openarc}" | 195 | "unix:${config.myServices.mail.milters.sockets.openarc}" |
195 | ]; | ||
196 | }; | ||
197 | enable = true; | ||
198 | enableSmtp = true; | ||
199 | enableSubmission = true; | ||
200 | submissionOptions = { | ||
201 | smtpd_tls_security_level = "encrypt"; | ||
202 | smtpd_sasl_auth_enable = "yes"; | ||
203 | smtpd_tls_auth_only = "yes"; | ||
204 | smtpd_sasl_tls_security_options = "noanonymous"; | ||
205 | smtpd_sasl_type = "dovecot"; | ||
206 | smtpd_sasl_path = "private/auth"; | ||
207 | smtpd_reject_unlisted_recipient = "no"; | ||
208 | smtpd_client_restrictions = "permit_sasl_authenticated,reject"; | ||
209 | # Refuse to send e-mails with a From that is not handled | ||
210 | smtpd_sender_restrictions = | ||
211 | "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; | ||
212 | smtpd_sender_login_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"; | ||
213 | smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; | ||
214 | milter_macro_daemon_name = "ORIGINATING"; | ||
215 | smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; | ||
216 | }; | ||
217 | # FIXME: Mail adressed to localhost.immae.eu will still have mx-1 as | ||
218 | # prioritized MX, which provokes "mail for localhost.immae.eu loops | ||
219 | # back to myself" errors. This transport entry forces to push | ||
220 | # e-mails to its right destination. | ||
221 | transport = '' | ||
222 | localhost.immae.eu smtp:[immae.eu]:25 | ||
223 | ''; | ||
224 | destination = ["localhost"]; | ||
225 | # This needs to reverse DNS | ||
226 | hostname = "eldiron.immae.eu"; | ||
227 | setSendmail = true; | ||
228 | sslCert = "/var/lib/acme/mail/fullchain.pem"; | ||
229 | sslKey = "/var/lib/acme/mail/key.pem"; | ||
230 | recipientDelimiter = "+"; | ||
231 | masterConfig = { | ||
232 | submissions = { | ||
233 | type = "inet"; | ||
234 | private = false; | ||
235 | command = "smtpd"; | ||
236 | args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let | ||
237 | mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ]; | ||
238 | in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions) | ||
239 | ); | ||
240 | }; | ||
241 | dovecot = { | ||
242 | type = "unix"; | ||
243 | privileged = true; | ||
244 | chroot = false; | ||
245 | command = "pipe"; | ||
246 | args = let | ||
247 | # rspamd could be used as a milter, but then it cannot apply | ||
248 | # its checks "per user" (milter is not yet dispatched to | ||
249 | # users), so we wrap dovecot-lda inside rspamc per recipient | ||
250 | # here. | ||
251 | dovecot_exe = "${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f \${sender} -a \${original_recipient} -d \${user}@\${nexthop}"; | ||
252 | in [ | ||
253 | "flags=DRhu" "user=vhost:vhost" | ||
254 | "argv=${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d \${user}@\${nexthop} --mime --exec {${dovecot_exe}}" | ||
255 | ]; | 196 | ]; |
256 | }; | 197 | }; |
198 | enable = true; | ||
199 | enableSmtp = true; | ||
200 | enableSubmission = true; | ||
201 | submissionOptions = { | ||
202 | smtpd_tls_security_level = "encrypt"; | ||
203 | smtpd_sasl_auth_enable = "yes"; | ||
204 | smtpd_tls_auth_only = "yes"; | ||
205 | smtpd_sasl_tls_security_options = "noanonymous"; | ||
206 | smtpd_sasl_type = "dovecot"; | ||
207 | smtpd_sasl_path = "private/auth"; | ||
208 | smtpd_reject_unlisted_recipient = "no"; | ||
209 | smtpd_client_restrictions = "permit_sasl_authenticated,reject"; | ||
210 | # Refuse to send e-mails with a From that is not handled | ||
211 | smtpd_sender_restrictions = | ||
212 | "reject_sender_login_mismatch,reject_unlisted_sender,permit_sasl_authenticated,reject"; | ||
213 | smtpd_sender_login_maps = "mysql:${config.secrets.fullPaths."postfix/mysql_sender_login_maps"}"; | ||
214 | smtpd_recipient_restrictions = "permit_sasl_authenticated,reject"; | ||
215 | milter_macro_daemon_name = "ORIGINATING"; | ||
216 | smtpd_milters = "unix:${config.myServices.mail.milters.sockets.opendkim}"; | ||
217 | }; | ||
218 | # FIXME: Mail adressed to localhost.immae.eu will still have mx-1 as | ||
219 | # prioritized MX, which provokes "mail for localhost.immae.eu loops | ||
220 | # back to myself" errors. This transport entry forces to push | ||
221 | # e-mails to its right destination. | ||
222 | transport = '' | ||
223 | localhost.immae.eu smtp:[immae.eu]:25 | ||
224 | ''; | ||
225 | destination = ["localhost"]; | ||
226 | # This needs to reverse DNS | ||
227 | hostname = "eldiron.immae.eu"; | ||
228 | setSendmail = true; | ||
229 | sslCert = "/var/lib/acme/mail/fullchain.pem"; | ||
230 | sslKey = "/var/lib/acme/mail/key.pem"; | ||
231 | recipientDelimiter = "+"; | ||
232 | masterConfig = { | ||
233 | submissions = { | ||
234 | type = "inet"; | ||
235 | private = false; | ||
236 | command = "smtpd"; | ||
237 | args = ["-o" "smtpd_tls_wrappermode=yes" ] ++ (let | ||
238 | mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ]; | ||
239 | in lib.concatLists (lib.mapAttrsToList mkKeyVal config.services.postfix.submissionOptions) | ||
240 | ); | ||
241 | }; | ||
242 | dovecot = { | ||
243 | type = "unix"; | ||
244 | privileged = true; | ||
245 | chroot = false; | ||
246 | command = "pipe"; | ||
247 | args = let | ||
248 | # rspamd could be used as a milter, but then it cannot apply | ||
249 | # its checks "per user" (milter is not yet dispatched to | ||
250 | # users), so we wrap dovecot-lda inside rspamc per recipient | ||
251 | # here. | ||
252 | dovecot_exe = "${pkgs.dovecot}/libexec/dovecot/dovecot-lda -f \${sender} -a \${original_recipient} -d \${user}@\${nexthop}"; | ||
253 | in [ | ||
254 | "flags=DRhu" "user=vhost:vhost" | ||
255 | "argv=${pkgs.rspamd}/bin/rspamc -h ${config.myServices.mail.rspamd.sockets.worker-controller} -c bayes -d \${user}@\${nexthop} --mime --exec {${dovecot_exe}}" | ||
256 | ]; | ||
257 | }; | ||
258 | }; | ||
257 | }; | 259 | }; |
258 | }; | 260 | security.acme.certs."mail" = { |
259 | config.security.acme.certs."mail" = { | 261 | postRun = '' |
260 | postRun = '' | 262 | systemctl restart postfix.service |
261 | systemctl restart postfix.service | 263 | ''; |
262 | ''; | 264 | extraDomains = { |
263 | extraDomains = { | 265 | "smtp.immae.eu" = null; |
264 | "smtp.immae.eu" = null; | 266 | }; |
265 | }; | 267 | }; |
266 | }; | 268 | }; |
267 | } | 269 | } |
diff --git a/modules/private/mail/rspamd.nix b/modules/private/mail/rspamd.nix index af3541f..5e0a239 100644 --- a/modules/private/mail/rspamd.nix +++ b/modules/private/mail/rspamd.nix | |||
@@ -10,78 +10,80 @@ | |||
10 | rspamd sockets | 10 | rspamd sockets |
11 | ''; | 11 | ''; |
12 | }; | 12 | }; |
13 | config.services.backup.profiles.mail.excludeFile = '' | 13 | config = lib.mkIf config.myServices.mail.enable { |
14 | + /var/lib/rspamd | 14 | services.backup.profiles.mail.excludeFile = '' |
15 | ''; | 15 | + /var/lib/rspamd |
16 | config.services.cron.systemCronJobs = let | ||
17 | cron_script = pkgs.runCommand "cron_script" { | ||
18 | buildInputs = [ pkgs.makeWrapper ]; | ||
19 | } '' | ||
20 | mkdir -p $out | ||
21 | cp ${./scan_reported_mails} $out/scan_reported_mails | ||
22 | patchShebangs $out | ||
23 | for i in $out/*; do | ||
24 | wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils pkgs.rspamd pkgs.flock ]} | ||
25 | done | ||
26 | ''; | 16 | ''; |
27 | in | 17 | services.cron.systemCronJobs = let |
28 | [ "*/20 * * * * vhost ${cron_script}/scan_reported_mails" ]; | 18 | cron_script = pkgs.runCommand "cron_script" { |
29 | 19 | buildInputs = [ pkgs.makeWrapper ]; | |
30 | config.services.rspamd = { | 20 | } '' |
31 | enable = true; | 21 | mkdir -p $out |
32 | debug = true; | 22 | cp ${./scan_reported_mails} $out/scan_reported_mails |
33 | overrides = { | 23 | patchShebangs $out |
34 | "actions.conf".text = '' | 24 | for i in $out/*; do |
35 | reject = null; | 25 | wrapProgram "$i" --prefix PATH : ${lib.makeBinPath [ pkgs.coreutils pkgs.rspamd pkgs.flock ]} |
36 | add_header = 6; | 26 | done |
37 | greylist = null; | ||
38 | ''; | 27 | ''; |
39 | "milter_headers.conf".text = '' | 28 | in |
40 | extended_spam_headers = true; | 29 | [ "*/20 * * * * vhost ${cron_script}/scan_reported_mails" ]; |
41 | ''; | 30 | |
42 | }; | 31 | services.rspamd = { |
43 | locals = { | 32 | enable = true; |
44 | "redis.conf".text = '' | 33 | debug = true; |
45 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; | 34 | overrides = { |
46 | db = "${myconfig.env.mail.rspamd.redis.db}"; | 35 | "actions.conf".text = '' |
36 | reject = null; | ||
37 | add_header = 6; | ||
38 | greylist = null; | ||
39 | ''; | ||
40 | "milter_headers.conf".text = '' | ||
41 | extended_spam_headers = true; | ||
47 | ''; | 42 | ''; |
48 | "classifier-bayes.conf".text = '' | 43 | }; |
49 | users_enabled = true; | 44 | locals = { |
50 | backend = "redis"; | 45 | "redis.conf".text = '' |
51 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; | 46 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; |
52 | database = "${myconfig.env.mail.rspamd.redis.db}"; | 47 | db = "${myconfig.env.mail.rspamd.redis.db}"; |
53 | autolearn = true; | 48 | ''; |
54 | cache { | 49 | "classifier-bayes.conf".text = '' |
50 | users_enabled = true; | ||
55 | backend = "redis"; | 51 | backend = "redis"; |
56 | } | 52 | servers = "${myconfig.env.mail.rspamd.redis.socket}"; |
57 | new_schema = true; | 53 | database = "${myconfig.env.mail.rspamd.redis.db}"; |
58 | statfile { | 54 | autolearn = true; |
59 | BAYES_HAM { | 55 | cache { |
60 | spam = false; | 56 | backend = "redis"; |
61 | } | 57 | } |
62 | BAYES_SPAM { | 58 | new_schema = true; |
63 | spam = true; | 59 | statfile { |
60 | BAYES_HAM { | ||
61 | spam = false; | ||
62 | } | ||
63 | BAYES_SPAM { | ||
64 | spam = true; | ||
65 | } | ||
64 | } | 66 | } |
65 | } | 67 | ''; |
66 | ''; | 68 | }; |
67 | }; | 69 | workers = { |
68 | workers = { | 70 | controller = { |
69 | controller = { | 71 | extraConfig = '' |
70 | extraConfig = '' | 72 | enable_password = "${myconfig.env.mail.rspamd.write_password_hashed}"; |
71 | enable_password = "${myconfig.env.mail.rspamd.write_password_hashed}"; | 73 | password = "${myconfig.env.mail.rspamd.read_password_hashed}"; |
72 | password = "${myconfig.env.mail.rspamd.read_password_hashed}"; | 74 | ''; |
73 | ''; | 75 | bindSockets = [ { |
74 | bindSockets = [ { | 76 | socket = config.myServices.mail.rspamd.sockets.worker-controller; |
75 | socket = config.myServices.mail.rspamd.sockets.worker-controller; | 77 | mode = "0660"; |
76 | mode = "0660"; | 78 | owner = config.services.rspamd.user; |
77 | owner = config.services.rspamd.user; | 79 | group = "vhost"; |
78 | group = "vhost"; | 80 | } ]; |
79 | } ]; | 81 | }; |
82 | }; | ||
83 | postfix = { | ||
84 | enable = true; | ||
85 | config = {}; | ||
80 | }; | 86 | }; |
81 | }; | ||
82 | postfix = { | ||
83 | enable = true; | ||
84 | config = {}; | ||
85 | }; | 87 | }; |
86 | }; | 88 | }; |
87 | } | 89 | } |
diff --git a/modules/private/mpd.nix b/modules/private/mpd.nix index b224165..759c9d3 100644 --- a/modules/private/mpd.nix +++ b/modules/private/mpd.nix | |||
@@ -1,6 +1,7 @@ | |||
1 | { lib, pkgs, config, myconfig, ... }: | 1 | { lib, pkgs, config, myconfig, ... }: |
2 | { | 2 | { |
3 | config = { | 3 | options.myServices.mpd.enable = lib.mkEnableOption "enable MPD"; |
4 | config = lib.mkIf config.myServices.mpd.enable { | ||
4 | services.backup.profiles.mpd = { | 5 | services.backup.profiles.mpd = { |
5 | rootDir = "/var/lib/mpd"; | 6 | rootDir = "/var/lib/mpd"; |
6 | }; | 7 | }; |
diff --git a/modules/private/system/backup-2.nix b/modules/private/system/backup-2.nix new file mode 100644 index 0000000..c67eab6 --- /dev/null +++ b/modules/private/system/backup-2.nix | |||
@@ -0,0 +1,24 @@ | |||
1 | { privateFiles }: | ||
2 | { config, pkgs, myconfig, ... }: | ||
3 | { | ||
4 | boot.kernelPackages = pkgs.linuxPackages_latest; | ||
5 | _module.args.privateFiles = privateFiles; | ||
6 | imports = builtins.attrValues (import ../..); | ||
7 | |||
8 | deployment = { | ||
9 | targetEnv = "hetznerCloud"; | ||
10 | hetznerCloud = { | ||
11 | authToken = myconfig.env.hetznerCloud.authToken; | ||
12 | datacenter = "hel1-dc2"; | ||
13 | location ="hel1"; | ||
14 | serverType = "cx11"; | ||
15 | }; | ||
16 | }; | ||
17 | |||
18 | # This value determines the NixOS release with which your system is | ||
19 | # to be compatible, in order to avoid breaking some software such as | ||
20 | # database servers. You should change this only after NixOS release | ||
21 | # notes say you should. | ||
22 | # https://nixos.org/nixos/manual/release-notes.html | ||
23 | system.stateVersion = "19.03"; # Did you read the comment? | ||
24 | } | ||
diff --git a/modules/private/system/eldiron.nix b/modules/private/system/eldiron.nix index 22de37e..079216b 100644 --- a/modules/private/system/eldiron.nix +++ b/modules/private/system/eldiron.nix | |||
@@ -28,7 +28,13 @@ | |||
28 | myServices.irc.enable = true; | 28 | myServices.irc.enable = true; |
29 | myServices.pub.enable = true; | 29 | myServices.pub.enable = true; |
30 | myServices.tasks.enable = true; | 30 | myServices.tasks.enable = true; |
31 | myServices.mpd.enable = true; | ||
32 | myServices.dns.enable = true; | ||
33 | myServices.certificates.enable = true; | ||
34 | myServices.websites.enable = true; | ||
35 | myServices.mail.enable = true; | ||
31 | services.pure-ftpd.enable = true; | 36 | services.pure-ftpd.enable = true; |
37 | services.backup.enable = true; | ||
32 | 38 | ||
33 | deployment = { | 39 | deployment = { |
34 | targetEnv = "hetzner"; | 40 | targetEnv = "hetzner"; |
diff --git a/modules/private/tasks/default.nix b/modules/private/tasks/default.nix index b2191c0..88d3b7a 100644 --- a/modules/private/tasks/default.nix +++ b/modules/private/tasks/default.nix | |||
@@ -192,7 +192,7 @@ in { | |||
192 | 192 | ||
193 | myServices.websites.webappDirs._task = ./www; | 193 | myServices.websites.webappDirs._task = ./www; |
194 | 194 | ||
195 | security.acme.certs."task" = config.services.myCertificates.certConfig // { | 195 | security.acme.certs."task" = config.myServices.certificates.certConfig // { |
196 | inherit user group; | 196 | inherit user group; |
197 | plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; | 197 | plugins = [ "fullchain.pem" "key.pem" "cert.pem" "account_key.json" ]; |
198 | domain = fqdn; | 198 | domain = fqdn; |
diff --git a/modules/private/websites/default.nix b/modules/private/websites/default.nix index e2bcef5..119d62e 100644 --- a/modules/private/websites/default.nix +++ b/modules/private/websites/default.nix | |||
@@ -64,15 +64,19 @@ let | |||
64 | makeExtraConfig = (builtins.filter (x: x != null) (lib.attrsets.mapAttrsToList (n: v: v.extraConfig or null) apacheConfig)); | 64 | makeExtraConfig = (builtins.filter (x: x != null) (lib.attrsets.mapAttrsToList (n: v: v.extraConfig or null) apacheConfig)); |
65 | in | 65 | in |
66 | { | 66 | { |
67 | options.myServices.websites.webappDirs = lib.mkOption { | 67 | options.myServices.websites = { |
68 | type = lib.types.attrsOf lib.types.path; | 68 | enable = lib.mkEnableOption "enable websites"; |
69 | description = '' | 69 | |
70 | Webapp paths to create in /run/current-system/webapps | 70 | webappDirs = lib.mkOption { |
71 | ''; | 71 | type = lib.types.attrsOf lib.types.path; |
72 | default = {}; | 72 | description = '' |
73 | Webapp paths to create in /run/current-system/webapps | ||
74 | ''; | ||
75 | default = {}; | ||
76 | }; | ||
73 | }; | 77 | }; |
74 | 78 | ||
75 | config = { | 79 | config = lib.mkIf config.myServices.websites.enable { |
76 | services.backup.profiles.php = { | 80 | services.backup.profiles.php = { |
77 | rootDir = "/var/lib/php"; | 81 | rootDir = "/var/lib/php"; |
78 | }; | 82 | }; |
diff --git a/modules/private/websites/tools/mail/mta-sts.nix b/modules/private/websites/tools/mail/mta-sts.nix index bedefda..d443f55 100644 --- a/modules/private/websites/tools/mail/mta-sts.nix +++ b/modules/private/websites/tools/mail/mta-sts.nix | |||
@@ -28,28 +28,30 @@ let | |||
28 | "cp ${file d} $out/${d.domain}.txt" | 28 | "cp ${file d} $out/${d.domain}.txt" |
29 | ) domains)} | 29 | ) domains)} |
30 | ''; | 30 | ''; |
31 | cfg = config.myServices.websites.tools.email; | ||
31 | in | 32 | in |
32 | { | 33 | { |
33 | config.myServices.websites.webappDirs = { | 34 | config = lib.mkIf cfg.enable { |
34 | _mta-sts = root; | 35 | myServices.websites.webappDirs = { |
35 | }; | 36 | _mta-sts = root; |
37 | }; | ||
36 | 38 | ||
37 | config.services.websites.env.tools.vhostConfs.mta_sts = { | 39 | services.websites.env.tools.vhostConfs.mta_sts = { |
38 | certName = "mail"; | 40 | certName = "mail"; |
39 | addToCerts = true; | 41 | addToCerts = true; |
40 | hosts = ["mta-sts.mail.immae.eu"] ++ map (v: "mta-sts.${v.domain}") domains; | 42 | hosts = ["mta-sts.mail.immae.eu"] ++ map (v: "mta-sts.${v.domain}") domains; |
41 | root = "/run/current-system/webapps/_mta-sts"; | 43 | root = "/run/current-system/webapps/_mta-sts"; |
42 | extraConfig = [ | 44 | extraConfig = [ |
43 | '' | 45 | '' |
44 | RewriteEngine on | 46 | RewriteEngine on |
45 | RewriteCond %{HTTP_HOST} ^mta-sts.(.*)$ | 47 | RewriteCond %{HTTP_HOST} ^mta-sts.(.*)$ |
46 | RewriteRule ^/.well-known/mta-sts.txt$ %{DOCUMENT_ROOT}/%1.txt [L] | 48 | RewriteRule ^/.well-known/mta-sts.txt$ %{DOCUMENT_ROOT}/%1.txt [L] |
47 | <Directory /run/current-system/webapps/_mta-sts> | 49 | <Directory /run/current-system/webapps/_mta-sts> |
48 | Require all granted | 50 | Require all granted |
49 | Options -Indexes | 51 | Options -Indexes |
50 | </Directory> | 52 | </Directory> |
51 | '' | 53 | '' |
52 | ]; | 54 | ]; |
55 | }; | ||
53 | }; | 56 | }; |
54 | |||
55 | } | 57 | } |
diff --git a/nixops/default.nix b/nixops/default.nix index 649e431..f65f3da 100644 --- a/nixops/default.nix +++ b/nixops/default.nix | |||
@@ -5,5 +5,7 @@ | |||
5 | enableRollback = true; | 5 | enableRollback = true; |
6 | }; | 6 | }; |
7 | 7 | ||
8 | resources.sshKeyPairs.ssh-key = {}; | ||
8 | eldiron = import ../modules/private/system/eldiron.nix { inherit privateFiles; }; | 9 | eldiron = import ../modules/private/system/eldiron.nix { inherit privateFiles; }; |
10 | backup-2 = import ../modules/private/system/backup-2.nix { inherit privateFiles; }; | ||
9 | } | 11 | } |
diff --git a/overlays/environments/immae-eu.nix b/overlays/environments/immae-eu.nix index db1caa4..cc2e5c3 100644 --- a/overlays/environments/immae-eu.nix +++ b/overlays/environments/immae-eu.nix | |||
@@ -63,7 +63,7 @@ let | |||
63 | newsboat irssi | 63 | newsboat irssi |
64 | 64 | ||
65 | # nix | 65 | # nix |
66 | mylibs.yarn2nixPackage.yarn2nix | 66 | mylibs.yarn2nixPackage.yarn2nix nix |
67 | nixops nix-prefetch-scripts nix-generate-from-cpan | 67 | nixops nix-prefetch-scripts nix-generate-from-cpan |
68 | nix-zsh-completions bundix nodePackages.bower2nix | 68 | nix-zsh-completions bundix nodePackages.bower2nix |
69 | nodePackages.node2nix | 69 | nodePackages.node2nix |
diff --git a/overlays/nixops/default.nix b/overlays/nixops/default.nix index eb29ecd..247d036 100644 --- a/overlays/nixops/default.nix +++ b/overlays/nixops/default.nix | |||
@@ -1,5 +1,6 @@ | |||
1 | self: super: { | 1 | self: super: { |
2 | nixops = super.nixops.overrideAttrs (old: { | 2 | nixops = super.nixops.overrideAttrs (old: { |
3 | patches = [ ./hetzner_cloud.patch ]; | ||
3 | preConfigure = (old.preConfigure or "") + '' | 4 | preConfigure = (old.preConfigure or "") + '' |
4 | sed -i -e "/'keyFile'/s/'path'/'string'/" nixops/backends/__init__.py | 5 | sed -i -e "/'keyFile'/s/'path'/'string'/" nixops/backends/__init__.py |
5 | ''; | 6 | ''; |
diff --git a/overlays/nixops/hetzner_cloud.patch b/overlays/nixops/hetzner_cloud.patch new file mode 100644 index 0000000..b75c116 --- /dev/null +++ b/overlays/nixops/hetzner_cloud.patch | |||
@@ -0,0 +1,480 @@ | |||
1 | From 272e50d0b0262e49cdcaad42cdab57aad183d1c2 Mon Sep 17 00:00:00 2001 | ||
2 | From: goodraven | ||
3 | <employee-pseudonym-7f597def-7eeb-47f8-b10a-0724f2ba59a9@google.com> | ||
4 | Date: Thu, 3 May 2018 22:24:58 -0700 | ||
5 | Subject: [PATCH] Initial commit adding support for hetzner cloud | ||
6 | |||
7 | This is based on the digital ocean backend. It also uses nixos-infect. I extended nixos-infect to be generic | ||
8 | for both backends. | ||
9 | |||
10 | Fixes #855 | ||
11 | --- | ||
12 | examples/trivial-hetzner-cloud.nix | 12 ++ | ||
13 | nix/eval-machine-info.nix | 1 + | ||
14 | nix/hetzner-cloud.nix | 56 +++++++ | ||
15 | nix/options.nix | 1 + | ||
16 | nixops/backends/hetzner_cloud.py | 230 +++++++++++++++++++++++++++++ | ||
17 | nixops/data/nixos-infect | 77 +++++++--- | ||
18 | 6 files changed, 354 insertions(+), 23 deletions(-) | ||
19 | create mode 100644 examples/trivial-hetzner-cloud.nix | ||
20 | create mode 100644 nix/hetzner-cloud.nix | ||
21 | create mode 100644 nixops/backends/hetzner_cloud.py | ||
22 | |||
23 | diff --git a/examples/trivial-hetzner-cloud.nix b/examples/trivial-hetzner-cloud.nix | ||
24 | new file mode 100644 | ||
25 | index 000000000..c61add6bb | ||
26 | --- /dev/null | ||
27 | +++ b/examples/trivial-hetzner-cloud.nix | ||
28 | @@ -0,0 +1,12 @@ | ||
29 | +{ | ||
30 | + resources.sshKeyPairs.ssh-key = {}; | ||
31 | + | ||
32 | + machine = { config, pkgs, ... }: { | ||
33 | + services.openssh.enable = true; | ||
34 | + | ||
35 | + deployment.targetEnv = "hetznerCloud"; | ||
36 | + deployment.hetznerCloud.serverType = "cx11"; | ||
37 | + | ||
38 | + networking.firewall.allowedTCPPorts = [ 22 ]; | ||
39 | + }; | ||
40 | +} | ||
41 | diff --git a/nix/eval-machine-info.nix b/nix/eval-machine-info.nix | ||
42 | index 2884b4b47..6a7205786 100644 | ||
43 | --- a/nix/eval-machine-info.nix | ||
44 | +++ b/nix/eval-machine-info.nix | ||
45 | @@ -309,6 +309,7 @@ rec { | ||
46 | digitalOcean = optionalAttrs (v.config.deployment.targetEnv == "digitalOcean") v.config.deployment.digitalOcean; | ||
47 | gce = optionalAttrs (v.config.deployment.targetEnv == "gce") v.config.deployment.gce; | ||
48 | hetzner = optionalAttrs (v.config.deployment.targetEnv == "hetzner") v.config.deployment.hetzner; | ||
49 | + hetznerCloud = optionalAttrs (v.config.deployment.targetEnv == "hetznerCloud") v.config.deployment.hetznerCloud; | ||
50 | container = optionalAttrs (v.config.deployment.targetEnv == "container") v.config.deployment.container; | ||
51 | route53 = v.config.deployment.route53; | ||
52 | virtualbox = | ||
53 | diff --git a/nix/hetzner-cloud.nix b/nix/hetzner-cloud.nix | ||
54 | new file mode 100644 | ||
55 | index 000000000..21d148c1a | ||
56 | --- /dev/null | ||
57 | +++ b/nix/hetzner-cloud.nix | ||
58 | @@ -0,0 +1,56 @@ | ||
59 | +{ config, pkgs, lib, utils, ... }: | ||
60 | + | ||
61 | +with utils; | ||
62 | +with lib; | ||
63 | +with import ./lib.nix lib; | ||
64 | + | ||
65 | +let | ||
66 | + cfg = config.deployment.hetznerCloud; | ||
67 | +in | ||
68 | +{ | ||
69 | + ###### interface | ||
70 | + options = { | ||
71 | + | ||
72 | + deployment.hetznerCloud.authToken = mkOption { | ||
73 | + default = ""; | ||
74 | + example = "8b2f4e96af3997853bfd4cd8998958eab871d9614e35d63fab45a5ddf981c4da"; | ||
75 | + type = types.str; | ||
76 | + description = '' | ||
77 | + The API auth token. We're checking the environment for | ||
78 | + <envar>HETZNER_CLOUD_AUTH_TOKEN</envar> first and if that is | ||
79 | + not set we try this auth token. | ||
80 | + ''; | ||
81 | + }; | ||
82 | + | ||
83 | + deployment.hetznerCloud.datacenter = mkOption { | ||
84 | + example = "fsn1-dc8"; | ||
85 | + default = null; | ||
86 | + type = types.nullOr types.str; | ||
87 | + description = '' | ||
88 | + The datacenter. | ||
89 | + ''; | ||
90 | + }; | ||
91 | + | ||
92 | + deployment.hetznerCloud.location = mkOption { | ||
93 | + example = "fsn1"; | ||
94 | + default = null; | ||
95 | + type = types.nullOr types.str; | ||
96 | + description = '' | ||
97 | + The location. | ||
98 | + ''; | ||
99 | + }; | ||
100 | + | ||
101 | + deployment.hetznerCloud.serverType = mkOption { | ||
102 | + example = "cx11"; | ||
103 | + type = types.str; | ||
104 | + description = '' | ||
105 | + Name or id of server types. | ||
106 | + ''; | ||
107 | + }; | ||
108 | + }; | ||
109 | + | ||
110 | + config = mkIf (config.deployment.targetEnv == "hetznerCloud") { | ||
111 | + nixpkgs.system = mkOverride 900 "x86_64-linux"; | ||
112 | + services.openssh.enable = true; | ||
113 | + }; | ||
114 | +} | ||
115 | diff --git a/nix/options.nix b/nix/options.nix | ||
116 | index 0866c3ab8..db021f74d 100644 | ||
117 | --- a/nix/options.nix | ||
118 | +++ b/nix/options.nix | ||
119 | @@ -22,6 +22,7 @@ in | ||
120 | ./keys.nix | ||
121 | ./gce.nix | ||
122 | ./hetzner.nix | ||
123 | + ./hetzner-cloud.nix | ||
124 | ./container.nix | ||
125 | ./libvirtd.nix | ||
126 | ]; | ||
127 | diff --git a/nixops/backends/hetzner_cloud.py b/nixops/backends/hetzner_cloud.py | ||
128 | new file mode 100644 | ||
129 | index 000000000..a2cb176b9 | ||
130 | --- /dev/null | ||
131 | +++ b/nixops/backends/hetzner_cloud.py | ||
132 | @@ -0,0 +1,230 @@ | ||
133 | +# -*- coding: utf-8 -*- | ||
134 | +""" | ||
135 | +A backend for hetzner cloud. | ||
136 | + | ||
137 | +This backend uses nixos-infect (which uses nixos LUSTRATE) to infect a | ||
138 | +hetzner cloud instance. The setup requires two reboots, one for | ||
139 | +the infect itself, another after we pushed the nixos image. | ||
140 | +""" | ||
141 | +import os | ||
142 | +import os.path | ||
143 | +import time | ||
144 | +import socket | ||
145 | + | ||
146 | +import requests | ||
147 | + | ||
148 | +import nixops.resources | ||
149 | +from nixops.backends import MachineDefinition, MachineState | ||
150 | +from nixops.nix_expr import Function, RawValue | ||
151 | +import nixops.util | ||
152 | +import nixops.known_hosts | ||
153 | + | ||
154 | +infect_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'nixos-infect')) | ||
155 | + | ||
156 | +API_HOST = 'api.hetzner.cloud' | ||
157 | + | ||
158 | +class ApiError(Exception): | ||
159 | + pass | ||
160 | + | ||
161 | +class ApiNotFoundError(ApiError): | ||
162 | + pass | ||
163 | + | ||
164 | +class HetznerCloudDefinition(MachineDefinition): | ||
165 | + @classmethod | ||
166 | + def get_type(cls): | ||
167 | + return "hetznerCloud" | ||
168 | + | ||
169 | + def __init__(self, xml, config): | ||
170 | + MachineDefinition.__init__(self, xml, config) | ||
171 | + self.auth_token = config["hetznerCloud"]["authToken"] | ||
172 | + self.location = config["hetznerCloud"]["location"] | ||
173 | + self.datacenter = config["hetznerCloud"]["datacenter"] | ||
174 | + self.server_type = config["hetznerCloud"]["serverType"] | ||
175 | + | ||
176 | + def show_type(self): | ||
177 | + return "{0} [{1}]".format(self.get_type(), self.location or self.datacenter or 'any location') | ||
178 | + | ||
179 | + | ||
180 | +class HetznerCloudState(MachineState): | ||
181 | + @classmethod | ||
182 | + def get_type(cls): | ||
183 | + return "hetznerCloud" | ||
184 | + | ||
185 | + state = nixops.util.attr_property("state", MachineState.MISSING, int) # override | ||
186 | + public_ipv4 = nixops.util.attr_property("publicIpv4", None) | ||
187 | + public_ipv6 = nixops.util.attr_property("publicIpv6", None) | ||
188 | + location = nixops.util.attr_property("hetznerCloud.location", None) | ||
189 | + datacenter = nixops.util.attr_property("hetznerCloud.datacenter", None) | ||
190 | + server_type = nixops.util.attr_property("hetznerCloud.serverType", None) | ||
191 | + auth_token = nixops.util.attr_property("hetznerCloud.authToken", None) | ||
192 | + server_id = nixops.util.attr_property("hetznerCloud.serverId", None, int) | ||
193 | + | ||
194 | + def __init__(self, depl, name, id): | ||
195 | + MachineState.__init__(self, depl, name, id) | ||
196 | + self.name = name | ||
197 | + | ||
198 | + def get_ssh_name(self): | ||
199 | + return self.public_ipv4 | ||
200 | + | ||
201 | + def get_ssh_flags(self, *args, **kwargs): | ||
202 | + super_flags = super(HetznerCloudState, self).get_ssh_flags(*args, **kwargs) | ||
203 | + return super_flags + [ | ||
204 | + '-o', 'UserKnownHostsFile=/dev/null', | ||
205 | + '-o', 'StrictHostKeyChecking=no', | ||
206 | + '-i', self.get_ssh_private_key_file(), | ||
207 | + ] | ||
208 | + | ||
209 | + def get_physical_spec(self): | ||
210 | + return Function("{ ... }", { | ||
211 | + 'imports': [ RawValue('<nixpkgs/nixos/modules/profiles/qemu-guest.nix>') ], | ||
212 | + ('boot', 'loader', 'grub', 'device'): 'nodev', | ||
213 | + ('fileSystems', '/'): { 'device': '/dev/sda1', 'fsType': 'ext4'}, | ||
214 | + ('users', 'extraUsers', 'root', 'openssh', 'authorizedKeys', 'keys'): [self.depl.active_resources.get('ssh-key').public_key], | ||
215 | + }) | ||
216 | + | ||
217 | + def get_ssh_private_key_file(self): | ||
218 | + return self.write_ssh_private_key(self.depl.active_resources.get('ssh-key').private_key) | ||
219 | + | ||
220 | + def create_after(self, resources, defn): | ||
221 | + # make sure the ssh key exists before we do anything else | ||
222 | + return { | ||
223 | + r for r in resources if | ||
224 | + isinstance(r, nixops.resources.ssh_keypair.SSHKeyPairState) | ||
225 | + } | ||
226 | + | ||
227 | + def get_auth_token(self): | ||
228 | + return os.environ.get('HETZNER_CLOUD_AUTH_TOKEN', self.auth_token) | ||
229 | + | ||
230 | + def _api(self, path, method=None, data=None, json=True): | ||
231 | + """Basic wrapper around requests that handles auth and serialization.""" | ||
232 | + assert path[0] == '/' | ||
233 | + url = 'https://%s%s' % (API_HOST, path) | ||
234 | + token = self.get_auth_token() | ||
235 | + if not token: | ||
236 | + raise Exception('No hetzner cloud auth token set') | ||
237 | + headers = { | ||
238 | + 'Authorization': 'Bearer '+self.get_auth_token(), | ||
239 | + } | ||
240 | + res = requests.request( | ||
241 | + method=method, | ||
242 | + url=url, | ||
243 | + json=data, | ||
244 | + headers=headers) | ||
245 | + | ||
246 | + if res.status_code == 404: | ||
247 | + raise ApiNotFoundError('Not Found: %r' % path) | ||
248 | + elif not res.ok: | ||
249 | + raise ApiError('Response for %s %s has status code %d: %s' % (method, path, res.status_code, res.content)) | ||
250 | + if not json: | ||
251 | + return | ||
252 | + try: | ||
253 | + res_data = res.json() | ||
254 | + except ValueError as e: | ||
255 | + raise ApiError('Response for %s %s has invalid JSON (%s): %r' % (method, path, e, res.content)) | ||
256 | + return res_data | ||
257 | + | ||
258 | + | ||
259 | + def destroy(self, wipe=False): | ||
260 | + if not self.server_id: | ||
261 | + self.log('server {} was never made'.format(self.name)) | ||
262 | + return | ||
263 | + self.log('destroying server {} with id {}'.format(self.name, self.server_id)) | ||
264 | + try: | ||
265 | + res = self._api('/v1/servers/%s' % (self.server_id), method='DELETE') | ||
266 | + except ApiNotFoundError: | ||
267 | + self.log("server not found - assuming it's been destroyed already") | ||
268 | + | ||
269 | + self.public_ipv4 = None | ||
270 | + self.server_id = None | ||
271 | + | ||
272 | + return True | ||
273 | + | ||
274 | + def _create_ssh_key(self, public_key): | ||
275 | + """Create or get an ssh key and return an id.""" | ||
276 | + public_key = public_key.strip() | ||
277 | + res = self._api('/v1/ssh_keys', method='GET') | ||
278 | + name = 'nixops-%s-%s' % (self.depl.uuid, self.name) | ||
279 | + deletes = [] | ||
280 | + for key in res['ssh_keys']: | ||
281 | + if key['public_key'].strip() == public_key: | ||
282 | + return key['id'] | ||
283 | + if key['name'] == name: | ||
284 | + deletes.append(key['id']) | ||
285 | + for d in deletes: | ||
286 | + # This reply is empty, so don't decode json. | ||
287 | + self._api('/v1/ssh_keys/%d' % d, method='DELETE', json=False) | ||
288 | + res = self._api('/v1/ssh_keys', method='POST', data={ | ||
289 | + 'name': name, | ||
290 | + 'public_key': public_key, | ||
291 | + }) | ||
292 | + return res['ssh_key']['id'] | ||
293 | + | ||
294 | + def create(self, defn, check, allow_reboot, allow_recreate): | ||
295 | + ssh_key = self.depl.active_resources.get('ssh-key') | ||
296 | + if ssh_key is None: | ||
297 | + raise Exception('Please specify a ssh-key resource (resources.sshKeyPairs.ssh-key = {}).') | ||
298 | + | ||
299 | + self.set_common_state(defn) | ||
300 | + | ||
301 | + if self.server_id is not None: | ||
302 | + return | ||
303 | + | ||
304 | + ssh_key_id = self._create_ssh_key(ssh_key.public_key) | ||
305 | + | ||
306 | + req = { | ||
307 | + 'name': self.name, | ||
308 | + 'server_type': defn.server_type, | ||
309 | + 'start_after_create': True, | ||
310 | + 'image': 'debian-9', | ||
311 | + 'ssh_keys': [ | ||
312 | + ssh_key_id, | ||
313 | + ], | ||
314 | + } | ||
315 | + | ||
316 | + if defn.datacenter: | ||
317 | + req['datacenter'] = defn.datacenter | ||
318 | + elif defn.location: | ||
319 | + req['location'] = defn.location | ||
320 | + | ||
321 | + self.log_start("creating server ...") | ||
322 | + create_res = self._api('/v1/servers', method='POST', data=req) | ||
323 | + self.server_id = create_res['server']['id'] | ||
324 | + self.public_ipv4 = create_res['server']['public_net']['ipv4']['ip'] | ||
325 | + self.public_ipv6 = create_res['server']['public_net']['ipv6']['ip'] | ||
326 | + self.datacenter = create_res['server']['datacenter']['name'] | ||
327 | + self.location = create_res['server']['datacenter']['location']['name'] | ||
328 | + | ||
329 | + action = create_res['action'] | ||
330 | + action_path = '/v1/servers/%d/actions/%d' % (self.server_id, action['id']) | ||
331 | + | ||
332 | + while action['status'] == 'running': | ||
333 | + time.sleep(1) | ||
334 | + res = self._api(action_path, method='GET') | ||
335 | + action = res['action'] | ||
336 | + | ||
337 | + if action['status'] != 'success': | ||
338 | + raise Exception('unexpected status: %s' % action['status']) | ||
339 | + | ||
340 | + self.log_end("{}".format(self.public_ipv4)) | ||
341 | + | ||
342 | + self.wait_for_ssh() | ||
343 | + self.log_start("running nixos-infect") | ||
344 | + self.run_command('bash </dev/stdin 2>&1', stdin=open(infect_path)) | ||
345 | + self.reboot_sync() | ||
346 | + | ||
347 | + def reboot(self, hard=False): | ||
348 | + if hard: | ||
349 | + self.log("sending hard reset to server...") | ||
350 | + res = self._api('/v1/servers/%d/actions/reset' % self.server_id, method='POST') | ||
351 | + action = res['action'] | ||
352 | + action_path = '/v1/servers/%d/actions/%d' % (self.server_id, action['id']) | ||
353 | + while action['status'] == 'running': | ||
354 | + time.sleep(1) | ||
355 | + res = self._api(action_path, method='GET') | ||
356 | + action = res['action'] | ||
357 | + if action['status'] != 'success': | ||
358 | + raise Exception('unexpected status: %s' % action['status']) | ||
359 | + self.wait_for_ssh() | ||
360 | + self.state = self.STARTING | ||
361 | + else: | ||
362 | + MachineState.reboot(self, hard=hard) | ||
363 | diff --git a/nixops/data/nixos-infect b/nixops/data/nixos-infect | ||
364 | index 66634357b..437a2ec61 100644 | ||
365 | --- a/nixops/data/nixos-infect | ||
366 | +++ b/nixops/data/nixos-infect | ||
367 | @@ -68,26 +68,49 @@ makeConf() { | ||
368 | } | ||
369 | EOF | ||
370 | # (nixos-generate-config will add qemu-user and bind-mounts, so avoid) | ||
371 | + local disk | ||
372 | + if [ -e /dev/sda ]; then | ||
373 | + disk=/dev/sda | ||
374 | + else | ||
375 | + disk=/dev/vda | ||
376 | + fi | ||
377 | cat > /etc/nixos/hardware-configuration.nix << EOF | ||
378 | { ... }: | ||
379 | { | ||
380 | imports = [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix> ]; | ||
381 | - boot.loader.grub.device = "/dev/vda"; | ||
382 | - fileSystems."/" = { device = "/dev/vda1"; fsType = "ext4"; }; | ||
383 | + boot.loader.grub.device = "${disk}"; | ||
384 | + fileSystems."/" = { device = "${disk}1"; fsType = "ext4"; }; | ||
385 | } | ||
386 | EOF | ||
387 | |||
388 | local IFS=$'\n' | ||
389 | - ens3_ip4s=($(ip address show dev eth0 | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
390 | - ens3_ip6s=($(ip address show dev eth0 | grep 'inet6 .*global' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
391 | - ens4_ip4s=($(ip address show dev eth1 | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
392 | - ens4_ip6s=($(ip address show dev eth1 | grep 'inet6 .*global' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
393 | - gateway=($(ip route show dev eth0 | grep default | sed -r 's|default via ([0-9.]+).*|\1|')) | ||
394 | - gateway6=($(ip -6 route show dev eth0 | grep default | sed -r 's|default via ([0-9a-f:]+).*|\1|')) | ||
395 | - ether0=($(ip address show dev eth0 | grep link/ether | sed -r 's|.*link/ether ([0-9a-f:]+) .*|\1|')) | ||
396 | - ether1=($(ip address show dev eth1 | grep link/ether | sed -r 's|.*link/ether ([0-9a-f:]+) .*|\1|')) | ||
397 | + gateway=($(ip route show | grep default | sed -r 's|default via ([0-9.]+).*|\1|')) | ||
398 | + gateway6=($(ip -6 route show | grep default | sed -r 's|default via ([0-9a-f:]+).*|\1|')) | ||
399 | + interfaces=($(ip link | awk -F ': ' '/^[0-9]*: / {if ($2 != "lo") {print $2}}')) | ||
400 | nameservers=($(grep ^nameserver /etc/resolv.conf | cut -f2 -d' ')) | ||
401 | |||
402 | + # Predict the predictable name for each interface since that is enabled in | ||
403 | + # the nixos system. | ||
404 | + declare -A predictable_names | ||
405 | + for interface in ${interfaces[@]}; do | ||
406 | + # udevadm prints out the candidate names which will be selected if | ||
407 | + # available in this order. | ||
408 | + local name=$(udevadm info /sys/class/net/$interface | awk -F = ' | ||
409 | + /^E: ID_NET_NAME_FROM_DATABASE=/ {arr[1]=$2} | ||
410 | + /^E: ID_NET_NAME_ONBOARD=/ {arr[2]=$2} | ||
411 | + /^E: ID_NET_NAME_SLOT=/ {arr[3]=$2} | ||
412 | + /^E: ID_NET_NAME_PATH=/ {arr[4]=$2} | ||
413 | + /^E: ID_NET_NAME_MAC=/ {arr[5]=$2} | ||
414 | + END {for (i=1;i<6;i++) {if (length(arr[i]) > 0) { print arr[i]; break}}}') | ||
415 | + if [ -z "$name" ]; then | ||
416 | + echo Could not determine predictable name for interface $interface | ||
417 | + fi | ||
418 | + predictable_names[$interface]=$name | ||
419 | + done | ||
420 | + | ||
421 | + # Take a gamble on the first interface being able to reach the gateway. | ||
422 | + local default_interface=${predictable_names[${interfaces[0]}]} | ||
423 | + | ||
424 | cat > /etc/nixos/networking.nix << EOF | ||
425 | { ... }: { | ||
426 | # This file was populated at runtime with the networking | ||
427 | @@ -96,25 +119,27 @@ EOF | ||
428 | nameservers = [$(for a in ${nameservers[@]}; do echo -n " | ||
429 | \"$a\""; done) | ||
430 | ]; | ||
431 | - defaultGateway = "${gateway}"; | ||
432 | - defaultGateway6 = "${gateway6}"; | ||
433 | + defaultGateway = {address = "${gateway}"; interface = "${default_interface}";}; | ||
434 | + defaultGateway6 = {address = "${gateway6}"; interface = "${default_interface}";}; | ||
435 | interfaces = { | ||
436 | - ens3 = { | ||
437 | - ip4 = [$(for a in ${ens3_ip4s[@]}; do echo -n " | ||
438 | - $a"; done) | ||
439 | - ]; | ||
440 | - ip6 = [$(for a in ${ens3_ip6s[@]}; do echo -n " | ||
441 | - $a"; done) | ||
442 | - ]; | ||
443 | - }; | ||
444 | - ens4 = { | ||
445 | - ip4 = [$(for a in ${ens4_ip4s[@]}; do echo -n " | ||
446 | +EOF | ||
447 | + | ||
448 | + for interface in ${interfaces[@]}; do | ||
449 | + ip4s=($(ip address show dev $interface | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
450 | + ip6s=($(ip address show dev $interface | grep 'inet6 .*global' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')) | ||
451 | + cat >> /etc/nixos/networking.nix << EOF | ||
452 | + ${predictable_names[$interface]} = { | ||
453 | + ip4 = [$(for a in ${ip4s[@]}; do echo -n " | ||
454 | $a"; done) | ||
455 | ]; | ||
456 | - ip6 = [$(for a in ${ens4_ip6s[@]}; do echo -n " | ||
457 | + ip6 = [$(for a in ${ip6s[@]}; do echo -n " | ||
458 | $a"; done) | ||
459 | ]; | ||
460 | }; | ||
461 | +EOF | ||
462 | + done | ||
463 | + | ||
464 | + cat >> /etc/nixos/networking.nix << EOF | ||
465 | }; | ||
466 | }; | ||
467 | } | ||
468 | @@ -154,6 +179,12 @@ export HOME="/root" | ||
469 | groupadd -r nixbld -g 30000 | ||
470 | seq 1 10 | xargs -I{} useradd -c "Nix build user {}" -d /var/empty -g nixbld -G nixbld -M -N -r -s `which nologin` nixbld{} | ||
471 | |||
472 | +if ! which curl >/dev/null 2>/dev/null; then | ||
473 | + if which apt-get >/dev/null 2>/dev/null; then | ||
474 | + apt-get update && apt-get install -y curl | ||
475 | + fi | ||
476 | +fi | ||
477 | + | ||
478 | curl https://nixos.org/nix/install | sh | ||
479 | |||
480 | source ~/.nix-profile/etc/profile.d/nix.sh | ||