diff --git a/clan.nix b/clan.nix index cdad86d..df1d690 100644 --- a/clan.nix +++ b/clan.nix @@ -1,4 +1,6 @@ let + hosts = import ./modules/hosts.nix; + # This clan-core pins the zerotier `allowedIps` interface (admit by network # IPv6), but node IDs are the stable per-device handle (what `zerotier-cli # info` prints). Derive a member's IP on THIS network from the controller's @@ -21,6 +23,7 @@ in control = { }; ns1 = { }; ns2 = { }; + mx1 = { }; }; inventory.instances = { @@ -51,14 +54,12 @@ in }; # Direct SSH to public IPs — clan's priority-1 connection path, with the - # ZeroTier mesh and Tor kept as automatic fallbacks. Raw IPs (not the - # ns1/ns2 DNS names) so reaching these hosts never depends on their own - # DNS being up. - internet = { - roles.default.machines.control.settings.host = "77.42.68.181"; - roles.default.machines.ns1.settings.host = "46.224.170.206"; - roles.default.machines.ns2.settings.host = "157.180.70.82"; - }; + # ZeroTier mesh and Tor kept as automatic fallbacks. Raw IPs (from + # modules/hosts.nix, not the ns1/ns2 DNS names) so reaching these hosts never + # depends on their own DNS being up. + internet.roles.default.machines = builtins.mapAttrs (_: h: { + settings.host = h.ipv4; + }) hosts; # Recovery root password for console access when a machine fails to boot. emergency-access = { diff --git a/docs/src/overview.md b/docs/src/overview.md index ebe1479..ae1a490 100644 --- a/docs/src/overview.md +++ b/docs/src/overview.md @@ -11,6 +11,7 @@ this book is built from `docs/` and served on `control` over the ZeroTier mesh. | `control` | ZeroTier controller, monitoring, docs | `77.42.68.181` | `2a01:4f9:c013:e6d0::1` | | `ns1` | Knot DNS **primary** (master) | `46.224.170.206` | `2a01:4f8:c014:b5c5::1` | | `ns2` | Knot DNS **secondary** (slave) | `157.180.70.82` | `2a01:4f9:c014:6d87::1` | +| `mx1` | Mail server (**MX** for cnx.email) | `5.223.65.38` | `2a01:4ff:2f0:1963::1` | ## Access diff --git a/machines/control/configuration.nix b/machines/control/configuration.nix index b1915a7..1ff33ee 100644 --- a/machines/control/configuration.nix +++ b/machines/control/configuration.nix @@ -1,3 +1,7 @@ +{ config, ... }: +let + hosts = import ../../modules/hosts.nix; +in { imports = [ ../../modules/hetzner-firewall.nix @@ -11,10 +15,10 @@ clan.core.sops.defaultGroups = [ "admins" ]; - # Public IPv6; SLAAC doesn't bring it up here. + # Public IPv6 (from modules/hosts.nix); SLAAC doesn't bring it up here. cnx.staticIPv6 = { enable = true; - address = "2a01:4f9:c013:e6d0::1"; + address = hosts.${config.networking.hostName}.ipv6; }; time.timeZone = "Etc/GMT-3"; # UTC+3 (fixed offset, no DST) diff --git a/machines/mx1/configuration.nix b/machines/mx1/configuration.nix index 090666a..a286d32 100644 --- a/machines/mx1/configuration.nix +++ b/machines/mx1/configuration.nix @@ -1,7 +1,23 @@ +{ config, ... }: +let + hosts = import ../../modules/hosts.nix; +in { imports = [ - + ../../modules/static-ipv6.nix + ../../modules/monitoring/exporters.nix ]; - # New machine! + clan.core.sops.defaultGroups = [ "admins" ]; + + # Public IPv6 (from modules/hosts.nix); SLAAC doesn't bring it up here. + cnx.staticIPv6 = { + enable = true; + address = hosts.${config.networking.hostName}.ipv6; + }; + + services.timesyncd.enable = true; + + # Mail host backing the cnx.email MX (mx1.cnx.email -> 5.223.65.38). + # SMTP/IMAP services to be configured. } diff --git a/machines/ns1/configuration.nix b/machines/ns1/configuration.nix index 0f0a238..e4f2532 100644 --- a/machines/ns1/configuration.nix +++ b/machines/ns1/configuration.nix @@ -2,6 +2,7 @@ let domains = import ../../modules/dns/domains.nix; mesh = import ../../modules/mesh-hosts.nix { inherit config lib; }; + hosts = import ../../modules/hosts.nix; in { imports = [ @@ -22,10 +23,11 @@ in # resolution, so map the control machine name to its ZeroTier mesh address. networking.hosts.${mesh.hosts.control} = [ "control" ]; - # Public IPv6 (matches the ns1 AAAA glue); SLAAC doesn't bring it up here. + # Public IPv6 (from modules/hosts.nix; matches the ns1 AAAA glue); SLAAC + # doesn't bring it up here. cnx.staticIPv6 = { enable = true; - address = "2a01:4f8:c014:b5c5::1"; + address = hosts.${config.networking.hostName}.ipv6; }; time.timeZone = "Etc/GMT-1"; # UTC+1 (fixed offset, no DST) diff --git a/machines/ns2/configuration.nix b/machines/ns2/configuration.nix index cd4dc59..4b613fa 100644 --- a/machines/ns2/configuration.nix +++ b/machines/ns2/configuration.nix @@ -1,6 +1,7 @@ -{ ... }: +{ config, ... }: let domains = import ../../modules/dns/domains.nix; + hosts = import ../../modules/hosts.nix; in { imports = [ @@ -11,10 +12,11 @@ in clan.core.sops.defaultGroups = [ "admins" ]; - # Public IPv6 (matches the ns2 AAAA glue); SLAAC doesn't bring it up here. + # Public IPv6 (from modules/hosts.nix; matches the ns2 AAAA glue); SLAAC + # doesn't bring it up here. cnx.staticIPv6 = { enable = true; - address = "2a01:4f9:c014:6d87::1"; + address = hosts.${config.networking.hostName}.ipv6; }; time.timeZone = "Etc/GMT-3"; # UTC+3 (fixed offset, no DST) diff --git a/modules/dns/zones/cnx.email.zone b/modules/dns/zones/cnx.email.zone index 01dfe9f..82ea682 100644 --- a/modules/dns/zones/cnx.email.zone +++ b/modules/dns/zones/cnx.email.zone @@ -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 -;@ 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" diff --git a/modules/hetzner-firewall-rules.nix b/modules/hetzner-firewall-rules.nix index 4fbbdad..9222486 100644 --- a/modules/hetzner-firewall-rules.nix +++ b/modules/hetzner-firewall-rules.nix @@ -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 + ]; } diff --git a/modules/hosts.nix b/modules/hosts.nix new file mode 100644 index 0000000..07e9280 --- /dev/null +++ b/modules/hosts.nix @@ -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"; + }; +} diff --git a/modules/mesh-hosts.nix b/modules/mesh-hosts.nix index 0c1d96d..0599f4c 100644 --- a/modules/mesh-hosts.nix +++ b/modules/mesh-hosts.nix @@ -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). diff --git a/modules/monitoring/server.nix b/modules/monitoring/server.nix index aeeda91..18eda5d 100644 --- a/modules/monitoring/server.nix +++ b/modules/monitoring/server.nix @@ -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) ]; } {