{ config, pkgs, modulesPath, ... }: let inherit (pkgs.liminix.services) oneshot; inherit (pkgs.pseudofile) symlink dir; inherit (pkgs) serviceFns; svc = config.system.service; secrets-1 = { ssid = "DGNum 2G prototype (N)"; }; secrets-2 = { ssid = "DGNum 5G prototype (AX)"; }; baseParams = { country_code = "FR"; hw_mode = "g"; channel = 6; wmm_enabled = 1; ieee80211n = 1; ht_capab = "[LDPC][GF][HT40-][HT40+][SHORT-GI-40][MAX-AMSDU-7935][TX-STBC]"; auth_algs = 1; wpa = 2; wpa_pairwise = "TKIP CCMP"; rsn_pairwise = "CCMP"; }; radiusKeyMgmt = { wpa_key_mgmt = "WPA-EAP"; }; modernParams = { hw_mode = "a"; he_su_beamformer = 1; he_su_beamformee = 1; he_mu_beamformer = 1; preamble = 1; # Allow radar detection. ieee80211d = 1; ieee80211h = 1; ieee80211ac = 1; ieee80211ax = 1; vht_capab = "[MAX-MPDU-7991][SU-BEAMFORMEE][SU-BEAMFORMER][RXLDPC][SHORT-GI-80][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN][TX-STBC-2BY1][RX-STBC-1][MU-BEAMFORMER]"; vht_oper_chwidth = 1; he_oper_chwidth = 1; channel = 36; vht_oper_centr_freq_seg0_idx = 42; he_oper_centr_freq_seg0_idx = 42; require_vht = 1; }; clientRadius = { ieee8021x = 1; eapol_version = 2; use_pae_group_addr = 1; dynamic_vlan = 0; vlan_tagged_interface = "lan"; }; externalRadius = { # TODO: when we have proper IPAM, set the right value here. own_ip_addr = "127.0.0.1"; nas_identifier = "ap01.dgnum.eu"; # No DNS here, hostapd do not support this mode. auth_server_addr = "129.199.195.129"; auth_server_port = 1812; auth_server_shared_secret = "read it online"; }; mkWifiSta = params: interface: secrets: svc.hostapd.build { inherit interface; package = pkgs.hostapd-radius; params = params // secrets; dependencies = [ config.services.jitter ]; }; in rec { imports = [ "${modulesPath}/wlan.nix" "${modulesPath}/network" "${modulesPath}/dhcp6c" "${modulesPath}/hostapd" "${modulesPath}/ssh" "${modulesPath}/ntp" "${modulesPath}/vlan" "${modulesPath}/bridge" "${modulesPath}/jitter-rng" "${modulesPath}/pki" "${modulesPath}/ubus" ../../modules/dgn-access-control.nix # TODO: god that's so a fucking hack. (import "${modulesPath}/../devices/zyxel-nwa50ax").module ]; hostname = "ap01-prototype"; # Get moar random please services.jitter = svc.jitter-rng.build { }; services.ubus = svc.ubus.build { }; # SSH keys are handled by the access control module. dgn-access-control.enable = true; users.root = { passwd = "$6$jVXFFOp8HBYmgINR$lutB4kvw.W1jlXRby9ZYAgBitQ32RxQdYAGN.s2x4ris8J07vM6tzlRBQoeLELOIEMClDzbciQV0itfHQnTqd1"; }; services.int = svc.bridge.primary.build { ifname = "int"; macAddressFromInterface = config.hardware.networkInterfaces.lan; }; services.bridge = svc.bridge.members.build { primary = services.int; members = { lan.member = config.hardware.networkInterfaces.lan; wlan0 = { member = config.hardware.networkInterfaces.wlan0; # Bridge only once hostapd is ready. dependencies = [ config.services.hostap-1-ready ]; }; wlan1 = { member = config.hardware.networkInterfaces.wlan1; # Bridge only once hostapd is ready. dependencies = [ config.services.hostap-2-ready ]; }; }; }; services.resolvconf = oneshot rec { name = "resolvconf"; up = '' . ${serviceFns} ( in_outputs ${name} for i in $(output ${services.dhcpv4} dns); do echo "nameserver $i" >> resolv.conf done ) ''; dependencies = [ config.services.dhcpv4 ]; }; filesystem = dir { etc = dir { "resolv.conf" = symlink "${config.services.resolvconf}/.outputs/resolv.conf"; "nixpkgs.version" = { type = "f"; file = "${pkgs.lib.version}"; mode = "0444"; }; }; }; services.dhcpv4 = svc.network.dhcp.client.build { interface = config.services.int; dependencies = [ config.services.hostname config.services.bridge.components.lan ]; }; # TODO(raito): these won't work with RAs # fix them in Liminix directly and re-enable. # services.dhcpv6 = svc.dhcp6c.client.build { # interface = config.services.int; # dependencies = [ # config.services.hostname # config.services.bridge # ]; # }; # services.ipv6 = svc.dhcp6c.address.build { # interface = config.services.int; # client = config.services.dhcpv6; # dependencies = [ config.services.hostname ]; # }; services.defaultroute4 = svc.network.route.build { via = "$(output ${services.dhcpv4} router)"; target = "default"; dependencies = [ services.dhcpv4 ]; }; services.packet_forwarding = svc.network.forward.build { }; services.sshd = svc.ssh.build { allowRoot = true; }; services.ntp = config.system.service.ntp.build { pools = { "pool.ntp.org" = [ "iburst" ]; }; dependencies = [ config.services.jitter ]; }; boot.tftp = { serverip = "192.0.2.10"; ipaddr = "192.0.2.12"; }; # wlan0 is the 2.4GHz interface. services.hostap-1 = mkWifiSta ( baseParams // radiusKeyMgmt ) config.hardware.networkInterfaces.wlan0 secrets-1; services.hostap-1-ready = svc.hostapd-ready.build { interface = config.hardware.networkInterfaces.wlan0; }; # wlan1 is the 5GHz interface, e.g. AX capable. services.hostap-2 = mkWifiSta ( baseParams // clientRadius // externalRadius // radiusKeyMgmt // modernParams ) config.hardware.networkInterfaces.wlan1 secrets-2; # Oneshot that waits until the hostapd has set the interface in operational state. services.hostap-2-ready = svc.hostapd-ready.build { interface = config.hardware.networkInterfaces.wlan1; }; defaultProfile.packages = with pkgs; [ zyxel-bootconfig min-collect-garbage iwinfo ifwait # Levitate enable us to mass-reinstall the system on the fly. (levitate.override { config = { imports = [ "${modulesPath}/network" "${modulesPath}/ssh" "${modulesPath}/hardware.nix" "${modulesPath}/kernel" "${modulesPath}/outputs/tftpboot.nix" "${modulesPath}/outputs.nix" ]; services = { # Simplest DHCPv4 we can find. dhcpv4 = svc.network.dhcp.client.build { interface = config.hardware.networkInterfaces.lan; }; inherit (config.services) sshd; defaultroute4 = svc.network.route.build { via = "$(output ${services.dhcpv4} router)"; target = "default"; dependencies = [ config.services.dhcpv4 ]; }; }; defaultProfile.packages = [ mtdutils ]; # Only keep root, which should inherit from DGN access control's root permissions. users.root = config.users.root; }; }) ]; }