The v2.12.16 fix put the nft cache populator in firstboot.sh, which
broke the rule "cron lives next to the module that consumes it" — the
SOC firewall_summary endpoint is owned by secubox-hub, so the timer
that feeds its cache belongs in the same package.
This commit:
* removes the inline cache populator from image/firstboot.sh
* ships /usr/sbin/secubox-nft-cache + secubox-nft-cache.{service,timer}
inside the secubox-hub package, with the timer auto-enabled via the
timers.target.wants/ symlink
* adds a sudoers fragment authorising user secubox to run
`nft list *`, `nft -j list *`, and `systemctl --no-block start
secubox-nft-cache.service` (and only that one unit)
* rewrites the /firewall_summary endpoint so the cache is a speed
optimisation, not the source of truth: if the cache file is missing
or older than 60 s, fall back to a realtime `sudo nft list`,
surface `source: "realtime"` in the JSON, and nudge the cache
populator via systemctl so the next request lands on a hot cache
Per the operator directive: "les cache double buffer ne doivent pas
tomber et permettre juste d'aller plus vite... un echec de cron et de
cache doit forcer le cron et faire du realtime".
Operator screenshot: SOC dashboard "🔥 FIREWALL nftables / Status
INACTIVE / Tables 7 / Chains 10 / Rules 19" with all counters at 0
(Accept/CAPI/CrowdSec/Manual).
Two bugs found:
1. The /api/v1/hub/public/firewall_summary endpoint docstring says
it reads cache files `/var/cache/secubox/nft-*.txt` "updated by
cron every 30s" — but the cron never existed in the repo. The
table/chain/rule counts were getting populated somehow (probably
a one-shot at firstboot) while the counters file stayed empty,
hence 7/10/19 visible but all hit-counts zero.
2. The endpoint never returned a `status` field, so the React
bundle (secubox-soc-web) defaulted to "INACTIVE" regardless of
whether nftables.service was active or rules were loaded.
Fixes:
* firstboot.sh installs `/usr/local/bin/secubox-nft-cache` +
`secubox-nft-cache.service` + `.timer` (oneshot, every 30s,
OnBootSec=10s). Runs as root via systemd so it can call
`nft -j list ruleset` + `nft list counters`. Writes the JSON
ruleset + counters dump + a `.lastrun` sentinel into
/var/cache/secubox/.
* hub `/firewall_summary` endpoint now:
- calls `systemctl is-active nftables.service` and returns a
`status` field ("active" | "inactive" | "active-manual" | "error")
- "active-manual" = systemd reports inactive but the kernel has
rules loaded (happens when firstboot.sh did `nft -f` directly
and the systemd unit didn't get re-enabled). Treat as active
for dashboard purposes — operator wants to see "firewall is up"
when there ARE rules.
- returns `cache_last_run` so the dashboard can warn on stale
cache.
WAF metrics empty is by design on a live USB without mitmproxy LXC
(THREATS_LOG never gets written → zero counters). Not addressed
here. Operator runs install-lxc.sh once to enable WAF traffic.
v2.12.11's rule `udp sport 67 udp dport 68 accept` was invalid nft
syntax — protocol prefix can't be repeated inside a single rule, the
firstboot.sh-generated /etc/nftables.conf failed to load and the
firewall stayed in its previous state (or empty), defeating the
whole point of the DHCP fix.
Just match the destination port: `udp dport 68 accept`. DHCPOFFER /
DHCPACK from any server (sport 67) lands on dport 68 of the client,
so the inbound match is sufficient and unambiguous.
Live-system workaround (no rebuild needed):
nft add rule inet secubox_filter input udp dport 68 accept
Bare-metal box ended up with 192.168.10.250 (assigned by secubox-net-
fallback's ARP-probe loop) instead of the real DHCP lease from the
LAN router. Operator confirmed DHCP server is healthy on the LAN
(dev box on same switch got 192.168.1.13 via DHCP without issue).
Root cause: firstboot.sh writes /etc/nftables.conf with policy=drop
on the input chain and no explicit DHCP allowance. The `ct state
established,related accept` rule does NOT cover DHCP because the
client request leaves from 0.0.0.0:68 and the server reply comes back
broadcast (or unicast direct to the offered IP before it's bound).
Neither matches the original 5-tuple → conntrack treats the reply
as a new packet → policy drop → DHCPOFFER lost → networkd times out.
secubox-net-fallback then picks a random gateway from its probe
list and lands the box on the wrong subnet.
Add `udp sport 67 udp dport 68 accept` to the input chain. This
matches DHCPv4 server-to-client replies specifically (port 67/68
are reserved BOOTP/DHCP). DHCPv6 would need a sibling `udp dport
546 accept` rule but is out of scope here.
Two bugs surfaced on the v2.12.0 amd64 live USB build (user-tested on
both VirtualBox + bare-metal amd64 2026-05-24):
1. **IP**: the static fallback added to board/x64-live/netplan/
00-secubox.yaml in commit 264e23e4 never reached the booted image
because image/build-live-usb.sh writes its OWN inline netplan
template (line 782, here-doc) that's a copy of the board file
without the static block. Both DHCP-only matches `en*` and `eth*`
now also carry `addresses: [192.168.1.55/24]` + gw 192.168.1.254 +
nameservers, with a high route metric (1000) so DHCP still wins
when available — but the appliance stays reachable on .55 when it
doesn't (the original #370 intent).
2. **Kiosk**: build-live-usb.sh touches /var/lib/secubox/.kiosk-
enabled + symlinks secubox-kiosk.service into multi-user.target.
wants at build time, but operators reported having to run
`secubox-kiosk-setup enable` manually on first boot. Suspected
cause is a live-boot persistence layer that masks parts of /var,
needs more digging. As a safety net firstboot.sh now invokes
`secubox-kiosk-setup enable --x11` whenever it can't find the
sentinel — idempotent, no-op on a working image, recovers a
broken one without needing console intervention.
Two regressions on a --static-ip image:
1. Default match `name: "eth0"` matched nothing on modern Debian. The
booted kiosk ended up on `enp0s31f6` with link-local 169.254.x.x
because the static stanza was inert.
Fix: default STATIC_IFACE="en*" (covers enp*/eno*/ens*/enx*).
2. firstboot.sh ran `secubox-net-detect apply router` unconditionally
if the binary was present, even though build-live-usb.sh's
--static-ip branch pre-touches /var/lib/secubox/.net-configured to
short-circuit the systemd unit. That re-injected the router-mode
bootstrap (WAN + br-lan 192.168.1.1/24), colliding with the user's
gateway.
Fix: gate the firstboot net-detect block on the marker file.
Also clarify --help wording about glob semantics.
Closes#128.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Fresh images had admin / secubox login always failing because
image/firstboot.sh wrote users.json with:
- Flat schema {"admin": {...}} instead of v2 {"users": [{...}]}
that secubox_core.user_store expects.
- SHA256 hash instead of argon2 that verify_password uses.
- And no /etc/secubox/auth.toml so the fallback path was also dead.
End result: locked out of every fresh image at the web UI; only root /
secubox shell login worked (unrelated chpasswd path earlier in firstboot).
Fix: replace the broken sha256sum + flat-dict heredoc with an inline
Python that uses argon2.PasswordHasher (already a secubox-core dep) and
writes the v2 schema directly. must_change_password is true so the very
first successful login is gated on a real password change — bypassing
password_policy.validate() (which would reject "secubox" as too weak)
is acceptable for the seed because the next interaction forces the upgrade.
Verified roundtrip locally:
ph.hash("secubox") -> argon2id$v=19$...
ph.verify(h, "secubox") -> True
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add 5 USB gadget modes: Normal, Flash, Debug, TTY, Auth
- Add HID keyboard emulation for U-Boot automation
- Add FIDO2/U2F security key mode (Eye Remote)
- Add x64/amd64 live boot support
- Update README.md with Eye Remote documentation
- Update WIKI.md with mode mockups and technical details
- Add INFOGRAPHIC-PROMPT.md for Claude.ai image generation
- Bump version from 1.7.0 to 1.8.0
New files:
- secubox-hid-keyboard.sh: Virtual HID keyboard driver
- INFOGRAPHIC-PROMPT.md: 7 prompts for publicity infographics
Eye Remote transforms the Round UI from a status display
into a full remote control device with debugging, flashing,
and authentication capabilities.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create /etc/secubox/users.json with admin user on firstboot
- Default password: secubox (instead of random)
- SHA256 hash password same as portal expects
- Hide Chromium --ignore-certificate-errors warning with --test-type
Credentials: admin / secubox
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Network detection (secubox-net-detect):
- Board detection: MochaBin, ESPRESSObin v7/Ultra, x64 VM/baremetal
- Interface mapping based on device model (eth0=WAN, lan*=LAN)
- Netplan generation for router/bridge/single modes
- Link detection for x64 auto-discovery
Kernel cmdline handler:
- secubox.netmode=router|bridge|single for boot-time network mode
- secubox.kiosk=1 for GUI kiosk mode
- secubox.debug=1 for debug mode
Kiosk mode (secubox-kiosk-setup):
- Minimal GUI: Cage Wayland + Chromium fullscreen
- Displays SecuBox WebUI on connected screen
- Commands: install, enable, disable, status
Board configs:
- board/x64-live/ for Live USB
- board/x64-vm/ for VM environments
Live USB updates:
- GRUB entries for Kiosk Mode and Bridge Mode
- Installs all detection scripts and services
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Includes all package APIs with public dashboard endpoints:
- secubox-system: System Hub with /info, /resources, /security
- secubox-crowdsec: CrowdSec dashboard with /status, /hub, /metrics, actions
- secubox-wireguard: WireGuard VPN with /interfaces, /peers, start/stop
- secubox-netdata: Monitoring with /stats, /processes, /alerts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>