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.
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.
Third batch after lyrion/zigbee/authelia (commit 54da8a7c). Same anti-
pattern across all three: nginx `/<module>/` was proxy_pass'ing to the
LXC, which silently broke any absolute asset URL the upstream app
served. Each module gets the same surgical fix:
- nginx/<module>.conf — /<module>/ is now `alias /usr/share/secubox/
www/<module>/` (SecuBox admin static page) instead of `proxy_pass`.
Both /api/v1/<module>/ and /<module>/ gated by `auth_request
/__sbx_auth_verify` so SSO covers admin access.
- <module>ctl — config_get strips TOML inline comments;
emit_access_json now returns the REAL upstream URLs (no /<module>/
admin suffix); PUBLIC_HOSTNAME defaults to <module>.gk2.secubox.in.
grafanactl also gets the `""|""` -> `""|list` case fix.
- www/<module>/index.html — "native UI" button reads its href from
/api/v1/<module>/access at runtime (single source of truth) and
opens in a new tab.
NEW dedicated vhost files for grafana and rustdesk (yacy already had
one): packages/{grafana,rustdesk}/nginx/<module>-vhost.conf. Both
listen 0.0.0.0:9080 server_name <module>.gk2.secubox.in, Authelia-
gated, serving the upstream app at root. Operator prerequisites (DNS
+ TLS cert + HAProxy ACL + mitmproxy route inside its LXC) documented
in each vhost header.
Side note: grafana's LMS-style absolute-path problem requires
`serve_from_sub_path = false` + `root_url = https://grafana.gk2.
secubox.in/` in /etc/grafana/grafana.ini inside the LXC. This is
flagged in the changelog as a follow-up for install-lxc.sh; for now
the live LXC config is unchanged.
Live verified on gk2 — all six modules (lyrion + zigbee + authelia +
yacy + grafana + rustdesk) now return the same /access shape:
lan http://<lxc_ip>:<port>/ (lan-only)
public https://<module>.gk2.secubox.in/ (Authelia SSO)
Two intertwined bugs caused the hub sidebar to show a duplicate Lyrion
row and several blank module icons:
1. packages/secubox-mqtt/menu.d/80-mqtt.json had been overwritten with
the Lyrion JSON (subtitle, icon, id="lyrion", path="/lyrion/", all
wrong for the MQTT package). The hub merged both entries and showed
Lyrion twice. Restored the file to a proper MQTT broker entry —
id="mqtt", path="/mqtt/", icon="📡", category="mesh".
2. lyrion / grafana / rustdesk / yacy / zigbee menu entries used
FontAwesome class names (fa-music, fa-chart-line, fa-desktop,
fa-search, fa-microchip) but the sidebar renders item.icon as a raw
text node — there's no FontAwesome stylesheet loaded. The literal
strings rendered as plain text, looking blank or like garbage to
the user. Swapped each for the closest emoji (🎵📈🖥️🔍🐝).
3. PAGE_ICONS in www/shared/sidebar.js had no entry for /sentinelle/,
/lyrion/, /grafana/, /zigbee/, /yacy/, /rustdesk/, /mqtt/ — so the
top page-icon badge fell back to the 📦 parcel for those routes.
Added the seven entries.
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>
Field-deploying v2.11.0's three new LXC modules on arm64 MOCHAbin
(gk2 192.168.1.200) surfaced nine bugs that the local smoke build
doesn't exercise (no lxc-create in dpkg-buildpackage). All three
modules now reach overall=green on the board with the canonical hub
vhost (admin.gk2.secubox.in) routing /api/v1/<m>/* and /<m>/ correctly.
install-lxc.sh (all 3 modules)
1. lxc-create -t download (the legacy `debian` template fails on
bookworm unprivileged containers).
2. ensure_masquerade() — nftables MASQUERADE rule for 10.100.0.0/24,
idempotent. systemd-networkd's IPMasquerade=ipv4 doesn't land on
appliances using ifupdown/NetworkManager.
3. ensure_resolv() — seed /etc/resolv.conf via lxc-attach AFTER
start_lxc + wait_for_network (rootfs is owned by mapped uid 100000,
host root can't write directly). Unlink the symlink the download
template ships (→ /run/systemd/resolve/stub-resolv.conf which
doesn't exist before systemd-resolved is up).
install-lxc.sh (grafana)
4. gpg --dearmor on apt.grafana.com key (signed-by= needs binary,
wget delivers ASCII-armored).
install-lxc.sh (yacy)
5. YACY_RELEASE_URL: v1.940 → v1.941 (old URL now returns 404).
install-lxc.sh (rustdesk)
6. Detect host arch (arm64/amd64/armhf) and pull the matching
RustDesk server build. Hardcoded amd64 binary failed with
"Exec format error" on the arm64 MOCHAbin.
7. Flatten any of amd64/, arm64v8/, armv7/ subdirs the zip ships.
debian/secubox-<m>.service (all 3 modules)
8. ExecStartPre prefix with `+` (run as root). The mkdir + chown
pre-steps run as User=secubox by default, chown fails ("Operation
not permitted") and the service can't start.
debian/rules + nginx/<m>.conf (all 3 modules)
9. Install nginx snippet to BOTH /etc/nginx/secubox.d/ AND
/etc/nginx/secubox-routes.d/. The canonical hub vhost
(sites-enabled/webui.conf serving admin.gk2.secubox.in) only
includes routes.d/, NOT secubox.d/ which is only included by the
legacy sites-available/secubox vhost. Also switch the
/api/v1/<m>/ location from `proxy_pass http://unix:.../sock:/` to
explicit `rewrite ^/api/v1/<m>/(.*)$ /$1 break; proxy_pass
http://unix:.../sock;` — matches the canonical pattern used by
secubox-users; the previous syntax did not strip the prefix so
FastAPI saw the full path and returned 404 on every route.
Docs
* docs/MODULE-GUIDELINES.md §7 extended with mandatory lifecycle
verbs install / repair / wizard / reload / uninstall. Repair detects
+ fixes drift idempotently; wizard chains interactive seed + install;
uninstall asks for confirmation, removes LXC + state + secrets.
Implementation in grafanactl/yacyctl/rustdeskctl deferred to v2.12.0
(currently install + reload are wired, the rest is on the v2.12.0
backlog).
* docs/superpowers/specs/2026-05-20-secubox-wall-ep06.md (#236) —
Quectel EP06-E modem-based rogue-base-station sensor (WALL layer).
* docs/superpowers/specs/2026-05-20-secubox-sentinelle-gsm.md (#237) —
RTL-SDR + gr-gsm passive RX-only false-BTS detection (MIND layer,
feeds WALL/OPAD). Privacy-by-design HMAC-truncated identifiers.
* .claude/{WIP,TODO}.md refreshed with v2.11.1 fix trail + v2.12.0
target + P2 sensor backlog.
Package version bump: 1.0.0 → 1.0.7 (grafana/rustdesk) / 1.0.7 (yacy),
all three end at the same .deb revision suitable for v2.11.1 tag.
Validated end-to-end on board gk2:
grafanactl status → overall=green (lxc + grafana-server + host-api all up,
admin/<password> at /etc/secubox/secrets/grafana-admin,
6 dashboards + secubox-metrics datasource provisioned)
yacyctl status → overall=green (lxc + JVM + YaCy v1.941 standalone,
admin password at /etc/secubox/secrets/yacy-admin)
rustdeskctl status → overall=green (lxc + hbbs + hbbr + host-api,
PSK at /etc/secubox/secrets/rustdesk-key,
nftables DNAT 21116/udp installed)
https://admin.gk2.secubox.in/api/v1/grafana/healthz → HTTP 200
https://admin.gk2.secubox.in/api/v1/yacy/healthz → HTTP 200
https://admin.gk2.secubox.in/api/v1/rustdesk/healthz → HTTP 200
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
First module shipped against docs/MODULE-GUIDELINES.md. Grafana OSS in a
Debian bookworm LXC at 10.100.0.70 on br-lxc; host-side FastAPI control
plane on /run/secubox/grafana.sock; bash CTL grafanactl with three-fold
introspection (components/status/access) + install/reload lifecycle.
Pre-provisioned datasource (secubox-metrics Prometheus) and six security
dashboards: secubox-services, nftables, crowdsec, suricata, cookie-audit,
mitmproxy-waf.
nginx vhost wires /api/v1/grafana/ to the Unix socket and /grafana/ to
the LXC native UI (iframe target from www/grafana/index.html). Menu
entry at order 50 in the monitoring section.
CTL stubs the remaining nouns (dashboard/datasource/alert/user/api-key)
behind a clear "not yet implemented" exit 2 for v1.0.0; full
implementation tracked as a follow-up task (G5 in the plan). The
three-fold + install/reload surface is complete and usable from day one.
Anchored on the new OPS MONITORING layer of the SecuBox CTL grammar
(sister to healthctl). Slipstreams automatically into the next image
build via build-packages.yml's discover-by-glob loop — no workflow edit
required.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>