haproxyctl: honour an opt-in `waf_bypass = true` field per vhost so the
generator emits `use_backend <backend>` (nginx_vhosts) instead of forcing
mitmproxy_inspector under waf_enabled. Makes the WAF-bypass of media/large-
upload services (nc/photoprism/matrix/torrent) declarative, auditable and
regen-durable instead of fragile hand-edits of the generated haproxy.cfg.
Normal vhosts are unaffected (verified in isolation).
secubox-torrent postinst: register torrent.<host>.<suffix> in haproxy.toml
(backend=nginx_vhosts, waf_bypass=true, ssl=true) idempotently and run a
drift-guarded `haproxyctl generate` — which refuses safely on a board that
still has un-migrated hand-edit vhosts (live cfg untouched) and succeeds on a
fresh board. Torrent bypasses sbxwaf by design, so no sbxwaf route is needed.
CLAUDE.md: WAF-bypass section updated — default stays 'never bypass'; the ONLY
sanctioned exception is the declarative waf_bypass=true in haproxy.toml, never
a hand-edit of the generated cfg.
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
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>
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.
* 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.
haproxyctl generate exited 1 producing no backends: set -e + && {} vhost-loop
chains aborted on the first non-SSL vhost, and a duplicate mitmproxy_inspector
(auto + user TOML) was fatal. Converted chains to if/then/fi, dedup user
backends. Added a drift guard: refuse to install a cfg with fewer vhosts/
backends than live, so a successful regen can't silently drop hand-maintained
vhosts (kbin/gitea/matrix/...) absent from haproxy.toml.
502/503/504 poll the URL and auto-reload on backend recovery (live status +
manual retry); 400/403/408/500 branded static. haproxyctl now emits errorfile
directives (durable across regen) + retries/redispatch in defaults. Pages shipped
to /etc/haproxy/errors/.
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.
Recurring "broken-by-vhost-add" bug: every haproxyctl vhost add ran
cmd_generate which wiped operator-added backends (nginx_vhosts,
gitea_ssh, webui-lan, …) and emitted the wrong cert path.
cmd_generate now:
- Writes to a tempfile, validates haproxy -c -f, then atomically
installs via install(1). Live cfg is LEFT UNTOUCHED on failure.
- DATA_PATH defaults to /data/haproxy (charter), /srv/haproxy
fallback. HAPROXY_DATA_PATH env override.
- Always emits mitmproxy_inspector + nginx_vhosts + webui_direct
regardless of waf_enabled.
- Reads /etc/haproxy/cfg.d/*.cfg as operator extras and appends them
verbatim — webui-lan, gitea-ssh etc. survive regens.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
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>
Five package builds failed on v2.9.0 first attempt, and the release
pipeline cascaded to skip all downstream jobs (collect/publish/images/
live-usb/create-release) because GH Actions treats *any* matrix failure
as a global failure for `needs:` purposes. This commit fixes both
root causes:
Package fixes:
- secubox-eye-square: drop debian/compat (conflicted with control's
`Build-Depends: debhelper-compat (= 13)`)
- secubox-defaults: same — drop debian/compat
- secubox-metoblizer: switch from legacy `Build-Depends: debhelper
(>= 13)` (which then requires a debian/compat file) to the modern
`Build-Depends: debhelper-compat (= 13)` virtual package
- secubox-haproxy: override dh_usrlocal — it can only rehome /usr/local
directories (Policy 9.1.2), not the individual admin tools we
deliberately drop there per issue #44
Pipeline resilience:
- build-packages.yml `collect` job: `if: always() && != cancelled` so we
bundle whatever .deb files the matrix produced even when entries failed
- build-packages.yml `publish` job: predicate now reads collect's success
rather than the matrix's overall conclusion
- release.yml `build-images`, `build-live-usb`, `publish`,
`create-release`: all gain `if: always() && needs.build-packages.result
!= 'cancelled'` so a partial build matrix doesn't black-hole the
release. `create-release` already gracefully skips its
`if: needs.build-images.result == 'success'` download steps when
images failed, so the partial release ships what's available.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Under set -euo pipefail, `if ! nginx -t 2>&1 | grep -q "syntax is ok"`
was flaky: when nginx -t emits dozens of warnings before the success
line, the grep pipeline doesn't see the match reliably. Replaced with
a direct exit-code check (nginx -t returns 0 on success even with
warnings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The endpoint is called by the secubox-render-nginx-webui script via the
secubox-haproxy unix socket. The script has no JWT token at hand (it runs
from postinst triggers as root). Requiring JWT broke the trigger-driven
render flow.
Rationale for making the endpoint public:
- The content is fully derivable from /webui/admin-domain (already public).
- Unix-socket access is root-only at the filesystem level.
- The rendered vhost contains no secrets.
Test updated: test_nginx_config_requires_jwt → test_nginx_config_is_public.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(spec+plan): WebUI obfuscation design and implementation plan (ref #44)
Spec: docs/superpowers/specs/2026-05-12-webui-obfuscation-design.md
Plan: docs/superpowers/plans/2026-05-12-webui-obfuscation.md
The design locks the SecuBox WebUI to admin.<HOSTNAME>.<DOMAIN_SUFFIX>
via strict-regex ACLs in HAProxy AND nginx, both driven by
/etc/default/secubox as the single source of truth. New package
secubox-defaults ships the env file; secubox-haproxy API exposes
/webui/admin-domain (info), /webui/nginx-config (JWT, rendered vhost),
/webui/refresh (JWT, cache invalidate). A safe renderer script writes
the nginx vhost atomically; haproxyctl + Python /generate both inject
the strict ACL at the top of http-in and https-in frontends.
Plan has 15 tasks with TDD steps, exact file paths, and complete code
in every step (no TBD/TODO). Acceptance criteria + integration test
script included.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(secubox-defaults): scaffold package skeleton (ref #44)
* feat(secubox-defaults): ship /etc/default/secubox env file (ref #44)
* feat(secubox-defaults): postinst autodetect + dpkg-trigger (ref #44)
* feat(secubox-haproxy): webui_identity helper + tests (ref #44)
Implement webui_identity module that parses /etc/default/secubox
and exposes canonical admin URL + regex pattern for HAProxy acl.
New module:
- api/webui_identity.py: Parses SECUBOX_HOSTNAME and SECUBOX_DOMAIN_SUFFIX
with LRU cache and invalidation support.
Tests (TDD):
- tests/test_webui_identity.py: 5 test cases covering basic parsing,
missing hostname validation, custom suffix handling, comment handling,
and cache invalidation.
- tests/conftest.py: sys.path bootstrap for api/ imports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(webui_identity): catch OSError on defaults read + test (ref #44)
* feat(secubox-haproxy): GET /webui/admin-domain endpoint (ref #44)
* feat(secubox-haproxy): GET /webui/nginx-config rendered vhost (ref #44)
* feat(secubox-haproxy): POST /webui/refresh cache invalidation (ref #44)
* feat(secubox-haproxy): secubox-render-nginx-webui safe renderer (ref #44)
* feat(haproxyctl): inject strict-regex WebUI ACL at frontend top (ref #44)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(secubox-haproxy): /generate symmetry with strict WebUI ACL (ref #44)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(secubox-haproxy): dpkg-trigger refresh on secubox-defaults-changed (ref #44)
* build(secubox-haproxy): depend on secubox-defaults (ref #44)
* test(infra): integration test for WebUI obfuscation (ref #44)
* docs: HISTORY Session 159 — WebUI obfuscation (ref #44)
Records the complete WebUI obfuscation work: new secubox-defaults
package, three /webui/* endpoints, nginx renderer script, haproxyctl
+ Python generator symmetry, dpkg trigger plumbing, integration test.
Includes notes on the brainstorming → spec → plan → subagent-driven
execution flow and the 6 fix iterations caught by reviewers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Triggered by cpf.gk2.secubox.in returning "Wrong Domain". Root cause: the
secubox-haproxy generators (both bash haproxyctl and Python FastAPI) emit
config rules that point to dead/wrong backends, leading to HTTPS-wide
metablog/streamlit outages when a regen runs.
Generator fixes:
- packages/secubox-haproxy/api/main.py: use_backend waf_inspector
→ use_backend mitmproxy_inspector (2 occurrences). waf_inspector
pointed to 127.0.0.1:8890 which is not listening; mitmproxy_inspector
is the actual healthy WAF backend at 10.100.0.60:8080.
- packages/secubox-haproxy/sbin/haproxyctl: default_backend fallback
→ default_backend mitmproxy_inspector (2 occurrences). The fallback
backend deny_status 503's unknown hosts; switching to mitmproxy_inspector
lets mitmproxy dispatch all hosts via its routes JSON (245 routes vs
93 declared in haproxy.toml).
New safe regen wrapper:
- scripts/secubox-haproxy-regen-safe: snapshot → regen via haproxyctl →
validate via haproxy -c -f → atomic swap → reload. On validation
failure, current cfg is preserved and the broken candidate is saved
for forensics. Prevents another silent breakage like Session 156.
Board-side state (not in repo): /etc/haproxy/haproxy.cfg patched in
place to mirror these fixes, secubox-haproxy.service stopped until
user confirms safe to re-enable. All 245 routes verified live (cpf,
arm, lldh, admin, pub, werdl, 3d, 42, zkp all HTTP 200 with correct
content).
See .claude/HISTORY.md Session 156 for full symptom chain and the
6-layer root cause analysis.
Co-Authored-By: Claude Opus 4.7 (1M context) <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>
- Add hybrid-skin.css: Glass Morphism + Matrix Terminal universal theme
- Update sidebar.js v2.23.0:
- Remove light/dark theme switching (single hybrid theme)
- Add global status bar (bottom) with health metrics
- Add global menu bar (top) replacing individual page headers
- Load health data from /api/v1/hub/health endpoint
- Update sidebar.css: Transparent header/footer to fix white corners
- Update design-tokens.css: Add .hybrid-skin to dark theme selector
- Update index.html files: Apply hybrid-skin body class
- Add SOC dashboard (soc/index.html): Reference implementation
- Add Health Doctor Pattern documentation
- Update module APIs with /health endpoints
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CrowdSec Dashboard:
- Update alerts router to use cscli instead of LAPI (bouncer key lacks alerts access)
- Fix alerts display to use source_ip and country fields
- Add loadMachines() function for machines count
- Update version display parsing
WAF Dashboard:
- Fix _get_bans() to flatten nested CrowdSec decision data
- Add country, ASN columns to bans table
- Update bans API to include metadata (country, asn, asn_org)
Config fixes on MOCHAbin (manual):
- Add lapi_key to /etc/secubox/secubox.conf
- Create empty /etc/crowdsec/online_api_credentials.yaml
- Add NoNewPrivileges=false drop-in for sudo access
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix _cfg() and _compute_status_sync() to read from cfg["main"]
- TOML structure uses [main] section for waf_enabled, ports, etc.
- Fixes waf_enabled showing false when TOML has true
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add _get_haproxy_config() to load /etc/secubox/haproxy.toml directly
using tomllib, bypassing broken secubox_core.config.get_config()
- Replace all get_config("haproxy") calls with _get_haproxy_config()
- Fix duplicate id="vhosts" in HTML (stat card vs tab content)
- Rename stat card IDs: vhosts -> vhosts-count, backends -> backends-count
- Update JavaScript loadStatus() to use correct element IDs
This fixes the dashboard showing 0 vhosts and empty VHOSTS tab.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added missing CSS for .form-group, .form-row, .form-check classes
- Removed unused toggleVhostWAF() function (WAF toggle now in edit modal)
- Proper styling for form elements with P31 Phosphor theme
Implement background refresh caching pattern for instant API responses:
CrowdSec (status.py):
- Add status, metrics, hub caches with 30s refresh
- Parallel computation using ThreadPoolExecutor
- File persistence for fast startup
- Before: 1800ms → After: 45ms (40x faster)
HAProxy (main.py):
- Add status cache with 30s refresh
- Integrate with existing health monitor
- Before: 353ms → After: 50ms (7x faster)
Users (main.py):
- Add status cache with 60s refresh
- Fallback to file-based user count
- Before: 1906ms → After: 37ms (51x faster)
All modules now respond in <50ms for status endpoints.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- postinst creates /etc/haproxy if haproxy package not installed
- Remove RuntimeDirectory=haproxy to avoid NAMESPACE errors
- Fixes systemd namespace setup failure when haproxy not present
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move directory creation from import-time to startup event
- Add try/except to handle permission errors gracefully
- Create haproxy data directories in postinst
- Increase RestartSec from 5s to 30s
- Add StartLimitBurst to prevent rapid restart spam
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- X11 setup now exits early for bare metal (VM_TYPE=none) without
creating config files - Intel/AMD/NVIDIA auto-detect perfectly
- Remove static fallback 10-modesetting.conf that caused conflicts
- Sanitize GPU_INFO in config comments to remove special chars
- Disable PrivateTmp and ProtectSystem for haproxy, metrics, threats
services - causes Python symlink namespace errors on some kernels
- Add fix-namespace-errors.sh script for quick fixes on running systems
Fixes: X11 "no screens found" on Intel HD Graphics 630
Fixes: "Failed to set up mount namespacing" for Python services
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>