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>
The API runs unprivileged (secubox) but probed the container via lxc-info/occ
WITHOUT sudo and with lxc_path=/srv/lxc (container is /data/lxc) → status always
'Stopped', cloud.local domain, empty version/users/disk (and a 500 once the path
was corrected, from a PermissionError on /data/lxc). Now: privilege-free TCP port
probe for liveness, all container ops via 'sudo nextcloudctl' (the sole sudoers
surface, incl. its occ passthrough), a daemon-thread 60s cache for the slow occ
fields (version/users/storage) so /status stays fast, real data_path, and a
public web_url from the domain. Webui restyled to the /certs/ cyan hybrid-skin
(361 lines, emoji cards, live pulse, 30s refresh); every endpoint preserved.
Needs (board drift): NoNewPrivileges=false drop-in (sudo was blocked) and
[nextcloud] domain=nc.gk2.secubox.in in secubox.conf.
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>
- nextcloud: sudo strips OC_PASS, so password reached occ empty. Pipe it via
stdin → export → sudo --preserve-env=OC_PASS; pass username/display as argv
(special-char safe for master passwords).
- photoprism: PhotoPrism CE gates non-admin roles behind a paid membership, so
--role user was rejected. Use the default role + --upload-path for per-user
scoping; admin → --superadmin.
Validated end-to-end with a throwaway user (created + per-user folder/storage in
both apps, then removed).
SecuBox stays source of truth. user_store gains set_password() (the single
password write path = capture point) + list_users(); a new host CLI
secubox-user-sync sets the canonical argon2 hash then pushes the same plaintext
(via SECUBOX_USER_PASSWORD env/stdin, never argv) into each app exposing a
`<app>ctl user-provision` verb. nextcloudctl + photoprismctl gain that verb,
each creating a per-user photo folder (/data/shared/photos/<user>) and a
user-scoped library; admin stays unrestricted (management), others scoped.
gk2 + admin are always provisioned (seed). Tests for set_password included.
Static resolv.conf in the container is hijacked by systemd-resolved's empty
127.0.0.53 stub, killing DNS (no appstore/updates/external mounts) while NAT
egress still works. Drop a resolved.conf.d upstream at install. Add
docs/FAQ-LXC-DNS.md describing symptom/cause/fix. Live NC already patched.
Captures the live PhotoPrism↔Nextcloud photo wiring in source (pairs with
secubox-photoprism #397). The package was already native-LXC (nextcloudctl
v1.3 builds the LXC + installs NC), so this is the focused integration delta.
- nextcloudctl: new `link-photos` verb (idempotent) + called from cmd_setup —
enables files_external + creates the "PhotoLibrary" Local external storage
→ /media/photos with filesystem_check_changes=1.
- create_lxc_config: bind-mount /data/shared/photos at /media/photos (outside
the NC data dir); 0777 owned by the LXC root uid (www-data writes, PhotoPrism
reads). Phone → PhotoLibrary → /data/shared/photos → PhotoPrism originals.
- Keep SQLite (cmd_setup already uses --database sqlite) per operator preference
— lower RAM, simpler backups. Live gk2 MariaDB install is the outlier;
migrating it is a separate data-migration task, not done here.
- Bump NC_VERSION 30.0.4 → 31.0.14 (matches live).
The official Nextcloud mobile client (Android/iOS) uses HTTP Basic with
an app-password and does NOT carry browser SSO cookies. With the
auth_request gate on `location /` in the NC public vhost, every
/remote.php/webdav/* and /ocs/* call returned 401 → redirect to
sso.gk2.secubox.in/, which the mobile client can't render → auth
lockout loop. The phone then triggered NC's own bruteforce throttle.
Comment out (don't delete) the 4 SSO-related lines in `location /`:
- auth_request /__sbx_auth_verify;
- error_page 401 = @sbx_auth_login;
- auth_request_set $sbx_user ...
- auth_request_set $sbx_groups ...
plus the two proxy_set_header X-Forwarded-User/Groups inside the
proxy block.
The @sbx_auth_login + /__sbx_auth_verify blocks below are kept for
revert symmetry — uncomment the four lines + the X-Forwarded-User/
Groups to restore SSO. NC handles its own web login + 2FA + (newly
disabled at runtime) bruteforce protection.
Verified on gk2 after the live fix:
curl -A 'Nextcloud-android' https://nc/status.php → 200 OK
curl -A 'Nextcloud-android' https://nc/remote.php/webdav/ → 401
(correct — NC's own Basic auth challenge)
Trade-off (flagged in #394 issue): drops SSO for desktop browser
users too. They go to NC's own login page. To preserve SSO on
desktop while opening mobile-only paths (/remote.php/*, /ocs/*,
/public.php/*) is more complex and deferred to a follow-up.
Bumped secubox-nextcloud 1.3.4 → 1.3.5.
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.
User asked for control + status metrics at /auth/ instead of an
iframe-redirect, plus a public portal vhost sso.gk2.secubox.in for
multi-SP single-sign-on across SecuBox apps.
* secubox-authelia v1.0.9
- nginx/authelia.conf: /auth/ on the canonical hub vhost becomes a
static alias to /usr/share/secubox/www/authelia/ (operator
dashboard). Used to reverse-proxy the Authelia LXC portal.
- nginx/authelia.conf: @sbx_auth_login redirects to
https://sso.gk2.secubox.in/?rd=… (the new public portal vhost).
- nginx/authelia-vhost.conf: new server block sso.gk2.secubox.in,
reverse-proxying / → /auth/ → Authelia LXC 10.100.0.20:9091.
Wildcard *.gk2.secubox.in.pem cert already covers it.
- www/authelia/index.html: rewrite as the SecuBox AUTH config
module (Charter AUTH palette #C04E24). Surfaces service state,
version, user count, cookie scopes, access rules. Was a
Lyrion-template copy-paste stub.
- lib/authelia/install-lxc.sh: session.cookies[] for the hub
domain now sets authelia_url to sso.${SECUBOX_HUB_DOMAIN}.
* secubox-nextcloud v1.3.3, secubox-zigbee v2.5.4, secubox-lyrion v1.0.9
- Their @sbx_auth_login redirects now target
https://sso.gk2.secubox.in/?rd=… for login.
Deploy-side follow-up (operator):
- DNS sso.gk2.secubox.in → board public IP
- mitmproxy routes JSON: add sso.gk2.secubox.in → [192.168.1.200, 9080]
- haproxyctl vhost add sso.gk2.secubox.in mitmproxy_inspector
(+ workaround for #286 — re-append the backends)
- ln -s ../sites-available/authelia.conf /etc/nginx/sites-enabled/
- Re-apply install-lxc.sh OR sed-patch the live /etc/authelia/configuration.yml
inside the LXC to repoint authelia_url.
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>
Operators previously had to grep the FastAPI / nextcloudctl source to
find the right key names. Symptom: Connection URLs panel on the SecuBox
config UI showed `http://localhost:8080` (the default fallback) even
after the new nc.gk2.secubox.in vhost was live.
* conf/nextcloud.toml.example — flat-key template documenting all keys
both api/main.py and sbin/nextcloudctl read. Includes the ssl_*,
http_port, and install knobs (domain, admin_user, db_type).
* debian/rules — ships to /etc/secubox/nextcloud.toml.example.
Pure-config addition. No service code change.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
v1.3.0 had reverse-proxied `/nextcloud/` on the canonical hub vhost to
the Nextcloud LXC by mistake. That URL is the SecuBox CONFIGURATION
MODULE (static UI driving /api/v1/nextcloud/), not the Nextcloud app.
The actual Nextcloud app now lives on its own vhost:
* nginx/nextcloud.conf — `/nextcloud/` reverted to the static alias of
/usr/share/secubox/www/nextcloud/ (SecuBox config UI). /api/v1/nextcloud/
socket proxy unchanged.
* nginx/nextcloud-vhost.conf (new) — public vhost `nc.gk2.secubox.in`
reverse-proxying to the LXC Apache at 10.100.0.21:80 with Authelia
SSO gating. Re-declares /__sbx_auth_verify with X-Original-URL +
X-Forwarded-* forwarding (#274 pattern, server-block scope).
@sbx_auth_login redirects to https://admin.gk2.secubox.in/auth/
— session.cookies on .gk2.secubox.in cover both vhosts (#272).
* debian/rules — installs nginx/nextcloud.conf to BOTH /etc/nginx/secubox.d/
AND /etc/nginx/secubox-routes.d/ (matches secubox-authelia convention).
Installs nginx/nextcloud-vhost.conf to /etc/nginx/sites-available/nextcloud.conf
for operator-driven symlink enable.
Deploy-side follow-up (operator): DNS for nc.gk2.secubox.in, HAProxy SNI ACL,
mitmproxy haproxy-routes.json entry, ACME cert, Nextcloud trusted_domains
update.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
Nextcloud package predated the SSO chain. On gk2 it sat as a static
stub alias on /nextcloud/, the LXC was hand-patched to veth at
10.100.0.20 (colliding with secubox-authelia), and there was no
Authelia gating.
* nginx/nextcloud.conf — /nextcloud/ is now a reverse-proxy to the
Nextcloud LXC at 10.100.0.21:80 with Apache-friendly headers
(Host, X-Forwarded-{For,Proto,Host}), 300s read timeout, request
buffering off, no body-size cap (large uploads / WebDAV).
* nginx/nextcloud.conf — SSO-gated via auth_request /__sbx_auth_verify
(handler owned by secubox-authelia v1.0.8, #278). User identity
forwarded to Apache via X-Forwarded-{User,Groups}.
* sbin/nextcloudctl — LXC template moved from lxc.net.0.type=none
(host-mode) to veth + br-lxc + 10.100.0.21/24. Frees 10.100.0.20
for secubox-authelia. Defaults aligned with v2.11.1 (LXC_PATH=
/data/lxc, DATA_PATH=/data/volumes/nextcloud). Env override knobs
per MODULE-GUIDELINES.md §3.
Operators on gk2-style boards must manually rebind the existing LXC
to .21 (recipe in #280). install-lxc.sh modernization deferred to
v1.4.0.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
- 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>
- 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>
- 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>
New modules (35 total):
- secubox-repo v1.0.0: APT repository management
- repoctl CLI for package management
- GPG key generation and signing
- Multi-distribution support (bookworm, trixie)
- Web dashboard for repository status
- secubox-hardening v1.0.0: Kernel and system hardening
- hardeningctl CLI for security management
- Sysctl hardening (ASLR, kptr_restrict, SYN cookies)
- Module blacklist (uncommon protocols, filesystems)
- Security benchmark with 100% score on VM
CI/CD workflows:
- build-packages.yml: Dynamic matrix for all packages
- build-image.yml: 5 board images with compression
- publish-packages.yml: APT repo publishing
- release.yml: Unified release orchestration
APT repository scripts:
- export-secrets.sh: Export GPG/SSH keys for GitHub Actions
- local-publish.sh: Local test server
- install.sh: User installation script
Security (Phase 5):
- AppArmor profiles for all services
- Audit rules for SecuBox services
- build-all.sh for local builds
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add real backend control scripts and migration support:
- secubox-streamlit: streamlitctl for LXC container management,
app deployment, Gitea integration, OpenWrt migration
- secubox-haproxy: haproxyctl with LXC/Docker/native modes,
vhost/backend/cert management, WAF integration, migration
- secubox-metablogizer: updated UI with three-fold tabs
All modules now follow Components/Status/Access architecture
with tabbed UI and migration modals for importing from OpenWrt.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>