fix(secubox-toolbox): persist AP iface IP via systemd-networkd (ref #482) (#484)

Sans ce fix, à chaque débranchement+rebranchement de la clé USB Wi-Fi,
l'iface perd son IP 10.99.0.1/24. dnsmasq répond "DHCP packet received
which has no address" et tous les clients VILLAGE3B restent en limbo.

## Implementation

- conf/ap-iface.network.j2 : template Jinja rendu vers
  /etc/systemd/network/10-secubox-toolbox-ap.network
- scripts/toolbox-up : rend + applique + recharge systemd-networkd
  (fallback ip-addr-replace si networkd indisponible)

Match=Name=<iface>, Address=<gateway>/24, ConfigureWithoutCarrier=yes
pour que l'IP s'applique AVANT que hostapd ne porte le radio.

## Bump

1.4.0 → 1.5.0-1~bookworm1

## Test E2E sur gk2 (2026-06-04)

- toolbox-up rendu le fichier depuis /etc/secubox/toolbox.toml ✓
- networkctl reload appliqué ✓
- ip addr show wlxa854b2428fbb → 10.99.0.1/24 OK ✓
- À tester : unplug → replug → vérifier IP réapparaît auto

Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
This commit is contained in:
CyberMind 2026-06-05 06:27:48 +02:00 committed by GitHub
parent 43b87d0111
commit 45e5f8aa84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 61 additions and 5 deletions

View File

@ -0,0 +1,13 @@
{# SPDX-License-Identifier: LicenseRef-CMSD-1.0 #}
{# Render via toolbox-up → /etc/systemd/network/10-secubox-toolbox-ap.network #}
# /etc/systemd/network/10-secubox-toolbox-ap.network
# Persists the AP captive-subnet IP across iface re-enumeration (USB replug).
[Match]
Name={{ ap.iface }}
[Network]
Address={{ dhcp.gateway }}/24
ConfigureWithoutCarrier=yes
LinkLocalAddressing=no
IPv6AcceptRA=no

View File

@ -1,3 +1,17 @@
secubox-toolbox (1.5.0-1~bookworm1) bookworm; urgency=medium
* Add ap-iface.network.j2 Jinja template + toolbox-up renders it to
/etc/systemd/network/10-secubox-toolbox-ap.network at toolbox-up time.
Persists 10.99.0.1/24 on the AP iface across USB-dongle re-enumeration
(replug). ConfigureWithoutCarrier=yes so the IP applies even before
hostapd brings the radio up.
* toolbox-up : detect systemd-networkd availability ; fallback to
imperative `ip addr replace` if networkd is not used.
* Closes #482.
-- Gerald KERMA <devel@cybermind.fr> Thu, 04 Jun 2026 18:15:00 +0200
secubox-toolbox (1.4.0-1~bookworm1) bookworm; urgency=medium
* WebUI admin /toolbox/ : nouvelles cards Live metrics 24h (events par

View File

@ -2,11 +2,13 @@
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
#
# ToolBoX bring-up bench : compose hostapd + dnsmasq + nftables + portal.
# Idempotent. Usage: toolbox-up [--apply-only]
# ToolBoX bring-up bench : compose nftables + persistent AP iface IP.
# Idempotent.
set -euo pipefail
readonly TOML="/etc/secubox/toolbox.toml"
readonly NFT_OUT="/run/secubox/toolbox.nft"
readonly NET_OUT="/etc/systemd/network/10-secubox-toolbox-ap.network"
readonly TPL_DIR="/usr/lib/secubox/toolbox/conf"
log() { printf '[toolbox-up] %s\n' "$*" >&2; }
@ -16,7 +18,7 @@ if [ ! -f "$TOML" ]; then
fi
# Render nftables from Jinja2 template via Python
log "rendering nftables rules from /usr/lib/secubox/toolbox/conf/nft-toolbox.nft.j2"
log "rendering nftables rules from ${TPL_DIR}/nft-toolbox.nft.j2"
PYTHONPATH=/usr/lib/secubox/toolbox python3 - <<'PY' > "$NFT_OUT"
import tomllib, jinja2
from pathlib import Path
@ -25,9 +27,36 @@ env = jinja2.Environment(loader=jinja2.FileSystemLoader("/usr/lib/secubox/toolbo
print(env.get_template("nft-toolbox.nft.j2").render(**cfg))
PY
# Apply : delete old + load new
# Apply nft : delete old + load new
log "applying nftables (table inet toolbox)"
nft delete table inet toolbox 2>/dev/null || true
nft -f "$NFT_OUT"
log "OK : table inet toolbox active. Use 'nft list set inet toolbox validated_macs' to inspect."
# Render systemd-networkd file for the AP iface (persistent IP)
log "rendering ${NET_OUT}"
mkdir -p "$(dirname "$NET_OUT")"
PYTHONPATH=/usr/lib/secubox/toolbox python3 - <<'PY' > "$NET_OUT"
import tomllib, jinja2
from pathlib import Path
cfg = tomllib.loads(Path("/etc/secubox/toolbox.toml").read_text())
env = jinja2.Environment(loader=jinja2.FileSystemLoader("/usr/lib/secubox/toolbox/conf"))
print(env.get_template("ap-iface.network.j2").render(**cfg))
PY
# Reload networkd if available
if systemctl list-unit-files systemd-networkd.service >/dev/null 2>&1; then
if ! systemctl is-active --quiet systemd-networkd; then
log "enabling + starting systemd-networkd"
systemctl enable --now systemd-networkd 2>&1 | tail -1 || true
else
log "reloading systemd-networkd"
networkctl reload 2>&1 | tail -1 || systemctl reload systemd-networkd 2>&1 | tail -1 || true
fi
else
log "WARN : systemd-networkd not available — applying IP imperatively"
IFACE=$(python3 -c "import tomllib; print(tomllib.load(open('/etc/secubox/toolbox.toml','rb'))['ap']['iface'])")
GW=$(python3 -c "import tomllib; print(tomllib.load(open('/etc/secubox/toolbox.toml','rb'))['dhcp']['gateway'])")
ip addr replace "$GW/24" dev "$IFACE" 2>&1 | head -1 || true
fi
log "OK : table inet toolbox active + IP persistence via ${NET_OUT}"