diff --git a/ap/10-van-ap.network b/ap/10-van-ap.network
index 976b150..2fdb8d2 100644
--- a/ap/10-van-ap.network
+++ b/ap/10-van-ap.network
@@ -1,5 +1,5 @@
[Match]
-Name=wlxc83a35a4ee55
+Name=@WIFI_5G_IFACE@
[Link]
RequiredForOnline=no
diff --git a/ap/11-van-ap-2g.network b/ap/11-van-ap-2g.network
index d55f3ce..e1d1342 100644
--- a/ap/11-van-ap-2g.network
+++ b/ap/11-van-ap-2g.network
@@ -1,5 +1,5 @@
[Match]
-Name=wlxd8ec5e2faa8c
+Name=@WIFI_2G_IFACE@
[Link]
RequiredForOnline=no
diff --git a/ap/22-van-lan.network b/ap/22-van-lan.network
index 029bf91..357e9b9 100644
--- a/ap/22-van-lan.network
+++ b/ap/22-van-lan.network
@@ -2,7 +2,7 @@
# gets DHCP/DNS from the same dnsmasq as Wi-Fi clients. NM must leave this device
# alone (see van-ap-unmanaged.conf) for networkd to own it here.
[Match]
-Name=enx00e04c331140
+Name=@LAN_USB_IFACE@
[Link]
RequiredForOnline=no
diff --git a/ap/50-van-wan.yaml b/ap/50-van-wan.yaml
index b660805..d6b5a55 100644
--- a/ap/50-van-wan.yaml
+++ b/ap/50-van-wan.yaml
@@ -10,10 +10,11 @@ network:
version: 2
ethernets:
# Starlink dish uplink (RTL8153 USB NIC, MAC-named — travels with the
- # adapter). The /32 link route keeps the dish's management address reachable
- # no matter which WAN holds the default route: the dish answers on
- # 192.168.100.1 (gRPC :9200) even while the uplink sits behind CGNAT.
- enxd8ec5eeb3512:
+ # adapter, see STARLINK_IFACE in deploy.conf). The /32 link route keeps the
+ # dish's management address reachable no matter which WAN holds the
+ # default route: the dish answers on 192.168.100.1 (gRPC :9200) even while
+ # the uplink sits behind CGNAT.
+ @STARLINK_IFACE@:
renderer: NetworkManager
optional: true
dhcp4: true
diff --git a/ap/hostapd-2g.conf b/ap/hostapd-2g.conf
index b96800f..299e210 100644
--- a/ap/hostapd-2g.conf
+++ b/ap/hostapd-2g.conf
@@ -1,4 +1,4 @@
-interface=wlxd8ec5e2faa8c
+interface=@WIFI_2G_IFACE@
# Second, independent hostapd instance (hostapd-2g.service) — 2.4GHz band of the same
# VanLink network, bridged into the same br0 segment as the 5GHz AP and the wired port.
# Separate process on purpose: a USB wedge on one radio must never take down the other.
@@ -17,7 +17,7 @@ wmm_enabled=1
# HT20 only — 40MHz in 2.4GHz overlaps most of the band and coexistence would force
# it back to 20MHz near any neighbor anyway.
ieee80211n=1
-ht_capab=[LDPC][SHORT-GI-20]
+ht_capab=[SHORT-GI-20]
auth_algs=1
wpa=2
wpa_passphrase=1foot11foot11
diff --git a/ap/hostapd-2g.service b/ap/hostapd-2g.service
index f6c3c43..858e7e1 100644
--- a/ap/hostapd-2g.service
+++ b/ap/hostapd-2g.service
@@ -1,5 +1,5 @@
[Unit]
-Description=VanLink 2.4GHz AP (hostapd on wlxd8ec5e2faa8c, RTL8822BU)
+Description=VanLink 2.4GHz AP (hostapd on @WIFI_2G_IFACE@ — see deploy.conf for current hardware)
# Same USB re-enumeration hazards as the 5GHz AP (see hostapd-restart.conf for the
# full story): never stop retrying, and back off a few seconds so the USB device can
# re-enumerate before the next attempt.
diff --git a/ap/hostapd.conf b/ap/hostapd.conf
index a2f9837..f7633c7 100644
--- a/ap/hostapd.conf
+++ b/ap/hostapd.conf
@@ -1,4 +1,4 @@
-interface=wlxc83a35a4ee55
+interface=@WIFI_5G_IFACE@
# Put the AP into br0 so Wi-Fi and the wired LAN port share one 10.42.0.0/24 segment.
# hostapd adds the wlan to the bridge after setting AP mode; the bridge itself + its
# wired member + the gateway IP are defined under /etc/systemd/network (2x-van-br0/lan).
diff --git a/ap/rtw88.conf b/ap/rtw88.conf
deleted file mode 100644
index b552803..0000000
--- a/ap/rtw88.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-# 2.4GHz AP dongle (Linksys WUSB6300 v2, RTL8822BU, rtw88_8822bu).
-# Deep power-save has no business on an always-on AP radio — same reasoning as the
-# rtw89 disable_ps_mode gotcha on the 5GHz dongle (rtw89.conf).
-options rtw88_core disable_lps_deep=Y
-# Don't let the driver switch a USB2-enumerated dongle up to USB3: USB3 signalling
-# radiates broadband noise right across the 2.4GHz band, and HT20 rates fit in USB2
-# easily. NOTE: this only blocks the driver's own USB2->3 switch — a dongle sitting in
-# a USB3 port still enumerates at SuperSpeed (it currently does); move it to a USB2
-# port if 2.4GHz range/interference ever looks poor.
-options rtw88_usb switch_usb_mode=N
diff --git a/ap/van-ap-unmanaged.conf b/ap/van-ap-unmanaged.conf
index f3c0546..16a2eae 100644
--- a/ap/van-ap-unmanaged.conf
+++ b/ap/van-ap-unmanaged.conf
@@ -2,4 +2,4 @@
# The AP wlans (5GHz + 2.4GHz) and the wired LAN ports (USB dongle + onboard
# eth0) are all owned by systemd-networkd/hostapd (bridged into br0), so
# NetworkManager must not touch them.
-unmanaged-devices=interface-name:wlxc83a35a4ee55;interface-name:wlxd8ec5e2faa8c;interface-name:enx00e04c331140;interface-name:eth0
+unmanaged-devices=interface-name:@WIFI_5G_IFACE@;interface-name:@WIFI_2G_IFACE@;interface-name:@LAN_USB_IFACE@;interface-name:eth0
diff --git a/cockpit/vanrouter/index.html b/cockpit/vanrouter/index.html
index 363256e..bbe47d0 100644
--- a/cockpit/vanrouter/index.html
+++ b/cockpit/vanrouter/index.html
@@ -15,6 +15,11 @@
loading…
+
+
Deploy Warnings
+
+
+
Access Points
@@ -45,6 +50,14 @@
+
+
+
Wi-Fi Networks
+
+
+
Click Scan to search for networks.
+
+
WAN / Uplinks
diff --git a/cockpit/vanrouter/vanrouter.css b/cockpit/vanrouter/vanrouter.css
index 0d30f91..f8178ae 100644
--- a/cockpit/vanrouter/vanrouter.css
+++ b/cockpit/vanrouter/vanrouter.css
@@ -23,3 +23,7 @@ th { color: #6a6e73; font-weight: 600; }
.active-wan { font-weight: 700; color: #0066cc; }
.btn { margin-left: 6px; padding: 3px 10px; cursor: pointer; }
.btn[disabled] { cursor: default; opacity: 0.5; }
+#deploywarn-card { border-color: #e08a00; }
+.warn-list { margin: 0; padding-left: 20px; }
+.warn-list li { background: #f5d9a8; color: #5f4414; border-radius: 4px;
+ padding: 4px 8px; margin: 4px 0; list-style: none; margin-left: -20px; }
diff --git a/cockpit/vanrouter/vanrouter.js b/cockpit/vanrouter/vanrouter.js
index 49dd9c3..3bf4f82 100644
--- a/cockpit/vanrouter/vanrouter.js
+++ b/cockpit/vanrouter/vanrouter.js
@@ -7,18 +7,20 @@
// { superuser: "require" } which triggers Cockpit's admin (polkit) escalation.
// AP radios (MAC-derived iface names, stable) — each runs its own hostapd unit.
+// Interface names/vendor ID below are templated from deploy.conf at deploy
+// time (see deploy.sh's render()) — edit deploy.conf, not the values here.
const APS = [
- { iface: "wlxc83a35a4ee55", unit: "hostapd", band: "5GHz" }, // RTL8852BU
- { iface: "wlxd8ec5e2faa8c", unit: "hostapd-2g", band: "2.4GHz" }, // RTL8822BU
+ { iface: "@WIFI_5G_IFACE@", unit: "hostapd", band: "5GHz" },
+ { iface: "@WIFI_2G_IFACE@", unit: "hostapd-2g", band: "2.4GHz" },
];
const PREFER_FILE = "/run/van-failover/prefer"; // van-failover reads this to pick the preferred WAN
// Wired LAN bridge ports: clients behind them are found via the bridge FDB.
-const LAN_PORTS = { "eth0": "LAN (eth0)", "enx00e04c331140": "LAN (USB)" };
+const LAN_PORTS = { "eth0": "LAN (eth0)", "@LAN_USB_IFACE@": "LAN (USB)" };
// Starlink dish: gRPC status API on the fixed management IP (reached via the /32
// link route the netplan profile installs on the RTL8153 uplink).
-const STARLINK = { iface: "enxd8ec5eeb3512", dish: "192.168.100.1:9200" };
+const STARLINK = { iface: "@STARLINK_IFACE@", dish: "192.168.100.1:9200" };
// Cellular modem (Quectel EC25-AF): used to find the USB device for a hard restart.
-const MODEM_USB_VENDOR = "2c7c";
+const MODEM_USB_VENDOR = "@MODEM_USB_VENDOR@";
function run(args, opts) {
return cockpit.spawn(args, Object.assign({ err: "message" }, opts || {}));
@@ -47,6 +49,7 @@ const STATUS_SCRIPT = (() => {
add(`info${i}`, `iw dev ${a.iface} info`);
add(`stations${i}`, `iw dev ${a.iface} station dump`);
});
+ add("deploywarn", "cat /var/lib/vanlink/deploy-warnings.json");
add("neigh", "ip -j neigh show dev br0");
add("fdb", "bridge -j fdb show br br0");
add("leases", "cat /var/lib/misc/dnsmasq.leases");
@@ -83,6 +86,21 @@ function parseJSON(text, fallback) {
try { return JSON.parse(text); } catch (e) { return fallback; }
}
+/* ---------- Deploy warnings (deploy.sh's collected warn() calls) ---------- */
+
+function renderDeployWarnings(dw) {
+ const card = document.getElementById("deploywarn-card");
+ const warnings = (dw && dw.warnings) || [];
+ if (!warnings.length) { card.style.display = "none"; return; }
+ card.style.display = "";
+ let html = `
`;
+ warnings.forEach(w => { html += `
${esc(w)}
`; });
+ html += `
`;
+ html += `
From last deploy (${esc(dw.deployed || "—")}) — ` +
+ `re-run sudo ./deploy.sh after fixing to clear.
`;
+ document.getElementById("deploywarn").innerHTML = html;
+}
+
/* ---------- Access Point ---------- */
function parseAP(a, i, secs) {
@@ -321,7 +339,7 @@ function renderFailover(fo) {
` · updated ${esc(fo.updated || "")}`;
html += `
WAN
Priority
Device
Status
Metric
`;
fo.wans.forEach(w => {
- const status = !w.present ? `absent`
+ const status = !w.present ? `absent`
: w.up ? `up`
: `down`;
const prio = w.preferred ? `${esc(w.priority)} (preferred)` : esc(w.priority);
@@ -333,6 +351,106 @@ function renderFailover(fo) {
el.innerHTML = html;
}
+/* ---------- Wi-Fi network selector (wlan0 WAN) ---------- */
+
+// The onboard radio used for the WiFi WAN — unlike the AP dongles it's never
+// templated, this host only ever has the one.
+const WIFI_IFACE = "wlan0";
+
+// SSIDs the van's own AP radios are currently beaconing (kept live from refresh()'s
+// `iw dev ... info` parse, not hardcoded) — wlan0 "connecting" to its own AP would be
+// a nonsensical loop, so those rows get their Connect button disabled below.
+let ownAPSSIDs = new Set();
+
+// nmcli -t escapes ':' inside field values as '\:' — split on unescaped ':' only.
+// SSIDs are environment-controlled strings (the van parks near arbitrary APs), so
+// worth handling properly rather than a naive split(":").
+function splitNmcli(line) {
+ const parts = [];
+ let cur = "";
+ for (let i = 0; i < line.length; i++) {
+ if (line[i] === "\\" && line[i + 1] === ":") { cur += ":"; i++; }
+ else if (line[i] === ":") { parts.push(cur); cur = ""; }
+ else cur += line[i];
+ }
+ parts.push(cur);
+ return parts;
+}
+
+function renderWifiList(rows) {
+ const el = document.getElementById("wifi");
+ if (!rows.length) {
+ el.innerHTML = `