Root cause of 'surf stops when wg-toolbox VPN activated' after a reboot: the
persistent nft drop-in (loaded atomically by nftables.service via
/etc/nftables.conf include) failed to load, so the wg-toolbox DNAT never
existed after boot and all tunnel traffic was blackholed.
Two boot-time aborts, both fixed:
- referenced 'inet filter input' which does not exist on gk2 at load time →
fold the UDP-51820 handshake accept into the wg-toolbox table's own input
base chain (self-contained, no external table dependency)
- used iif/oif "wg-toolbox" (resolves an interface index at LOAD time, fails
when wg-quick@wg-toolbox has not created the iface yet) → iifname/oifname
(per-packet name match, load-order independent)
nft -c -f /etc/nftables.conf now passes; the DNAT survives reboot.
- _parse_wg_show: parse all-dump by field count (5=iface/9=peer), drop private_key
(sudo route had made it leak 48 bits via /stats,/summary,/peers/status);
also restores peer parsing (old detection misclassified every peer line)
- webui: data-attr + delegated click handlers; guard peer-drawer loading flicker
- api: clear read cache after up/down/add/remove so mutations show immediately
Add a "LAN clients (from NAC)" subsection to the Tor-VPN selective-routing
panel in both /tor/ and /toolbox/#tor webuis. Lists NAC clients that are
online and not quarantined, cross-referenced against the current
/toolbox/vpn/clients selectors to derive each toggle's on/off state.
Toggling calls the existing POST/DELETE /toolbox/vpn/client {kind:"ip"}
endpoints and refreshes both lists. IPv6 client IPs are filtered out
client-side (VPN backend is v4-only, Phase 1). Existing free-text
add-selector control (MAC/CIDR/off-NAC clients) is unchanged.
Swap the standalone MESH-blue charter in reality.html for the fleet
crt-light/P31-phosphor skin (Courier Prime, --p31-* palette,
.main{margin-left:220px}) matching secubox-tor and other webuis, and
replace the bespoke breadcrumb sidebar with the shared sidebar.js/
sidebar-light.css injector so /reality/ appears in the common navbar.
All panels (server list+create, validate-target, apply, clients+link
QR, volume-guard config+stats) and their JS/fetch wiring are unchanged.
Add menu.d/562-reality.json (mesh category) and install it via
debian/rules, mirroring secubox-tor. python3-segno was already
declared in debian/control.
APIRouter(prefix="/api/mesh/reality", dependencies=[require_jwt]) —
router-level JWT gate on every route. Servers/clients CRUD,
validate-target, apply (force-overridable sanity gate), link (vless://
+ QR), guard get/put, stats. All subprocess-touching handlers are
plain def so FastAPI threadpools them off the event loop. main.py adds
the uvicorn ASGI app target (secubox_reality.main:app) — not in the
original file list but required to serve the router.
xray api statsquery via loopback StatsService, samples stored in
SQLite; per-window delta = max-min across samples (cumulative
counters). Soft warn / hard disable (stop-not-destroy). CLI entry
point for the 5-min systemd timer.
Plain-def systemctl wrappers (start/stop/enable/disable/restart/status)
for the hardened templated unit; stop_instance() never disables/destroys
so the volume-guard DISABLE action preserves IP/keys.
Mirror the toolbox #tor tab's five control panels (exit-country, Tor-VPN
clients w/ IPv6 warning, obfs4 bridges, emancipate-to-.onion, hidden
services + .onion-DNS status) onto secubox-tor's own /tor/ dashboard,
wired to the same cross-module endpoints via /api/v1/{toolbox,exposure,tor}.
All onclick="fn(...)" handlers replaced with data-attrs + delegated
listeners; every backend-derived string is esc()'d before landing in
innerHTML.
Fix _discover_hidden_services(): secubox-exposure's emancipate flow
creates hidden services under /var/lib/tor/hidden_services/<name>/hostname
(its own TOR_DATA constant), not directly under /var/lib/tor/<name>/hostname
like this module's legacy add_hidden_service(). Discovery now scans both
layouts (still fail-safe, still de-duplicated by name), so GET
/hidden_services surfaces .onions regardless of which flow created them.
Also grants secubox-tor.service SupplementaryGroups=debian-tor so the
process can traverse the debian-tor-owned hidden-service directories once
the board operator grants group-read/traverse bits (documented in the
unit's comment + PR description — this alone isn't sufficient, the parent
dirs still need their permission bits relaxed operator-side).
FINAL-REVIEW fix wave for tor-enhancement-phase1:
- Critical 1: postinst pre-creates + chowns tor-exit-country.txt,
tor-vpn-clients.txt, tor-bridges.txt under the 0750 root:secubox-toolbox
/etc/secubox/toolbox dir, mirroring the filters.json pattern, so the
first CRUD write on a fresh install no longer 500s with PermissionError.
- Critical 2: tor_reconcile_persist() now re-applies the torrc
HiddenService* stanza whenever the key dir survives but the stanza is
missing (torrc reset), not just when the key dir is absent, so a torrc
reset no longer leaves a .onion permanently dark. Added
_torrc_has_stanza() + repair-path tests.
- Packaging: debian/rules now installs
sbin/secubox-exposure-tor-reconcile and the matching systemd unit;
postinst enables the unit, so boot-time persist actually runs.
- Audit: emancipate_webui() now writes a CSPN audit-log entry (operator,
action, resulting onion) via a new _audit_exposure_action() helper
shared with the existing _audit_exposure() vhost-reach audit path.
- Confirmed emancipate_webui() already records local_port=9080 (not 80).
- _discover_hidden_services() globs TOR_DATA for every */hostname (not just
config-listed ones), cross-referencing secubox config for local_port/
virtual_port/enabled where a matching entry exists. Fail-safe: missing
TOR_DATA, broken config, or unreadable hostname files degrade to empty
entries, never raise.
- GET /hidden_services now surfaces every on-disk hidden service via
_discover_hidden_services(), including orphaned/hand-created ones.
- GET /onion_dns reports {dnsport_up, forward_zone_installed, resolves}:
dnsport_up via a local-only /proc/net/{tcp,udp}[6] scan (no network I/O,
never hangs), forward_zone_installed via checking the unbound drop-in,
resolves via a tightly bounded (1.5s) UDP canary query only attempted when
both prerequisites hold.