Add IPv6 and VLAN devices to the web UI and router-setup

The web UI lists physical and VLAN interfaces and takes a VLAN uplink as
a device name (enp2s0.1001); it has IPv6 checkboxes for the WAN and each
LAN. router-setup asks whether to create VLANs before WAN and LAN are
picked, and whether to enable IPv6. --config-only writes the
configuration without installing, which the setup test uses for a PPPoE
uplink on a VLAN.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
2026-10-29 21:11:11 +01:00
co-authored by Claude Sonnet 5.5
parent 5983eadae2
commit 574748806f
6 changed files with 276 additions and 38 deletions
+74 -19
View File
@@ -39,15 +39,17 @@ let
];
text = ''
usage() {
echo "usage: router-setup [--local FILE]" >&2
echo "usage: router-setup [--local FILE] [--config-only DIR]" >&2
echo " Asks a few questions, then ERASES the chosen disk and installs the router." >&2
echo " --local FILE extra NixOS module for this box (installed as config/local.nix)" >&2
echo " --config-only DIR only write the settings and secrets to DIR; install nothing" >&2
exit 2
}
local_nix=""
local_nix="" config_only=""
while [ $# -gt 0 ]; do
case $1 in
--local) shift; [ $# -gt 0 ] || usage; local_nix=$1 ;;
--config-only) shift; [ $# -gt 0 ] || usage; config_only=$1 ;;
*) usage ;;
esac
shift
@@ -88,6 +90,17 @@ let
echo " please pick one of the above" >&2
done
}
yes_no() { # yes_no VAR PROMPT DEFAULT(y|n)
local yn
while :; do
ask yn "$2 (y/n)" "$3"
case $yn in
y|Y|yes) printf -v "$1" '%s' y; return ;;
n|N|no) printf -v "$1" '%s' n; return ;;
esac
echo " y or n" >&2
done
}
ipv4_ok() {
[[ $1 =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]] || return 1
for o in "''${BASH_REMATCH[@]:1}"; do [ "$o" -le 255 ] || return 1; done
@@ -100,7 +113,7 @@ let
}
# Set by ask/choose (printf -v).
disk="" wan="" lan="" wan_type="" host="" lan_address="" ssh_key="" answer=""
disk="" wan="" lan="" wan_type="" want_vlans="" want_v6="" vlan_parent="" vlan_id="" host="" lan_address="" ssh_key="" answer=""
webui_password="" webui_password2="" reuse="" reuse_label="" new=""
work=$(mktemp -d /run/router-setup.XXXXXX)
@@ -252,34 +265,65 @@ let
fi
else # a new router
# Devices WAN and LAN can be assigned to: the NICs, plus VLANs on them.
devices=("''${nics[@]}")
echo
echo "VLANs are separate networks sharing one cable: some providers deliver the internet"
echo "tagged with a VLAN ID, and one NIC can then also serve the LAN untagged."
yes_no want_vlans "Create VLANs now?" n
while [ "$want_vlans" = y ]; do
choose vlan_parent "VLAN on which interface" "''${nics[@]}"
while :; do
ask vlan_id "VLAN ID (1-4094)"
[[ $vlan_id =~ ^[0-9]+$ ]] && [ "$vlan_id" -ge 1 ] && [ "$vlan_id" -le 4094 ] && break
echo " a number from 1 to 4094" >&2
done
devices+=("$vlan_parent.$vlan_id VLAN $vlan_id on $vlan_parent")
yes_no want_vlans "Another VLAN?" n
done
echo
echo "Interface towards the internet (WAN):"
choose wan "WAN interface" "''${nics[@]}"
choose wan "WAN interface" "''${devices[@]}"
echo
echo "Interface for the local network (LAN):"
choose lan "LAN interface" "''${nics[@]}"
choose lan "LAN interface" "''${devices[@]}"
# A VLAN device is its NIC plus an ID in the settings.
wan_dev=''${wan%.*} wan_vlan=""
[ "$wan_dev" = "$wan" ] || wan_vlan=''${wan##*.}
lan_dev=''${lan%.*} lan_vlan=""
[ "$lan_dev" = "$lan" ] || lan_vlan=''${lan##*.}
if [ "$wan" = "$lan" ]; then
echo "router-setup: WAN and LAN cannot be the same interface" >&2
exit 1
fi
echo
echo "Internet connection:"
choose wan_type "Connection" \
"dhcp (behind a cable modem, an ONT or another router)" \
"pppoe (DSL or fibre with a username and password)"
pppoe_user="" pppoe_password="" pppoe_vlan=""
pppoe_user="" pppoe_password=""
if [ "$wan_type" = pppoe ]; then
while [ -z "$pppoe_user" ]; do ask pppoe_user "PPPoE username"; done
while [ -z "$pppoe_password" ]; do ask_secret pppoe_password "PPPoE password"; done
while :; do
ask pppoe_vlan "VLAN ID the provider requires (empty for none)" ""
[ -z "$pppoe_vlan" ] && break
[[ $pppoe_vlan =~ ^[0-9]+$ ]] && [ "$pppoe_vlan" -ge 1 ] && [ "$pppoe_vlan" -le 4094 ] && break
echo " a number from 1 to 4094" >&2
done
fi
if [ "$wan" = "$lan" ] && [ -z "$pppoe_vlan" ]; then
echo "router-setup: WAN and LAN can only share an interface when PPPoE runs on a VLAN" >&2
if [ -n "$wan_vlan" ] && [ "$wan_type" != pppoe ]; then
echo "router-setup: an uplink on a VLAN is only supported with PPPoE" >&2
exit 1
fi
if [ "$wan_dev" = "$lan_dev" ] && [ -z "$wan_vlan$lan_vlan" ]; then
echo "router-setup: WAN and LAN cannot share an interface without a VLAN" >&2
exit 1
fi
if [ "$wan_dev" = "$lan_dev" ] && [ -z "$wan_vlan" ]; then
echo "router-setup: with WAN and LAN on one interface, the WAN must be the VLAN" >&2
exit 1
fi
echo
yes_no want_v6 "Enable IPv6 (the provider delegates a prefix to the LAN)?" y
echo
ask host "Host name" "router"
while :; do
@@ -302,7 +346,7 @@ let
echo
echo "Summary"
echo " disk: $disk (will be erased)"
echo " WAN: $wan, $wan_type''${pppoe_user:+ as $pppoe_user}''${pppoe_vlan:+ on VLAN $pppoe_vlan}"
echo " WAN: $wan, $wan_type''${pppoe_user:+ as $pppoe_user}, IPv6 $([ "$want_v6" = y ] && echo on || echo off)"
echo " LAN: $lan, $lan_address/24, DHCP $net.100 - $net.200"
echo " host name: $host"
echo " web UI: https://$lan_address"
@@ -320,21 +364,25 @@ let
jq -n \
--arg host "$host" --arg state "${cfg.stateVersion}" --argjson keys "$keys" \
--argjson schema ${toString cfg.schemaVersion} \
--arg wan "$wan" --arg type "$wan_type" --arg user "$pppoe_user" --arg vlan "$pppoe_vlan" \
--arg lan "$lan" --arg addr "$lan_address" --arg net "$net" '
--arg wan "$wan_dev" --arg type "$wan_type" --arg user "$pppoe_user" --arg vlan "$wan_vlan" \
--arg lan "$lan_dev" --arg lanvlan "$lan_vlan" --arg v6 "$want_v6" \
--arg addr "$lan_address" --arg net "$net" '
{
version: $schema,
system: { hostName: $host, stateVersion: $state, sshKeys: $keys },
router: {
wan: ({ interface: $wan, ipv4: { type: $type } }
| if $type == "pppoe" then .ipv4.pppoe = ({ username: $user, passwordFile: "/var/lib/router/pppoe-password" }
+ (if $vlan == "" then {} else { vlan: ($vlan | tonumber) } end)) else . end),
+ (if $vlan == "" then {} else { vlan: ($vlan | tonumber) } end)) else . end
| if $v6 == "y" then .ipv6 = { enable: true, transport: (if $type == "pppoe" then "ppp" else "native" end) } else . end),
lans: {
lan: {
lan: ({
interface: $lan, internet: true,
ipv4: { address: $addr },
dhcp: { pool: { start: ($net + ".100"), end: ($net + ".200") } }
}
+ (if $lanvlan == "" then {} else { vlan: ($lanvlan | tonumber) } end)
+ (if $v6 == "y" then { ipv6: { pd: true } } else {} end))
}
}
}' > "$config/settings.json"
@@ -346,6 +394,13 @@ let
(umask 077; printf '%s\n' "$webui_password" | mkpasswd -s > "$work/secrets/root-password")
fi # new router
if [ -n "$config_only" ]; then
mkdir -p "$config_only"
cp -a "$work/secrets/." "$config_only/"
echo "router-setup: wrote the configuration to $config_only"
exit 0
fi
echo
echo ">> building the router system (no internet needed; takes a few minutes)"
system=$(nix build --no-link --print-out-paths --out-link "$work/system" \