Reusable cnx.router.* module for the Topton 1U boxes replacing OPNsense: PPPoE WAN (optionally VLAN-tagged, AIS: 10, secret credentials incl. username), VLAN-filtering bridge, nftables NAT/firewall with MSS clamp, Kea DHCP with per-VLAN lease time, Blocky DNS, DHCPv6-PD, CrowdSec with the ZeroTier mesh whitelisted, optional Omada controller, ZFS disk. Fleet baseline rides along: admins sops group is now derived for every machine in clan.nix (secrets encrypt to it from the first vars generate) and time sync is chrony everywhere instead of systemd-timesyncd.
80 lines
2.1 KiB
Nix
80 lines
2.1 KiB
Nix
# LAN DHCP (Kea) and DNS (Blocky). Fully declarative: one Kea subnet per VLAN
|
|
# with dhcp.enable, Blocky as the blocklist resolver every DHCP lease points
|
|
# at. Blocky's HTTP listener (:4000) serves Prometheus metrics, scraped by
|
|
# control over the mesh (firewall.nix scopes it to the mesh subnet).
|
|
{
|
|
config,
|
|
lib,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.cnx.router;
|
|
dhcpVlans = lib.filterAttrs (_: vlan: vlan.dhcp.enable) cfg.vlans;
|
|
in
|
|
{
|
|
config = lib.mkIf cfg.enable {
|
|
services.kea.dhcp4 = {
|
|
enable = true;
|
|
settings = {
|
|
interfaces-config.interfaces = lib.mapAttrsToList (name: _: "vlan-${name}") dhcpVlans;
|
|
lease-database = {
|
|
type = "memfile";
|
|
persist = true;
|
|
name = "/var/lib/kea/dhcp4.leases";
|
|
};
|
|
valid-lifetime = 86400;
|
|
subnet4 = lib.mapAttrsToList (name: vlan: {
|
|
id = vlan.id;
|
|
subnet = vlan.subnet;
|
|
interface = "vlan-${name}";
|
|
valid-lifetime = vlan.dhcp.leaseTime;
|
|
pools = [ { pool = "${vlan.dhcp.pool.from} - ${vlan.dhcp.pool.to}"; } ];
|
|
option-data = [
|
|
{
|
|
name = "routers";
|
|
data = vlan.address;
|
|
}
|
|
{
|
|
name = "domain-name-servers";
|
|
data = vlan.address;
|
|
}
|
|
];
|
|
}) dhcpVlans;
|
|
};
|
|
};
|
|
|
|
services.blocky = {
|
|
enable = true;
|
|
settings = {
|
|
ports = {
|
|
dns = 53;
|
|
http = 4000;
|
|
};
|
|
upstreams.groups.default = [
|
|
"9.9.9.9"
|
|
"149.112.112.112"
|
|
"2620:fe::fe"
|
|
];
|
|
blocking = {
|
|
denylists.ads = [
|
|
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
|
|
];
|
|
clientGroupsBlock.default = [ "ads" ];
|
|
};
|
|
caching = {
|
|
minTime = "5m";
|
|
prefetching = true;
|
|
};
|
|
prometheus.enable = true;
|
|
};
|
|
};
|
|
|
|
# The router itself resolves via public resolvers, not via Blocky, so DNS
|
|
# for deploys/updates survives a broken local resolver.
|
|
networking.nameservers = [
|
|
"9.9.9.9"
|
|
"1.1.1.1"
|
|
];
|
|
};
|
|
}
|