From e5f31469ee01e8fc6ab40d62c0a58210200113fb Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:18:08 +0100 Subject: [PATCH 1/7] feat(machines/ap01): add default VLAN and admin VLAN Put DHCPv4 on the default VLAN now. Signed-off-by: Ryan Lahfa --- machines/liminix/ap01/addresses.nix | 10 +++-- machines/liminix/ap01/dns.nix | 5 ++- machines/liminix/ap01/lan.nix | 62 +++++++++++++++-------------- npins/sources.json | 4 +- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/machines/liminix/ap01/addresses.nix b/machines/liminix/ap01/addresses.nix index b3d0840..932a805 100644 --- a/machines/liminix/ap01/addresses.nix +++ b/machines/liminix/ap01/addresses.nix @@ -3,16 +3,18 @@ let svc = config.system.service; in { - services.dhcpv4 = svc.network.dhcp.client.build { + services.init-dhcpv4 = svc.network.dhcp.client.build { interface = config.services.int; dependencies = [ config.services.bridge.components.lan ]; }; - services.defaultroute4 = svc.network.route.build { - via = "$(output ${config.services.dhcpv4} router)"; + services.init-defaultroute4 = svc.network.route.build { + via = "$(output ${config.services.init-dhcpv4} router)"; target = "default"; - dependencies = [ config.services.dhcpv4 ]; + dependencies = [ config.services.init-dhcpv4 ]; }; + + # TODO: ensure SLAAC for admin-vlan. } diff --git a/machines/liminix/ap01/dns.nix b/machines/liminix/ap01/dns.nix index d8346f8..0cbe3f6 100644 --- a/machines/liminix/ap01/dns.nix +++ b/machines/liminix/ap01/dns.nix @@ -8,17 +8,18 @@ in # TODO: support dynamic reconfiguration once we are in the target VLAN? services.resolvconf = oneshot rec { name = "resolvconf"; + # TODO: imho, DNS should be static and provided by the router? up = '' . ${serviceFns} ( in_outputs ${name} - for i in $(output ${config.services.dhcpv4} dns); do + for i in $(output ${config.services.init-dhcpv4} dns); do echo "nameserver $i" >> resolv.conf done ) ''; dependencies = [ - config.services.dhcpv4 + config.services.init-dhcpv4 ]; }; diff --git a/machines/liminix/ap01/lan.nix b/machines/liminix/ap01/lan.nix index 4916706..f8b0051 100644 --- a/machines/liminix/ap01/lan.nix +++ b/machines/liminix/ap01/lan.nix @@ -3,37 +3,41 @@ let svc = config.system.service; in { - services.int = svc.bridge.primary.build { - ifname = "int"; - macAddressFromInterface = config.hardware.networkInterfaces.lan; - }; + # Our bridging is a bit complicated, therefore, we need iproute2. + programs.iproute2.enable = true; - services.bridge = svc.bridge.members.build { - primary = config.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 = { + int = svc.bridge.primary.build { + ifname = "int"; + macAddressFromInterface = config.hardware.networkInterfaces.lan; + untagged = { + enable = true; + pvid = 1; + default-pvid = 1; }; }; + + bridge = svc.bridge.members.build { + primary = config.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 ]; + }; + }; + }; + + admin-vlan = svc.vlan.build { + ifname = "admin"; + primary = config.services.int; + vid = "3001"; + }; }; - - # Default VLAN - # services.vlan-apro = svc.vlan.build { - # vlanId = 0; - # interface = config.services.int; - # }; - - # # Administration VLAN - # services.vlan-admin = svc.vlan.build { - # vlan = 3001; - # interface = config.services.int; - # }; } diff --git a/npins/sources.json b/npins/sources.json index d376076..a9adcb1 100644 --- a/npins/sources.json +++ b/npins/sources.json @@ -131,9 +131,9 @@ "url": "https://git.dgnum.eu/DGNum/liminix" }, "branch": "main", - "revision": "473d6acc3de70bd6dbbb4a77af54f508f25c3c9c", + "revision": "1322de1ee0cdb19fead79e12ab279ee0b575019a", "url": null, - "hash": "00slsh0yqd8n8jcx3sbxgcmw1z28bnszy87pfs0ynfkl3bldzs3d" + "hash": "07nk6nik97k8a57cf17dcj3gn2lbhw1myymrxpqc2aqa3haj754k" }, "linkal": { "type": "Git", -- 2.47.0 From 054cbee74af5143c8444263bb88b89b956352839 Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:18:28 +0100 Subject: [PATCH 2/7] feat(machines/ap01/recovery): fix levitation dependencies We should use the right DHCPv4. Signed-off-by: Ryan Lahfa --- machines/liminix/ap01/recovery.nix | 34 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/machines/liminix/ap01/recovery.nix b/machines/liminix/ap01/recovery.nix index 38271f3..bb8643e 100644 --- a/machines/liminix/ap01/recovery.nix +++ b/machines/liminix/ap01/recovery.nix @@ -6,6 +6,7 @@ }: let svc = config.system.service; + parentConfig = config; in { defaultProfile.packages = with pkgs; [ @@ -19,22 +20,27 @@ in "${modulesPath}/kernel" "${modulesPath}/outputs/tftpboot.nix" "${modulesPath}/outputs.nix" + ( + { config, ... }: + { + services = { + # In this situation, we fallback to the appro VLAN. + # TODO: add support for the admin VLAN. + # Simplest DHCPv4 we can find. + dhcpv4 = svc.network.dhcp.client.build { + interface = parentConfig.hardware.networkInterfaces.lan; + }; + inherit (parentConfig.services) sshd; + defaultroute4 = svc.network.route.build { + via = "$(output ${config.services.dhcpv4} router)"; + target = "default"; + dependencies = [ config.services.dhcpv4 ]; + }; + }; + } + ) ]; nixpkgs.buildPlatform = builtins.currentSystem; - services = { - # In this situation, we fallback to the appro VLAN. - # TODO: add support for the admin VLAN. - # 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 ${config.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. -- 2.47.0 From 7e159cf466a799c515eb701a7d71e260b2e5156a Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:41:01 +0100 Subject: [PATCH 3/7] feat(machines/ap01/recovery): pass the parent hostname with live indicator This makes the experience nicer. Signed-off-by: Ryan Lahfa --- machines/liminix/ap01/recovery.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/machines/liminix/ap01/recovery.nix b/machines/liminix/ap01/recovery.nix index bb8643e..da305c7 100644 --- a/machines/liminix/ap01/recovery.nix +++ b/machines/liminix/ap01/recovery.nix @@ -40,6 +40,7 @@ in } ) ]; + hostname = "${parentConfig.hostname}-live"; nixpkgs.buildPlatform = builtins.currentSystem; defaultProfile.packages = [ mtdutils ]; -- 2.47.0 From d915875a25d8c7f1b39b7f812fee832be29b99fb Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:41:44 +0100 Subject: [PATCH 4/7] feat(scripts/liminix): make it easy to extract the firmware part of the Zyxel NWA FIT image This is useful when reflashing the system from scratch in the levitation mode. Note that doing this will reset the UBI counter to zero, this is bad for wear leveling. Signed-off-by: Ryan Lahfa --- scripts/extract-firmware-from-zyxel-nwa-fit.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 scripts/extract-firmware-from-zyxel-nwa-fit.sh diff --git a/scripts/extract-firmware-from-zyxel-nwa-fit.sh b/scripts/extract-firmware-from-zyxel-nwa-fit.sh new file mode 100755 index 0000000..12a8cd2 --- /dev/null +++ b/scripts/extract-firmware-from-zyxel-nwa-fit.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p ubootTools + +usage() { + echo "extract the firmware part to write it manually from a Zyxel NWA FIT image" + echo "$0 " +} + +ZYXEL_NWA_FIT="$1" +FIRMWARE_OUTPUT="$2" + +dumpimage -T flat_dt -p 0 $ZYXEL_NWA_FIT -o $FIRMWARE_OUTPUT -- 2.47.0 From f3cc7bda8646d7343195b1e31baee7155bc44906 Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:45:51 +0100 Subject: [PATCH 5/7] feat(scripts/liminix): add a basic liminix rebuild This script requires manual efforts on the operator end not to fuck up too hard. This adds min-copy-closure and min-garbage-collect to the development shell. Signed-off-by: Ryan Lahfa --- default.nix | 4 +++ scripts/liminix-rebuild.sh | 53 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100755 scripts/liminix-rebuild.sh diff --git a/default.nix b/default.nix index 2041484..5294372 100644 --- a/default.nix +++ b/default.nix @@ -116,6 +116,10 @@ in pkgs.freeradius pkgs.picocom # for serial access + # Daemon-less copy closure for Liminix systems. + (pkgs.callPackage (sources.liminix + "/pkgs/min-copy-closure") { nix = pkgs.lix; }) + # Daemon-less garbage collection for Liminix systems. + (pkgs.callPackage (sources.liminix + "/pkgs/min-collect-garbage") { nix = pkgs.lix; }) (pkgs.callPackage ./lib/colmena { colmena = pkgs.callPackage "${sources.colmena}/package.nix" { }; }) diff --git a/scripts/liminix-rebuild.sh b/scripts/liminix-rebuild.sh new file mode 100755 index 0000000..5dd9c2b --- /dev/null +++ b/scripts/liminix-rebuild.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# TODO: support automatic levitation when the rebuild counts does not fit in the target. +# TODO: support preflight checks where we detect the environment we are booted in (this requires the levitation to write a special file). +# TODO: use colmena to know the target host and focus on the node name. +set -Eeuo pipefail + +ssh_command=${SSH_COMMAND-ssh} +root_prefix=${ROOT_PREFIX-/} + +reboot="reboot" + +case "$1" in + "--no-reboot") + unset reboot + shift + ;; + "--fast") + reboot="soft" + shift + ;; + "--live") + echo "Root prefix changed from $root_prefix to /mnt" + root_prefix="/mnt" + shift + ;; +esac + +target_host=$1 +shift + +if [ -z "$target_host" ] ; then + echo Usage: liminix-rebuild \[--no-reboot\] target-host params + exit 1 +fi + +if toplevel="$(nom-build $(colmena eval -E "{ nodes, ... }: nodes.$@.config.system.outputs.systemConfiguration" --instantiate))"; then + echo systemConfiguration $toplevel aimed at $root_prefix + sleep 3 + min-copy-closure --root "$root_prefix" $target_host $toplevel + $ssh_command $target_host "$root_prefix/$toplevel/bin/install" "$root_prefix" + case "$reboot" in + reboot) + $ssh_command $target_host "sync; source /etc/profile; reboot" + ;; + soft) + $ssh_command $target_host $toplevel/bin/restart-services + ;; + *) + ;; + esac +else + echo Rebuild failed +fi -- 2.47.0 From eb87c9b3597b8f8e890cd14e9a04a66e742fbbb0 Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:49:14 +0100 Subject: [PATCH 6/7] feat(devshell): add tufted, a Fennel TFTP server This is useful for in-memory testing of APs connected over a simple network. In the future, we may use a TFTP server for field recovery by configuring all our APs to use a certain known IPv4 address for the TFTP server and ensuring that we always have a router on the untagged area answering TFTP requests to reboot an AP in a known configuration, without any serial console attached. Signed-off-by: Ryan Lahfa --- default.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/default.nix b/default.nix index 5294372..6f889f9 100644 --- a/default.nix +++ b/default.nix @@ -120,6 +120,9 @@ in (pkgs.callPackage (sources.liminix + "/pkgs/min-copy-closure") { nix = pkgs.lix; }) # Daemon-less garbage collection for Liminix systems. (pkgs.callPackage (sources.liminix + "/pkgs/min-collect-garbage") { nix = pkgs.lix; }) + # TFTP server, friendly for Nix builds. + (pkgs.callPackage (sources.liminix + "/pkgs/tufted") { }) + (pkgs.callPackage ./lib/colmena { colmena = pkgs.callPackage "${sources.colmena}/package.nix" { }; }) -- 2.47.0 From 9d5d75a427adeebcbbb7076730dc40b51af3e15a Mon Sep 17 00:00:00 2001 From: Ryan Lahfa Date: Sun, 8 Dec 2024 23:51:21 +0100 Subject: [PATCH 7/7] chore(machines/ap01/recovery): better scope for `pkgs` and add `zyxel-bootconfig` to levitation This is useful to reconfigure A/B in memory. Signed-off-by: Ryan Lahfa --- machines/liminix/ap01/recovery.nix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/machines/liminix/ap01/recovery.nix b/machines/liminix/ap01/recovery.nix index da305c7..70c3e2e 100644 --- a/machines/liminix/ap01/recovery.nix +++ b/machines/liminix/ap01/recovery.nix @@ -9,9 +9,9 @@ let parentConfig = config; in { - defaultProfile.packages = with pkgs; [ + defaultProfile.packages = [ # Levitate enable us to mass-reinstall the system on the fly. - (levitate.override { + (pkgs.levitate.override { config = { imports = [ "${modulesPath}/network" @@ -43,7 +43,10 @@ in hostname = "${parentConfig.hostname}-live"; nixpkgs.buildPlatform = builtins.currentSystem; - defaultProfile.packages = [ mtdutils ]; + 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; }; -- 2.47.0