secubox-core.service is a Type=oneshot (mkdir+chown) that RemainAfterExit=yes. A hard
Requires= on ~108 units cascade-stops them all if core is restarted/fails (e.g. a
secubox-core package upgrade) — a thundering-herd outage. After= keeps the ordering;
Wants= keeps the soft dependency without the cascade. Prereq for mass native apply
(Phase 3). Scaffolds (new-module.sh/new-package.sh) updated so future units use Wants=.
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
/login/mfa and /totp/confirm both hardcoded "ip": "" and omitted user_agent
entirely, while only the password path captured them. admin is forced-TOTP,
so EVERY real admin login took the MFA path — every session row landed
unauditable (who logged in, from where, with what), and the users webui
sessions tab rendered blanks for data it was already asking for.
Factor the extraction into _client_meta(request) (X-Forwarded-For first: nginx
and HAProxy front every login, so request.client.host is only ever the proxy)
and use it on all three paths.
Verified by driving the real /login/mfa handler in-process with
SECUBOX_AUTH_SESSIONS pointed at a temp file: the session row now records
ip=192.168.1.77 (the first XFF hop, not the proxy) and the full user-agent.
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
Sessions file defined as __SESSIONS_FILE but referenced as _SESSIONS_FILE in
every handler -> NameError on session save/load, so valid logins failed and
/status 500d. Normalised all refs to _SESSIONS_FILE. Verified: login endpoint
returns proper JSON (401 on bad creds), sessions persist.
Add /shared/hybrid-dark.css: a shared overlay that remaps the design tokens
(consumed by crt-light.css, sidebar-light.css and every module's inline styles)
to the cyan hybrid-dark palette from /certs/ + /wireguard/. body.hybrid-dark
(specificity 0,1,1) beats :root and inline :root, so one token remap flips the
whole tree — no per-panel rewrite. Each panel gains the overlay <link> + the
hybrid-dark body class (2-line additive swap; crt-light.css kept as base).
See .claude/WEBUI-PANEL-GUIDELINES.md.
Panels with fully bespoke styles (certs/wireguard/users/nac already hybrid;
~14 non-crt-light custom panels) are untouched.
secubox-auth forced every role=admin account without TOTP into enrollment on
login, returning no session — blocking operators who want password-only admin
login (e.g. a freshly reset admin on a satellite node).
Gate the forced enrollment on [auth] require_admin_totp (default true, secure/
CSPN). Set false in /etc/secubox/secubox.conf for password-only admin login.
Fail-secure: any get_config error keeps enrollment mandatory.
- packages/secubox-auth/api/main.py: config-gated enrollment branch
- secubox.conf.example: document require_admin_totp under [auth]
- tests: session-when-disabled + fail-secure-on-config-error
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
* perf(aggregator): async-sweep — 243 blocking route handlers async def→def (#738)
Mounted in the aggregator's single event loop, an 'async def' route handler
that runs blocking code (subprocess/journalctl/openssl/argon2) freezes the
WHOLE loop -> aggregator.sock Connection refused -> 502 board-wide.
Deterministic AST codemod (scripts/async-sweep.py) converts route handlers
that (a) are decorated with an HTTP verb, (b) contain a known blocking call,
(c) have NO await/async-with/async-for/yield, (d) are never used as a
coroutine elsewhere -> plain 'def'. Starlette then runs them in the AnyIO
threadpool, so the blocking call no longer stalls the gateway. await/stream/
websocket handlers are left untouched. Every file py_compile-checked.
243 handlers across 56 modules (system 17, qos 13, netdiag/hexo 12, hub 11...).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(aggregator): raise AnyIO threadpool to 80 tokens (#738)
The async-sweep moves ~243 blocking handlers to the threadpool. With ~110
modules in one process, the default 40-token pool can queue head-of-line under
concurrent blocking load. Raise to 80 on startup (best-effort, never breaks
boot).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(hub): double-buffer status cache + emojized health page (#738)
Navbar status (menu + health-batch) is now a strict double-buffer cache:
- request handlers NEVER compute on the request path — they return the current
snapshot instantly (or a 'warming' placeholder), so the sidebar's polling can
no longer serialize behind a ~3s systemctl walk and starve the loop;
- the background refresher is kicked from the request path (_ensure_bg) because
mounted sub-apps receive neither startup nor @app.middleware events under the
aggregator — the previous lazy-start middleware never fired there;
- snapshots are built complete then swapped atomically, so the dashboard never
shows partial/bad counts.
Served by the dedicated secubox-hub process (:8001, isolated loop) the navbar
stays <50ms and holds 200 under 25+ concurrent polls where the aggregator-
mounted copy wedged (000). health.js: 🟢🟡🔴 emoji status indicators.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style(hub): render health status emoji cleanly (neutralize .led dot) (#738)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* perf(sidebar): kill per-module health storm, use batch endpoint (#738)
The navbar refreshed LEDs by firing ONE /api/v1/<module>/health request per
module — ~119 requests every 30s, in batches of 8 — straight at the aggregator's
single shared event loop. Combined with the in-process module mount this is a
prime driver of the recurring board-wide 502 wedge (user-identified).
checkAllHealth + refreshStaleHealth now call /api/v1/hub/public/health-batch
ONCE (served by the dedicated, double-buffered hub process) and populate every
module's LED from that single response. 119 reqs/cycle -> 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Systemic clobber: the scaffold boilerplate (install -d -m 750 /var/lib/secubox,
/run/secubox) put restrictive modes on SHARED parents in ~56 module postinsts,
reverting them to 0750 on every install/upgrade and breaking traversal for
non-secubox daemons (kbin/toolbox 500). Empirically confirmed install -d -m only
modes the final component, so /parent/leaf forms are harmless — only bare-parent
targets were rewritten. Multi-arg lines (incl. ones making /var/lib world-writable
1777) split per-parent: /run/secubox=1777 root:root, /var/lib|cache|etc=0755
secubox:secubox; module-private leaves keep 0750. Scaffold + PATTERNS.md fixed so
new packages don't reintroduce it.
Admin login UI was permanently flagging "Clock not synced" with the
warning "TOTP window widened to ±60s" even when the system clock was
correctly synced. Cause : ntp_health.probe() only knew how to read
chrony, but SecuBox boxes ship systemd-timesyncd by default ; chrony
is the operator-opt-in alternative. On a stock install the probe
caught FileNotFoundError, returned `synced: False`, and the
recommended_totp_window() widened to ±60s on EVERY login.
Added a chrony → timedatectl fallback chain :
1. Try `chronyc -n tracking` (chrony users).
2. Fall through to `timedatectl show` reading NTP +
NTPSynchronized properties (timesyncd users).
3. Only return the synthetic error if BOTH commands are missing.
The response now carries an extra `source` field
("chrony" / "timedatectl") so the operator can see which backend
answered the probe.
Fixes "re-authentication everywhere despite being logged into the webui".
- secubox-core auth.py: require_jwt now accepts the parent-domain session
cookie (secubox_session) as well as the Bearer token (additive — existing
Bearer clients unchanged). Exposes set_session_cookie() publicly.
- secubox-auth main.py: emit the session cookie on every login-success path
(direct, post-MFA, post-TOTP-enrollment) so one SecuBox login (incl. TOTP)
drops a cookie that authenticates all module APIs without re-prompting.
Cookie is HttpOnly + Secure + SameSite=Lax (CSRF-mitigated). Set
api.sso_cookie_domain=".gk2.secubox.in" for cross-subdomain SSO.
Authelia vhost cutover (lyrion/zigbee) is a separate later step.
Hub + portal stayed `inactive (dead)` on real-hardware boot. The
journal error was `Failed to set up mount namespacing: /run/systemd/
unit-root/run/secubox: No such file or directory` at the NAMESPACE
step. Root cause: 96 services declare `RuntimeDirectory=secubox`
without the matching `RuntimeDirectoryPreserve=yes`. When any of them
stops (including any of the LXC-backed services that fail their
health probe and Restart=on-failure for a few cycles), systemd
removes /run/secubox on the way out. The next service with the same
RuntimeDirectory= that tries to namespace its inputs hits the gap
and falls into 226/NAMESPACE failure — Restart hammers a few times,
then the unit goes failed-permanent.
Earlier fmrelay + sentinelle units got the fix individually
(v2.12.0/v2.12.3 era). Now applied to all 96 remaining units in a
single sweep via:
sed -i '/^RuntimeDirectory=secubox/a RuntimeDirectoryPreserve=yes' "$f"
No version bumps in changelog — `dpkg -i --force-depends` in the
live-USB slipstream picks up the new .deb regardless of version
number.
The hub's `_compute_menu_sync()` drops menu entries lacking an `id`
field. 6+ packages (lyrion, yacy, zigbee, rustdesk, grafana, authelia)
shipped a different schema (`title/url/section/module`) so they NEVER
appeared in the navbar. Plus the existing CATEGORY_META declared 12
sections (dashboard/security/network/system/core/users/services/
privacy/monitoring/publishing/apps/admin), not the 6 SecuBox charter
modules.
Changes:
* All 121 packages/secubox-*/menu.d/*.json normalised to the canonical
schema (id, name, path, category, icon, order, description). Legacy
schema aliases (title/url/module/section) preserved as fallbacks then
dropped from the file.
* Each menu entry's category remapped to one of the 6 charter modules:
AUTH (auth/users/identity/zkp/nac/openclaw),
WALL (crowdsec/waf/mitmproxy/hardening/threat-* /cve-triage/...),
BOOT (kernel-build/eye-remote/master-link/droplet/cloner/backup/...),
MIND (ai-gateway/mcp-server/grafana/ndpid/netifyd/glances/...),
ROOT (system/hub/portal/console/admin/vault/vm/rtty/...),
MESH (wireguard/dns/tor/matrix/gitea/nextcloud/mail/lyrion/yacy/
zigbee/dns-provider/rustdesk/... — all network + comms apps).
* secubox-hub v1.4.0 — CATEGORY_META rewritten with the 6 charter
modules carrying their official color from DESIGN-CHARTER.md and
the complementary-pair order. DEFAULT_MENU fallback remapped too.
* secubox-zigbee v2.5.3 — www/zigbee/index.html rewritten with the
canonical SecuBox scaffold (body display:flex, sidebar 220px fixed,
.main reserving 48px for the global-menu-bar). MESH palette.
Browser side: operators need to clear localStorage sbx_menu_cache (or
hard-refresh after the 1h TTL) to see the new sections after deploy.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
- secubox-auth: new endpoint moved to /auth/status to avoid clashing with
the existing /auth/health liveness check used by the sidebar.
Distinct semantics: /health = "is the service up?",
/status = "is auth healthy? (NTP, identity store source)".
- login.html: renders the server-side QR PNG (data: URI) so the user
scans instead of copy-pasting. Manual entry stays available under a
collapsible details block.
- login.html: fetches /auth/status on load and surfaces a red banner when
NTP is desynced (with the widened TOTP window) or identity-store is in
fallback mode.
- Live-verified on gk2: /auth/status returns the correct shape
(chronyc absent on this board -> window=2 unknown branch, as expected).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
usersctl: parents[3] indexing crashed when run from /usr/sbin/usersctl
(production has only 2 parents). Wrapped path resolution in try/except
so the script still works in-tree AND when installed.
main.py: nginx strips /api/v1/auth/ prefix before forwarding to the auth
socket, so the canonical external URL /api/v1/auth/login was being
routed to FastAPI's /login — but _login_router was mounted under /auth,
producing 404. Now mounted under BOTH '' (canonical) and /auth (legacy
doubled-URL compat). Tests still pass under the /auth prefix.
Live verified on https://admin.gk2.secubox.in:
- empty password -> setup_token (200)
- wrong password -> 401 "Aucun mot de passe local"
- unknown user -> 401 "Identifiants incorrects" (same body, no enum)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove set_session_callback(_handle_session_event) from startup() — it was
overwriting the Task 13 _on_session_event registration (which uses jwt.jti)
with the legacy handler (which used secrets.token_hex(8)), breaking jti-keyed
session lookup in _session_validator.
- Fix comment on module-load set_session_callback registration (line 167).
- Add pytest.ini at worktree root: documents the api/ vs api/ namespace collision
that prevents combined cross-dir collection; enforces per-directory test runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PendingStore (JSON+TTL) for in-progress TOTP enrollment secrets
- Inject _users_api package loader (importlib) to side-step api/ name collision
- Register _session_validator, _on_session_event, _revoke_sessions callbacks
- POST /auth/login now branches: setup / mfa-challenge / totp-enroll / access
- POST /auth/login/mfa: verifies TOTP code or backup code, issues full JWT
- POST /auth/totp/enroll: generates secret, stores in PendingStore, returns URI
- POST /auth/totp/confirm: verifies code, calls engine.enroll_totp, returns JWT
- POST /auth/set-password: handles set-password scope and full-JWT change
- Mount _login_router before auth_router so /auth/login override wins
- Fix conftest sys.path order so secubox-auth/api takes priority for auth tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /multigadget skill covering autorun, storage, round UI, tooling
- Add Eye-Remote-Multigadget.md unified wiki hub
- Add Build-System.md to wiki with build documentation
- Add AI-BUILD-PROMPT.md for GPT/Gemini assistance
- Update sidebar and home page with new links
- Update WIP.md with #70 and #71 done
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Auto-generated health check endpoints for sidebar status:
- Returns {status: "ok", module: "name"}
- Public endpoint (no auth required)
- Used by sidebar.js for LED status display
Added via scripts/add-health-endpoints.py
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added session callback mechanism in secubox_core.auth
- Login events (success/failure) now emit to registered callback
- secubox-auth module records sessions in JSON file
- Fixed login.html endpoint URLs and JSON parsing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add LogsDirectory=secubox to systemd services for proper logging
- Fix systemd service security sandboxing (remove PrivateTmp issues)
- Replace text icons with emojis in menu.d JSON files
- Fixes navbar display issues (overlapping text from icon names)
Services updated:
- secubox-system, secubox-hub, secubox-portal, secubox-watchdog
- ~70 other services with LogsDirectory directive
Menu icons fixed:
- ipblock, interceptor, cookies, dns-provider, homeassistant, etc.
- Changed from text strings to emojis for proper sidebar display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add RuntimeDirectory=secubox to all services using ProtectSystem
- Change ProtectSystem=strict to ProtectSystem=full for compatibility
- Add systemd overrides in build script for cached packages
- Create tmpfiles.d entry for /run/secubox
This fixes the namespace issue where services couldn't create sockets
in /run/secubox due to ProtectSystem=strict mount namespacing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
secubox-repo:
- Add /scan-local endpoint to import local .deb packages
- Add /scan-local/preview for dry-run package discovery
- Fix success detection for repoctl ANSI output
- Fix _update_stats to use dict format for distributions
- Make summary endpoint handle both list and dict formats
secubox-iot-guard:
- Add Google Cast/Chromecast debug module
- Add /cast/devices endpoint to find Cast devices via ARP/mDNS
- Add /cast/diagnose/{ip} for full diagnostic (CrowdSec, Suricata, DNS, nftables)
- Add /cast/whitelist/{ip} to apply whitelist rules
- Add /cast/capture/{ip} for packet capture debugging
- Add /cast/config/nftables and /cast/config/unbound for config generation
- Support Google OUI detection and Cast domain whitelisting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Changed body class from "crt-body crt-scanlines" to "crt-light" on all pages
- Fixed portal menu to point to /portal/ instead of /c3box/
- Removed c3box/portal duplicate in menu
- All pages now start with light theme, sidebar.js handles theme switching
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Generate README.md for all 45 secubox-* packages with screenshots
- Add multilingual wiki pages (EN, FR, DE, ZH) documenting 47 modules
- Add generate-docs.py tool for documentation generation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix all 19 prerm scripts to only remove nginx config on actual
package removal, not during upgrades. This prevents API routing
failures after package updates.
- Fix uptime parsing in hub dashboard API - use int(float(...)) to
handle decimal values from /proc/uptime
- Fix portal login.html to store JWT token to localStorage (sbx_token
and secubox_token) for API authentication
- Fix hub logout function to clear both token names and redirect to
login page
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add /menu endpoint to secubox-hub for centralized menu management
- Create menu.d/*.json files for all 18 modules with category/order
- Update frontend sidebar to dynamically load menu from API
- Show module status (installed/active) with status dots
- Menu auto-discovers modules when packages are installed/removed
Categories: Dashboard, Security, Network, Monitoring, Publishing, Apps
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>