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:
@@ -1,4 +1,6 @@
|
|||||||
let
|
let
|
||||||
|
hosts = import ./modules/hosts.nix;
|
||||||
|
|
||||||
# This clan-core pins the zerotier `allowedIps` interface (admit by network
|
# This clan-core pins the zerotier `allowedIps` interface (admit by network
|
||||||
# IPv6), but node IDs are the stable per-device handle (what `zerotier-cli
|
# 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
|
# info` prints). Derive a member's IP on THIS network from the controller's
|
||||||
@@ -21,6 +23,7 @@ in
|
|||||||
control = { };
|
control = { };
|
||||||
ns1 = { };
|
ns1 = { };
|
||||||
ns2 = { };
|
ns2 = { };
|
||||||
|
mx1 = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
inventory.instances = {
|
inventory.instances = {
|
||||||
@@ -51,14 +54,12 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Direct SSH to public IPs — clan's priority-1 connection path, with the
|
# 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
|
# ZeroTier mesh and Tor kept as automatic fallbacks. Raw IPs (from
|
||||||
# ns1/ns2 DNS names) so reaching these hosts never depends on their own
|
# modules/hosts.nix, not the ns1/ns2 DNS names) so reaching these hosts never
|
||||||
# DNS being up.
|
# depends on their own DNS being up.
|
||||||
internet = {
|
internet.roles.default.machines = builtins.mapAttrs (_: h: {
|
||||||
roles.default.machines.control.settings.host = "77.42.68.181";
|
settings.host = h.ipv4;
|
||||||
roles.default.machines.ns1.settings.host = "46.224.170.206";
|
}) hosts;
|
||||||
roles.default.machines.ns2.settings.host = "157.180.70.82";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Recovery root password for console access when a machine fails to boot.
|
# Recovery root password for console access when a machine fails to boot.
|
||||||
emergency-access = {
|
emergency-access = {
|
||||||
|
|||||||
@@ -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` |
|
| `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` |
|
| `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` |
|
| `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
|
## Access
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
let
|
||||||
|
hosts = import ../../modules/hosts.nix;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/hetzner-firewall.nix
|
../../modules/hetzner-firewall.nix
|
||||||
@@ -11,10 +15,10 @@
|
|||||||
|
|
||||||
clan.core.sops.defaultGroups = [ "admins" ];
|
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 = {
|
cnx.staticIPv6 = {
|
||||||
enable = true;
|
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)
|
time.timeZone = "Etc/GMT-3"; # UTC+3 (fixed offset, no DST)
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
|
{ config, ... }:
|
||||||
|
let
|
||||||
|
hosts = import ../../modules/hosts.nix;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
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.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
let
|
let
|
||||||
domains = import ../../modules/dns/domains.nix;
|
domains = import ../../modules/dns/domains.nix;
|
||||||
mesh = import ../../modules/mesh-hosts.nix { inherit config lib; };
|
mesh = import ../../modules/mesh-hosts.nix { inherit config lib; };
|
||||||
|
hosts = import ../../modules/hosts.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -22,10 +23,11 @@ in
|
|||||||
# resolution, so map the control machine name to its ZeroTier mesh address.
|
# resolution, so map the control machine name to its ZeroTier mesh address.
|
||||||
networking.hosts.${mesh.hosts.control} = [ "control" ];
|
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 = {
|
cnx.staticIPv6 = {
|
||||||
enable = true;
|
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)
|
time.timeZone = "Etc/GMT-1"; # UTC+1 (fixed offset, no DST)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{ ... }:
|
{ config, ... }:
|
||||||
let
|
let
|
||||||
domains = import ../../modules/dns/domains.nix;
|
domains = import ../../modules/dns/domains.nix;
|
||||||
|
hosts = import ../../modules/hosts.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -11,10 +12,11 @@ in
|
|||||||
|
|
||||||
clan.core.sops.defaultGroups = [ "admins" ];
|
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 = {
|
cnx.staticIPv6 = {
|
||||||
enable = true;
|
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)
|
time.timeZone = "Etc/GMT-3"; # UTC+3 (fixed offset, no DST)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ $TTL 3600
|
|||||||
@ IN NS ns1.cnx.network.
|
@ IN NS ns1.cnx.network.
|
||||||
@ IN NS ns2.cnx.network.
|
@ IN NS ns2.cnx.network.
|
||||||
|
|
||||||
; ---- Mail (fill in once the mail host exists) ----
|
; ---- Mail ----
|
||||||
;@ IN MX 10 mail.cnx.email.
|
mx1 IN A 5.223.65.38
|
||||||
;mail IN A <mail-ipv4>
|
@ IN MX 10 mx1.cnx.email.
|
||||||
;@ IN TXT "v=spf1 mx -all"
|
@ IN TXT "v=spf1 mx -all"
|
||||||
;_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@cnx.email"
|
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@cnx.email"
|
||||||
|
|||||||
@@ -24,6 +24,17 @@ let
|
|||||||
description = "ICMP (ping / PMTUD)";
|
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 = [
|
dnsRules = [
|
||||||
{
|
{
|
||||||
direction = "in";
|
direction = "in";
|
||||||
@@ -50,4 +61,9 @@ in
|
|||||||
];
|
];
|
||||||
"clan-ns1" = dnsRules;
|
"clan-ns1" = dnsRules;
|
||||||
"clan-ns2" = dnsRules;
|
"clan-ns2" = dnsRules;
|
||||||
|
"clan-mx1" = [
|
||||||
|
smtp
|
||||||
|
zerotier
|
||||||
|
ping
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ let
|
|||||||
machine: file:
|
machine: file:
|
||||||
builtins.readFile "${dir}/vars/per-machine/${machine}/zerotier/${file}/value";
|
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
|
# 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).
|
# 0x9993 marker. The network id is a public var on the controller (control).
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ in
|
|||||||
(target "control" "127.0.0.1" 9100)
|
(target "control" "127.0.0.1" 9100)
|
||||||
(target "ns1" (v6 mesh.hosts.ns1) 9100)
|
(target "ns1" (v6 mesh.hosts.ns1) 9100)
|
||||||
(target "ns2" (v6 mesh.hosts.ns2) 9100)
|
(target "ns2" (v6 mesh.hosts.ns2) 9100)
|
||||||
|
(target "mx1" (v6 mesh.hosts.mx1) 9100)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user