# TODO: don't pull in bluez (or any bluetooth components) { repoFlake, pkgs, lib, config, nodeFlake, nodeName, localDomainName, system, ... }: let inherit (nodeFlake.inputs) nixos-nftables-firewall nixos-sbc ; vlanRangeStart = builtins.head vlanRange; vlanRangeEnd = builtins.elemAt vlanRange ((builtins.length vlanRange) - 1); vlanRange = builtins.map (vlanid: (lib.strings.toInt vlanid)) (builtins.attrNames vlans); vlanRangeWith0 = [0] ++ vlanRange; mkVlanIpv4HostAddr = { vlanid, host, thirdIpv4SegmentMin ? 20, cidr ? true, }: let # reserve the first subnet for vlanid == 0 # number the other subnets continously from there offset = if vlanid == 0 then thirdIpv4SegmentMin else thirdIpv4SegmentMin + 1 - vlanRangeStart; in builtins.concatStringsSep "." ["192" "168" (toString (vlanid + offset)) "${toString host}${lib.strings.optionalString cidr "/24"}"]; defaultVlan = { name = "${localDomainName}"; packet_priority = 0; }; vlans = { "10".name = "mgmt"; "10".packet_priority = 0; "11".name = "dmz"; "11".packet_priority = -5; "12".name = "iot"; "12".packet_priority = -5; "13".name = "office"; "13".packet_priority = -10; "14".name = "guests"; "14".packet_priority = 10; "15".name = "iot2"; "15".packet_priority = -10; }; vlansByName = lib.attrsets.mapAttrs' ( vlanid': attrs: lib.attrsets.nameValuePair attrs.name (attrs // { id = lib.strings.toInt vlanid'; id' = vlanid'; }) ) vlans; getVlanDomain = {vlanid}: if vlanid == 0 then defaultVlan.name else vlans."${toString vlanid}".name + "." + defaultVlan.name; bridgeInterfaceName = "br-lan"; mkInterfaceName = {vlanid}: if vlanid == 0 then bridgeInterfaceName else "${bridgeInterfaceName}.${toString vlanid}"; dmzExposedHost = "sj-srv1.dmz.internal"; dmzExposedHostIpv4 = mkVlanIpv4HostAddr { vlanid = vlansByName.dmz.id; host = 99; cidr = false; }; # "sj-srv1.dmz.internal"; in { imports = [ nixos-sbc.nixosModules.default nixos-sbc.nixosModules.boards.bananapi.bpir3 { sbc.version = "0.2"; sbc.bootstrap.rootFilesystem = "btrfs"; sbc.wireless.wifi.acceptRegulatoryResponsibility = true; } repoFlake.inputs.sops-nix.nixosModules.sops ../../profiles/common/user.nix ../../snippets/nix-settings.nix nixos-nftables-firewall.nixosModules.default { services.openssh.enable = true; services.openssh.settings.PermitRootLogin = "yes"; users.commonUsers = { enable = true; enableNonRoot = false; rootPasswordFile = config.sops.secrets.passwords-root.path; }; sops.defaultSopsFile = ../../../../secrets/${nodeName}/secrets.yaml; sops.defaultSopsFormat = "yaml"; sops.secrets.passwords-root.neededForUsers = true; # sops.secrets.wlan0_saePasswordsFile = {}; sops.secrets.wlan0_wpaPskFile = {}; } ]; # sops.secrets.ssh_host_ed25519_key = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_ed25519_key"; # mode = "0600"; # }; # sops.secrets.ssh_host_ed25519_key_pub = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_ed25519_key.pub"; # mode = "0600"; # }; # sops.secrets.ssh_host_rsa_key = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_rsa_key"; # mode = "0600"; # }; # sops.secrets.ssh_host_rsa_key_pub = { # sopsFile = ../../../../secrets/${nodeName}/secrets.yaml; # format = "yaml"; # path = "/etc/ssh/ssh_host_rsa_key.pub"; # mode = "0644"; # }; boot = { kernel = { sysctl = { "net.ipv4.conf.all.forwarding" = true; "net.ipv6.conf.all.forwarding" = true; }; }; }; networking = { hostName = nodeName; useNetworkd = true; useDHCP = false; # these will be configured via nftables nat.enable = lib.mkForce false; firewall.enable = lib.mkForce false; # Use the nftables firewall instead of the base nixos scripted rules. # This flake provides a similar utility to the base nixos scripting. # https://github.com/thelegy/nixos-nftables-firewall/tree/main # TODO: configure packet_priority for VLANs (see https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority, https://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation#packet_priority) nftables = { enable = true; stopRuleset = ""; chains = { prerouting = { "exposeHost" = { after = ["hook"]; rules = let wanInterfaces = builtins.concatStringsSep ", " config.networking.nftables.firewall.zones.wan.interfaces; in [ "iifname { ${wanInterfaces} } tcp dport 220 redirect to 22" "iifname { ${wanInterfaces} } dnat ip to ${dmzExposedHostIpv4}" ]; }; }; }; firewall = { enable = true; zones = { lan.interfaces = [(mkInterfaceName {vlanid = 0;})]; vlan.interfaces = builtins.map (vlanid: (mkInterfaceName {inherit vlanid;})) vlanRange; # lan.ipv4Addresses = ["192.168.0.0/16"]; wan.interfaces = ["wan" "lan0"]; vpn.interfaces = ["wg0" "wg1" "wg2"]; } // # generate a zone for each vlan lib.attrsets.mapAttrs (key: value: { interfaces = [(mkInterfaceName {vlanid = value.id;})]; }) vlansByName; rules = let ipv6IcmpTypes = [ "destination-unreachable" "echo-reply" "echo-request" "packet-too-big" "parameter-problem" "time-exceeded" # Without the nd-* ones ipv6 will not work. "nd-neighbor-solicit" "nd-router-advert" "nd-neighbor-advert" ]; ipv4IcmpTypes = [ "destination-unreachable" "echo-reply" "echo-request" "source-quench" "time-exceeded" "router-advertisement" ]; allowIcmpLines = [ "ip protocol icmp icmp type { ${builtins.concatStringsSep ", " ipv4IcmpTypes} } accept" "ip6 nexthdr icmpv6 icmpv6 type { ${builtins.concatStringsSep ", " ipv6IcmpTypes} } accept" ]; in { fw = { from = ["fw"]; verdict = "accept"; }; office-to-dmz = { from = ["office"]; to = ["dmz"]; verdict = "accept"; }; lan-to-fw = { from = ["lan"]; to = ["fw" "lan"]; verdict = "accept"; }; lan-to-wan = { from = ["lan"]; to = ["wan"]; verdict = "accept"; }; vlan-to-wan = { from = ["vlan"]; to = ["wan"]; verdict = "accept"; }; vlan-to-fw = { allowedUDPPortRanges = [ { from = 53; to = 53; } { from = 67; to = 68; } { from = 5201; to = 5201; } ]; allowedTCPPortRanges = [ { from = 22; to = 22; } { from = 53; to = 53; } { from = 5201; to = 5201; } ]; from = ["vlan"]; to = ["fw"]; extraLines = allowIcmpLines ++ [ "drop" ]; }; to-wan-nat = { from = ["lan" "vlan"]; to = ["wan"]; masquerade = true; verdict = "accept"; }; wan-to-dmz = { from = ["wan"]; to = ["dmz"]; verdict = "accept"; }; wan-to-fw = { from = ["wan"]; to = ["fw"]; allowedTCPPortRanges = [ { from = 22; to = 22; } ]; extraLines = allowIcmpLines ++ [ "drop" ]; }; to-vpn-nat = { from = ["lan" "vlan"]; to = ["vpn"]; masquerade = false; verdict = "accept"; }; }; }; }; }; sops.secrets.wg0-privatekey = { mode = "440"; group = "systemd-network"; }; sops.secrets.wg1-privatekey = { mode = "440"; group = "systemd-network"; }; sops.secrets.wg0-peer0-psk = { mode = "440"; group = "systemd-network"; }; sops.secrets.wg1-peer0-psk = { mode = "440"; group = "systemd-network"; }; systemd.network = { wait-online.anyInterface = true; netdevs = let router0-ifog_wg0Endpoint = "${repoFlake.colmena.router0-ifog.deployment.targetHost}:${ builtins.toString repoFlake .nixosConfigurations .router0-ifog .config .systemd .network .netdevs .wg0 .wireguardConfig .ListenPort }"; router0-ifog_wg1Endpoint = "${repoFlake.colmena.router0-ifog.deployment.targetHost}:${ builtins.toString repoFlake .nixosConfigurations .router0-ifog .config .systemd .network .netdevs .wg1 .wireguardConfig .ListenPort }"; router0-hosthatch_wg0Endpoint = "${repoFlake.colmena.router0-hosthatch.deployment.targetHost}:${ builtins.toString repoFlake .nixosConfigurations .router0-hosthatch .config .systemd .network .netdevs .wg0 .wireguardConfig .ListenPort }"; in { # Create the bridge interface "20-${bridgeInterfaceName}" = { netdevConfig = { Kind = "bridge"; Name = bridgeInterfaceName; }; extraConfig = '' [Bridge] STP=yes VLANFiltering=yes VLANProtocol=802.1q DefaultPVID=0 ''; }; wg0 = { enable = true; netdevConfig = { Name = "wg0"; Kind = "wireguard"; }; wireguardConfig = { PrivateKeyFile = builtins.toString config.sops.secrets.wg0-privatekey.path; FirewallMark = 100; }; wireguardPeers = [ { wireguardPeerConfig = { AllowedIPs = [ # this allows all traffic to be routed through this interface "0.0.0.0/0" # # alternatively, specific destinations could be allowed # # remote peer wg addr # "10.0.0.0/32" # "1.1.1.1/32" # # ifconfig.co. # "172.67.168.106" # "104.21.54.91" ]; PersistentKeepalive = 15; PresharedKeyFile = builtins.toString config.sops.secrets.wg0-peer0-psk.path; PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM="; Endpoint = router0-ifog_wg0Endpoint; }; } ]; }; wg1 = { enable = true; netdevConfig = { Name = "wg1"; Kind = "wireguard"; }; wireguardConfig = { PrivateKeyFile = builtins.toString config.sops.secrets.wg1-privatekey.path; FirewallMark = 101; }; wireguardPeers = [ { wireguardPeerConfig = { AllowedIPs = [ # this allows all traffic to be routed through this interface "0.0.0.0/0" ]; PersistentKeepalive = 15; PresharedKeyFile = builtins.toString config.sops.secrets.wg1-peer0-psk.path; PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM="; Endpoint = router0-ifog_wg1Endpoint; }; } ]; }; wg2 = { enable = true; netdevConfig = { Name = "wg2"; Kind = "wireguard"; }; wireguardConfig = { PrivateKeyFile = builtins.toString config.sops.secrets.wg0-privatekey.path; FirewallMark = 102; }; wireguardPeers = [ { wireguardPeerConfig = { AllowedIPs = [ # this allows all traffic to be routed through this interface "0.0.0.0/0" # # alternatively, specific destinations could be allowed # # remote peer wg addr # "10.0.0.0/32" # "1.1.1.1/32" # # ifconfig.co. # "172.67.168.106" # "104.21.54.91" ]; PersistentKeepalive = 15; PresharedKeyFile = builtins.toString config.sops.secrets.wg0-peer0-psk.path; PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM="; Endpoint = router0-hosthatch_wg0Endpoint; }; } ]; }; } # generate the vlan devices. these will be tagged on the main bridge // builtins.foldl' (acc: cur: acc // cur) {} ( builtins.map ({ vlanid, vlanid', }: { "20-${mkInterfaceName {inherit vlanid;}}" = { netdevConfig = { Kind = "vlan"; Name = "${mkInterfaceName {inherit vlanid;}}"; }; vlanConfig.Id = vlanid; }; }) ( builtins.map (vlanid: { inherit vlanid; vlanid' = builtins.toString vlanid; }) vlanRange ) ); networks = { # places options here that should always exist "lo" = { matchConfig.Name = "lo"; # these are roughly equivalent to: # ip rule add fwmark 100 priority 0 table 100 # ip rule add fwmark 100 priority 1 prohibit # ip rule add fwmark 101 priority 0 table 101 # ip rule add fwmark 101 priority 1 prohibit routingPolicyRules = [ { routingPolicyRuleConfig = { FirewallMark = 100; Priority = 30000; Table = 100; }; } { routingPolicyRuleConfig = { FirewallMark = 100; Priority = 30001; Table = 100; Type = "prohibit"; }; } { routingPolicyRuleConfig = { FirewallMark = 101; Priority = 30000; Table = 101; }; } { routingPolicyRuleConfig = { FirewallMark = 101; Priority = 30001; Table = 101; Type = "prohibit"; }; } { routingPolicyRuleConfig = { FirewallMark = 102; Priority = 30000; Table = 102; }; } { routingPolicyRuleConfig = { FirewallMark = 102; Priority = 30001; Table = 102; Type = "prohibit"; }; } ]; }; # use lan0 as secondary WAN interface "10-lan0-wan" = { matchConfig.Name = "lan0"; networkConfig = { # start a DHCP Client for IPv4/6 Addressing/Routing DHCP = true; # accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC) IPv6AcceptRA = true; DNSOverTLS = true; DNSSEC = true; IPv6PrivacyExtensions = false; IPForward = true; }; linkConfig.RequiredForOnline = "no"; # similar to # ip route add default via 172.16.0.1 table 101 routes = [ { routeConfig = { Gateway = "_dhcp4"; Table = 101; }; } ]; }; "10-wan" = { matchConfig.Name = "wan"; networkConfig = { # start a DHCP Client for IPv4/6 Addressing/Routing DHCP = true; # accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC) IPv6AcceptRA = true; DNSOverTLS = true; DNSSEC = true; IPv6PrivacyExtensions = false; IPForward = true; }; # make routing on this interface a dependency for network-online.target # linkConfig.RequiredForOnline = "routable"; linkConfig.RequiredForOnline = "no"; # similar to # ip route add default via 192.168.0.1 table 100 routes = [ { routeConfig = { Gateway = "_dhcp4"; Table = 100; }; } { routeConfig = { Gateway = "_dhcp4"; Table = 102; }; } ]; }; # Connect the bridge ports to the bridge "30-lan1" = { matchConfig.Name = "lan1"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "enslaved"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlansByName.dmz.id; PVID = vlansByName.dmz.id; EgressUntagged = vlansByName.dmz.id; }; } ]; }; "30-lan2" = { matchConfig.Name = "lan2"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "enslaved"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlansByName.office.id; PVID = vlansByName.office.id; EgressUntagged = vlansByName.office.id; }; } ]; }; "30-lan3" = { matchConfig.Name = "lan3"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "enslaved"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}"; }; } ]; }; # Configure the bridge for its desired function "40-${bridgeInterfaceName}" = { matchConfig.Name = bridgeInterfaceName; bridgeConfig = {}; address = [ (mkVlanIpv4HostAddr { vlanid = 0; host = 1; }) ]; networkConfig = { ConfigureWithoutCarrier = true; }; # Don't wait for it as it also would wait for wlan and DFS which takes around 5 min linkConfig.RequiredForOnline = "no"; linkConfig.ActivationPolicy = "always-up"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}"; }; } ]; vlan = ( builtins.map (vlanid: (mkInterfaceName {inherit vlanid;})) vlanRange ); }; "50-wg0" = { enable = true; matchConfig.Name = "wg0"; address = [ "10.0.0.1/31" ]; routes = [ # { # routeConfig = { # # test the set uprouting to a specific IP # Destination = "${repoFlake.colmena.sj-bm-hostkey0.deployment.targetHost}/32"; # MultiPathRoute = "10.0.0.0 1"; # }; # } ]; }; "50-wg1" = { enable = true; matchConfig.Name = "wg1"; address = [ "10.0.0.3/31" ]; routes = [ # { # routeConfig = { # Destination = "${repoFlake.colmena.sj-bm-hostkey0.deployment.targetHost}/32"; # MultiPathRoute = "10.0.0.2 1"; # }; # } ]; }; "50-wg2" = { enable = true; matchConfig.Name = "wg2"; address = [ "10.0.1.1/31" ]; routes = [ # TODO: add a testing route here ]; }; } # configuration for the hostapd dynamic interfaces # * netdev type vlan # * host address for vlan # * vlan config for wlan interface // builtins.foldl' (acc: cur: acc // cur) {} (builtins.map ({ vlanid, vlanid', }: { # configure the tagged vlan device with an address and vlan filtering. # dnsmasq is configured to serve the respective /24 range on each tagged device. # this device only receives traffic for the given vlanid and sends tagged traffic to the bridge. "41-${mkInterfaceName {inherit vlanid;}}" = { matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}"; address = [ (mkVlanIpv4HostAddr { inherit vlanid; host = 1; }) ]; networkConfig = { ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "no"; linkConfig.ActivationPolicy = "always-up"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlanid; }; } ]; }; # configure the wlan interface as a bridge member that # * only gets traffic for vid 15 # * untags traffic after receiving it # * tags traffic that comes out of it "41-wlan0.${vlanid'}" = { matchConfig.Name = "wlan0.${vlanid'}"; networkConfig = { Bridge = bridgeInterfaceName; ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "no"; bridgeVLANs = [ { bridgeVLANConfig = { VLAN = vlanid; PVID = vlanid; EgressUntagged = vlanid; }; } ]; }; "50-${mkInterfaceName {inherit vlanid;}}" = { matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}"; address = [ (mkVlanIpv4HostAddr { inherit vlanid; host = 1; }) ]; networkConfig = { ConfigureWithoutCarrier = true; }; linkConfig.RequiredForOnline = "no"; }; }) ( builtins.map (vlanid: { inherit vlanid; vlanid' = builtins.toString vlanid; }) vlanRange )); }; # wireless access point services.hostapd = { enable = true; # package = nodeFlake.packages.${system}.hostapd_patched; radios = let # generated with https://miniwebtool.com/mac-address-generator/ mkBssid = i: "34:56:ce:0f:ed:4${toString i}"; in { wlan0 = { band = "2g"; # FIXME: apparently setting this could cause bugs, testing disabling it for a while. # countryCode = "CH"; channel = 0; # 0 would mean Automatic Channel Selection settings = { # TODO: this would be faster but x13s on windows can't connect when it's enabled. # ieee80211n = 1; # Exclude DFS channels from ACS # This option can be used to exclude all DFS channels from the ACS channel list # in cases where the driver supports DFS channels. acs_exclude_dfs = 0; }; # use 'iw phy#1 info' to determine your VHT capabilities wifi4 = { enable = true; require = false; capabilities = [ "HT20" "HT40+" "LDPC" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1" "MAX-AMSDU-7935" "40-INTOLERANT" # not supported by BPI-R3 module # "DELAYED-BA" # "DSSS_CCK-40" ]; }; wifi5 = { enable = false; require = false; }; wifi6 = { enable = false; require = false; }; networks = { wlan0 = let iface = "wlan0"; in { ssid = "mlsia"; bssid = mkBssid 0; # enables debug logging logLevel = 0; authentication.mode = "wpa2-sha256" # "wpa3-sae-transition" # "wpa3-sae" ; authentication.wpaPskFile = config.sops.secrets."${iface}_wpaPskFile".path; # TODO: unfortunately SAE passwords don't work per VLAN like PSKs do # authentication.saePasswordsFile = config.sops.secrets."${iface}_saePasswordsFile".path; # see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf for reference settings = { # disable syslog because it duplicates stdout logger_syslog = lib.mkForce 0; # bridge = bridgeInterfaceName; # wpa_psk_file = config.sops.secrets.wlan0_wpaPskFile.path; # not yet supported on hostapd 2.10 # sae_password_file = config.sops.secrets.wlan0_saePasswordsFile.path; # resources on vlan tagging # https://wireless.wiki.kernel.org/en/users/Documentation/hostapd#dynamic_vlan_tagging # https://forum.openwrt.org/t/individual-per-passphrase-wifi-vlans-using-wpa-psk-file-no-radius-required/161696/4 dynamic_vlan = 1; # this option currently requires a patch to hostapd vlan_no_bridge = 1; /* not used due to the above vlan_no_bridge setting vlan_tagged_interface = bridgeInterfaceName; vlan_naming = 1; vlan_bridge = "br-${iface}."; */ vlan_file = let generated = builtins.map ( vlanid: "${builtins.toString vlanid} ${iface}.${builtins.toString vlanid}" ) vlanRange; wildcard = [ # Optional wildcard entry matching all VLAN IDs. The first # in the interface # name will be replaced with the VLAN ID. The network interfaces are created # (and removed) dynamically based on the use. # see https://w1.fi/cgit/hostap/tree/hostapd/hostapd.vlan "* ${iface}.#" ]; file = pkgs.writeText "hostapd.vlan" (builtins.concatStringsSep "\n" (generated ++ wildcard)); filePath = toString file; in filePath; wpa_key_mgmt = lib.mkForce (builtins.concatStringsSep " " [ "WPA-PSK" # TODO: the printer can't connect when this is on # "WPA-PSK-SHA256" # unfortunately SAE doesn't support VLAN passwords in the way i'd like to use them # "SAE" ]); # wpa_psk_radius = 0; wpa_pairwise = "CCMP"; wmm_enabled = 1; # IEEE 802.11i (authentication) related configuration # Encrypt management frames to protect against deauthentication and similar attacks. # 0 := disabled; 1 := optional; 2 := required ieee80211w = 1; # sae_require_mfp = 1; # sae_groups = "19 20 21"; # [ENABLE-TLSv1.3] = enable TLSv1.3 (experimental - disabled by default) tls_flags = "[ENABLE-TLSv1.3]"; # TODO: debugging for wifi drops happens below here # Require IEEE 802.1X authorization ieee8021x = 0; # Optionally, hostapd can be configured to use an integrated EAP server # to process EAP authentication locally without need for an external RADIUS # server. This functionality can be used both as a local authentication server # for IEEE 802.1X/EAPOL and as a RADIUS server for other devices. # Use integrated EAP server instead of external RADIUS authentication # server. This is also needed if hostapd is configured to act as a RADIUS # authentication server. eap_server = 0; # Disassociate stations based on excessive transmission failures or other # indications of connection loss. This depends on the driver capabilities and # may not be available with all drivers. disassoc_low_ack = 0; skip_inactivity_poll = 1; # TODO: check if this is required. multicast can be more efficient so it'd be nice to disable this. multicast_to_unicast = 0; }; }; }; }; }; }; services.resolved.enable = false; services.dnsmasq = { enable = true; settings = { domain-needed = true; bogus-priv = true; no-resolv = true; localise-queries = true; proxy-dnssec = true; conntrack = true; # enable for debugging # log-debug = true; # log-queries = true; # disable negative caching no-negcache = true; local-ttl = 0; dhcp-ttl = 0; dhcp-range = let mkDhcpRange = { tag, vlanid, }: builtins.concatStringsSep "," [ tag (mkVlanIpv4HostAddr { inherit vlanid; host = 100; cidr = false; }) (mkVlanIpv4HostAddr { inherit vlanid; host = 199; cidr = false; }) "12h" ]; in builtins.map ( vlanid: mkDhcpRange { tag = mkInterfaceName {inherit vlanid;}; inherit vlanid; } ) vlanRangeWith0; # TODO: double-check that this works dhcp-host = "1c:69:7a:07:08:5f,${dmzExposedHostIpv4},${dmzExposedHost}"; expand-hosts = true; # don't use /etc/hosts as this would advertise ${nodeName} as localhost no-hosts = true; server = [ # upstream DNS servers # https://dnsforge.de/ "176.9.93.198" "176.9.1.117" "2a01:4f8:151:34aa::198" "2a01:4f8:141:316d::117" # cloudflare and google # "9.9.9.9" "8.8.8.8" "1.1.1.1" ]; domain = [ "/${getVlanDomain {vlanid = 0;}}/,local" ] ++ builtins.map ( vlanid: "${getVlanDomain {inherit vlanid;}},${mkVlanIpv4HostAddr { inherit vlanid; host = 0; cidr = true; }},local" ) vlanRangeWith0; # TODO: compare this to using `interface-name` dynamic-host = [ ] ++ builtins.map ( vlanid: builtins.concatStringsSep "," [ # "${getVlanDomain{inherit vlanid;}}" "0.0.0.1" (mkInterfaceName {inherit vlanid;}) "${nodeName}.${getVlanDomain {inherit vlanid;}}" "0.0.0.1" (mkInterfaceName {inherit vlanid;}) ] ) vlanRangeWith0; dhcp-option-force = builtins.map (vlanid: "${mkInterfaceName {inherit vlanid;}},option:domain-search,${getVlanDomain {inherit vlanid;}}") vlanRangeWith0; # auth-server = [ # (builtins.concatStringsSep "," [ # "www.stefanjunker.de" # # (mkInterfaceName { vlanid = vlansByName.dmz.id; }) # # (mkInterfaceName { vlanid = vlansByName.office.id; }) # ]) # ]; cname = [ "mailserver.svc.stefanjunker.de,${dmzExposedHost}" "www.stefanjunker.de,${dmzExposedHost}" "hedgedoc.www.stefanjunker.de,${dmzExposedHost}" "jitsi.www.stefanjunker.de,${dmzExposedHost}" "lldap.www.stefanjunker.de,${dmzExposedHost}" "forgejo.www.stefanjunker.de,${dmzExposedHost}" ]; }; }; system.stateVersion = "24.05"; # boot.kernelPackages = pkgs.linuxPackages_bpir3_6_6; environment.systemPackages = [ pkgs.ethtool pkgs.vim pkgs.wireguard-tools pkgs.tshark pkgs.tmux (pkgs.writeShellScriptBin "dbg-ip" '' echo links: ip -br -c l echo echo addresses: ip -br -c a echo echo vlans: bridge -c vlan '') (pkgs.writeShellScriptBin "dbg-dnsmasq" '' # get the rendered in-use config pgrep -a dnsmasq | grep -Eo '[^ ]*conf' | xargs cat | grep -Eo '[^=]*conf' | xargs cat '') ]; }