diff --git a/hive.nix b/hive.nix index 0055174..7445b1c 100644 --- a/hive.nix +++ b/hive.nix @@ -34,7 +34,6 @@ let system = node: nodes'.${node}.nixpkgs.system; category = node: nixpkgs'.categories.${system node}; - nodePkgs = node: nixpkgs.${system node}.${version node}; # Builds a patched version of nixpkgs, only as the source @@ -112,7 +111,7 @@ in args; defaults = - { name, nodeMeta, nodePath, ... }: + { name, nodePath, ... }: { # Import the default modules imports = [ diff --git a/machines/liminix/ap-dev/_configuration.nix b/machines/liminix/ap-dev/_configuration.nix new file mode 100644 index 0000000..b85a5ea --- /dev/null +++ b/machines/liminix/ap-dev/_configuration.nix @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ + modulesPath, + sourcePkgs, + name, + ... +}: +{ + imports = [ + "${modulesPath}/wlan.nix" + "${modulesPath}/network" + "${modulesPath}/hostapd" + "${modulesPath}/usteer" + "${modulesPath}/ssh" + "${modulesPath}/ntp" + "${modulesPath}/vlan" + "${modulesPath}/bridge" + "${modulesPath}/jitter-rng" + "${modulesPath}/pki" + "${modulesPath}/ubus" + # System-level configuration + ./system.nix + # Configures our own WLAN. + ./wlan.nix + # Configures our IPv4/IPv6 addresses, e.g. DHCPv4 on VLAN 0, SLAAC on VLAN 3001. + ./addresses.nix + # Configures a basic local DNS. + ./dns.nix + # Configures our management layer, e.g. SSH server + DGNum FAI keys. + ./management.nix + # Configures our recovery system, e.g. a levitation script. + ./recovery.nix + # ubus + ./ipc.nix + # Metadata on the system for field recovery. + ./metadata.nix + # TODO: god that's so a fucking hack. + (import "${modulesPath}/../devices/zyxel-nwa50ax").module + ]; + + hostname = name; + nixpkgs.source = sourcePkgs.path; +} diff --git a/machines/liminix/ap-dev/addresses.nix b/machines/liminix/ap-dev/addresses.nix new file mode 100644 index 0000000..b1a8692 --- /dev/null +++ b/machines/liminix/ap-dev/addresses.nix @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ config, nodeMeta, ... }: +let + svc = config.system.service; + inherit (nodeMeta.extraNodeSettings) ap-no; + adminIp = "192.168.1.${builtins.toString (ap-no + 10)}"; +in +{ + # Our bridging is a bit complicated, therefore, we need iproute2. + programs.iproute2.enable = true; + + services.admin-ip = svc.network.address.build { + interface = config.hardware.networkInterfaces.lan; + address = adminIp; + prefixLength = 24; + family = "inet"; + }; + + services.admin-defaultroute4 = svc.network.route.build { + via = "192.168.1.254"; + target = "default"; + dependencies = [ config.services.admin-ip ]; + }; +} diff --git a/machines/liminix/ap-dev/dns.nix b/machines/liminix/ap-dev/dns.nix new file mode 100644 index 0000000..b371292 --- /dev/null +++ b/machines/liminix/ap-dev/dns.nix @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ pkgs, lib, ... }: +let + inherit (pkgs.pseudofile) dir symlink; + # TODO: imho, DNS should be static and provided by the router? + dns = [ + "8.8.8.8" + "8.8.4.4" + "1.0.0.1" + ]; + resolvconf = pkgs.writeText "resolv.conf" ( + lib.concatMapStringsSep "\n" (dns: ''echo "nameserver ${dns}" >> resolv.conf'') dns + ); +in +{ + # TODO: support dynamic reconfiguration once we are in the target VLAN? + filesystem = dir { + etc = dir { + "resolv.conf" = symlink "${resolvconf}"; + }; + }; +} diff --git a/machines/liminix/ap-dev/ipc.nix b/machines/liminix/ap-dev/ipc.nix new file mode 100644 index 0000000..43c918c --- /dev/null +++ b/machines/liminix/ap-dev/ipc.nix @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ config, ... }: +let + svc = config.system.service; +in +{ + # ubus socket for various needs. + services.ubus = svc.ubus.build { }; +} diff --git a/machines/liminix/ap-dev/management.nix b/machines/liminix/ap-dev/management.nix new file mode 100644 index 0000000..18f6b6f --- /dev/null +++ b/machines/liminix/ap-dev/management.nix @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ config, ... }: +let + svc = config.system.service; +in +{ + # SSH keys are handled by the access control module. + dgn-access-control.enable = true; + users.root = { + # TODO: Change this well-known password + passwd = "$6$Z2MiaMXkpUJRPl2/$fxVE3iD/n208CISM2F6OnWj0Qq0QG2tTQqLCjU80PFJJGIwNLLyOp6SeYH3dH20OvJX1loZRETrThZfIPw.rb/"; + }; + services.sshd = svc.ssh.build { allowRoot = true; }; +} diff --git a/machines/liminix/ap-dev/metadata.nix b/machines/liminix/ap-dev/metadata.nix new file mode 100644 index 0000000..80dce50 --- /dev/null +++ b/machines/liminix/ap-dev/metadata.nix @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ pkgs, ... }: +let + inherit (pkgs.pseudofile) dir; +in +{ + filesystem = dir { + etc = dir { + "nixpkgs.version" = { + type = "f"; + file = "${pkgs.lib.version}"; + mode = "0444"; + }; + }; + }; +} diff --git a/machines/liminix/ap-dev/recovery.nix b/machines/liminix/ap-dev/recovery.nix new file mode 100644 index 0000000..e2d9593 --- /dev/null +++ b/machines/liminix/ap-dev/recovery.nix @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ + config, + pkgs, + modulesPath, + ... +}: +let + parentConfig = config; +in +{ + defaultProfile.packages = [ + # Levitate enable us to mass-reinstall the system on the fly. + # TODO: Test levitation + (pkgs.levitate.override { + config = { + imports = [ + "${modulesPath}/network" + "${modulesPath}/ssh" + "${modulesPath}/hardware.nix" + "${modulesPath}/kernel" + "${modulesPath}/outputs/tftpboot.nix" + "${modulesPath}/outputs.nix" + # FIXME: DHCP has a hidden deps on this, shoud be done in a more intelligent way upstream + "${modulesPath}/iproute2.nix" + (_: { + # FIXME: DHCP has a hidden deps on this, shoud be done in a more intelligent way upstream + programs.iproute2.enable = true; + services = { + inherit (parentConfig.services) + sshd + admin-ip + admin-defaultroute4 + ; + }; + }) + ]; + hostname = "${parentConfig.hostname}-live"; + nixpkgs.buildPlatform = builtins.currentSystem; + + defaultProfile.packages = with pkgs; [ + mtdutils + zyxel-bootconfig + ]; + # Only keep root, which should inherit from DGN access control's root permissions. + users.root = config.users.root; + }; + }) + ]; +} diff --git a/machines/liminix/ap-dev/system.nix b/machines/liminix/ap-dev/system.nix new file mode 100644 index 0000000..6072f8f --- /dev/null +++ b/machines/liminix/ap-dev/system.nix @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ pkgs, config, ... }: +let + svc = config.system.service; +in +{ + # Get moar random please + services = { + jitter = svc.jitter-rng.build { }; + packet_forwarding = svc.network.forward.build { }; + 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"; + }; + + defaultProfile.packages = with pkgs; [ + zyxel-bootconfig + iw + min-collect-garbage + ubus + ]; +} diff --git a/machines/liminix/ap-dev/wlan.nix b/machines/liminix/ap-dev/wlan.nix new file mode 100644 index 0000000..f3d476e --- /dev/null +++ b/machines/liminix/ap-dev/wlan.nix @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: 2024 Ryan Lahfa +# +# SPDX-License-Identifier: EUPL-1.2 + +{ + config, + pkgs, + lib, + nodeMeta, + ... +}: +let + svc = config.system.service; + + inherit (nodeMeta.extraNodeSettings) ap-no; + + hex = x: lib.fixedWidthString 2 "0" (lib.toHexString x); + + mac-1 = "02:5B:6B:00:00:${hex ap-no}"; + mac-2 = "02:5B:6B:01:00:${hex ap-no}"; + + secrets-1 = { + ssid = "DGNum"; + }; + secrets-2 = { + ssid = "DGNum"; + }; + 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"; + bss_transition = 1; + rrm_neighbor_report = 1; + rrm_beacon_report = 1; + }; + + 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; # TODO understand interferences + 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 = 3; + 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 = + let + secret = builtins.getEnv "RADIUS_SECRET"; + in + if secret == "" then + lib.warn "Using a dummy RADIUS secret. Please do not use in production" "DUMMYSECRET" + else + secret; + }; + + mkWifiSta = + params: interface: secrets: + svc.hostapd.build { + inherit interface; + package = pkgs.hostapd-radius; + params = params // secrets; + dependencies = [ config.services.jitter ]; + }; +in +{ + hardware.wlanMacAddresses = { + wlan0 = mac-1; + wlan1 = mac-2; + }; + services = { + usteer = svc.usteer.build { + ifname = "lan"; + dependencies = [ + # TODO is it the right stuff to deend on ? + config.services.hostap-1-ready + config.services.hostap-2-ready + ]; + }; + # wlan0 is the 2.4GHz interface. + hostap-1 = mkWifiSta ( + baseParams // clientRadius // externalRadius // radiusKeyMgmt + ) config.hardware.networkInterfaces.wlan0 secrets-1; + hostap-1-ready = svc.hostapd-ready.build { + interface = config.hardware.networkInterfaces.wlan0; + }; + # wlan1 is the 5GHz interface, e.g. AX capable. + 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. + hostap-2-ready = svc.hostapd-ready.build { + interface = config.hardware.networkInterfaces.wlan1; + }; + }; +} diff --git a/meta/nodes/liminix.nix b/meta/nodes/liminix.nix index 5cc6130..2017f08 100644 --- a/meta/nodes/liminix.nix +++ b/meta/nodes/liminix.nix @@ -18,6 +18,33 @@ let nix-lib = import ../../lib/nix-lib; inherit (nix-lib) mapFuse; + mkDevAp = ap-no: { + "ap-dev-${builtins.toString ap-no}" = { + site = "unknown"; + adminGroups = [ "fai" ]; + + hashedPassword = "$y$j9T$DMOQEWOYFHjNS0myrXp4x/$MG33VSdXGvib.99eN.AbvyVdNNJw4ERjAwK4.ULJe/A"; + + stateVersion = null; + + nodeDir = "ap-dev"; + + nixpkgs = { + system = "zyxel-nwa50ax"; + version = "24.05"; + }; + + deployment.tags = [ + "ap-dev" + ]; + + extraNodeSettings = { + inherit ap-no; + vendor-mac = null; + }; + }; + }; + mkAP' = building: floor: ap-no: { "ap-v01-${builtins.toString building}-${builtins.toString floor}-${builtins.toString ap-no}" = { site = "unknown"; @@ -95,3 +122,7 @@ let in { } // builtins.foldl' (nodes: building: nodes // mkAPs-building building) { } (builtins.attrValues APs) +// mapFuse mkDevAp [ + 0 + 1 +] diff --git a/npins/sources.json b/npins/sources.json index 80f920b..bb71942 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -355,4 +355,4 @@ } }, "version": 3 -} \ No newline at end of file +}