Onboard mx1 mail host and factor out per-host public IPs

- Register mx1 in the inventory and as a direct-SSH `internet` host; give it
  a static public IPv6 (2a01:4ff:2f0:1963::1).
- Point the cnx.email MX (plus SPF/DMARC) at mx1 and add its A record.
- Bring mx1 into monitoring: import exporters, add it to the mesh map and the
  node scrape job so its host metrics and journald reach control.
- Add a clan-mx1 Hetzner firewall: inbound SMTP + ZeroTier + ICMP, no public
  SSH (admin rides the mesh like the other hosts). 587/465/993 held for now.
- Extract per-host public IPv4/IPv6 into modules/hosts.nix, consumed by
  clan.nix's internet hosts and each machine's cnx.staticIPv6, so each address
  is declared once instead of being duplicated across configs.
- docs: add mx1 to the machines table.
This commit is contained in:
Berwn
2026-06-18 11:53:14 +07:00
parent 2c89ab913c
commit 6e4178df04
11 changed files with 94 additions and 23 deletions
+5 -5
View File
@@ -11,8 +11,8 @@ $TTL 3600
@ IN NS ns1.cnx.network.
@ IN NS ns2.cnx.network.
; ---- Mail (fill in once the mail host exists) ----
;@ IN MX 10 mail.cnx.email.
;mail IN A <mail-ipv4>
;@ IN TXT "v=spf1 mx -all"
;_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@cnx.email"
; ---- Mail ----
mx1 IN A 5.223.65.38
@ IN MX 10 mx1.cnx.email.
@ IN TXT "v=spf1 mx -all"
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@cnx.email"
+16
View File
@@ -24,6 +24,17 @@ let
description = "ICMP (ping / PMTUD)";
};
# Inbound mail only. mx1 is the MX for cnx.email, so other servers deliver on
# 25. Submission (587/465) and IMAP (993) stay closed until the mail stack and
# mailboxes exist — admin access rides the mesh, same as the other hosts.
smtp = {
direction = "in";
protocol = "tcp";
port = "25";
source_ips = world;
description = "SMTP (inbound mail)";
};
dnsRules = [
{
direction = "in";
@@ -50,4 +61,9 @@ in
];
"clan-ns1" = dnsRules;
"clan-ns2" = dnsRules;
"clan-mx1" = [
smtp
zerotier
ping
];
}
+28
View File
@@ -0,0 +1,28 @@
# Per-host public network facts: single source of truth for each machine's
# public IPv4 and its static public IPv6. Consumed by clan.nix's `internet`
# connection hosts (ipv4) and each machine's `cnx.staticIPv6` (ipv6), so an
# address is written once instead of being duplicated across configs.
#
# NOT a driver for the DNS zone files — those stay hand-edited text, so a record
# here that also appears as A/AAAA glue still needs a matching manual zone edit.
#
# ipv6 is the single address to assign from the host's allocated /64 (we take
# ::1), without prefix length; cnx.staticIPv6 supplies the /64 default.
{
control = {
ipv4 = "77.42.68.181";
ipv6 = "2a01:4f9:c013:e6d0::1";
};
ns1 = {
ipv4 = "46.224.170.206";
ipv6 = "2a01:4f8:c014:b5c5::1";
};
ns2 = {
ipv4 = "157.180.70.82";
ipv6 = "2a01:4f9:c014:6d87::1";
};
mx1 = {
ipv4 = "5.223.65.38";
ipv6 = "2a01:4ff:2f0:1963::1";
};
}
+1 -1
View File
@@ -14,7 +14,7 @@ let
machine: file:
builtins.readFile "${dir}/vars/per-machine/${machine}/zerotier/${file}/value";
hosts = lib.genAttrs [ "control" "ns1" "ns2" ] (m: readVar m "zerotier-ip");
hosts = lib.genAttrs [ "control" "ns1" "ns2" "mx1" ] (m: readVar m "zerotier-ip");
# RFC 4193 prefix of this ZeroTier network: fd + the 8-byte network id + the
# 0x9993 marker. The network id is a public var on the controller (control).
+1
View File
@@ -45,6 +45,7 @@ in
(target "control" "127.0.0.1" 9100)
(target "ns1" (v6 mesh.hosts.ns1) 9100)
(target "ns2" (v6 mesh.hosts.ns2) 9100)
(target "mx1" (v6 mesh.hosts.mx1) 9100)
];
}
{