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.
The PeerTube dashboard bounced to /login.html?redirect=/peertube/ and looped after
a successful login. getToken() read localStorage 'jwt_token' || 'token' — keys the
canonical login.html never writes. login.html (and every other module webui: waf,
soc, crowdsec, metrics, shared sidebar.js) uses 'sbx_token'. So PeerTube always sent
an empty Authorization → API 401 → api()'s 401 handler redirected to login → login
stored sbx_token → back to /peertube/ → getToken() read jwt_token (empty) → 401 → loop.
Pre-existing divergence (present in the May-28 backup); not caused by #798. Same class
as the cookies webui drift (#749). Fix: read sbx_token first, keep the legacy keys as
fallback. Applied live on the board to unblock.
Co-authored-by: CyberMind-FR <gandalf@Gk2.net>
* fix(peertube): drain admin-ops results to a separate dir (fixes start-limit loop, ref #798)
The root peertube-ops.path watches OPS_DIR with DirectoryNotEmpty=. process-ops
wrote each <id>.result.json back into OPS_DIR, so the directory was never empty
after draining a request → the .path re-triggered peertube-ops.service in a tight
loop until systemd killed it with start-limit-hit, after which the mechanism died
after the first op.
Write results to a dedicated /run/secubox/peertube/results/ instead; process-ops
rm's the request, leaving OPS_DIR empty so the watcher goes idle. The API's
_read_op_result now polls RESULTS_DIR. tmpfiles + postinst create the dir
(0750 secubox:secubox); peertubectl honors SECUBOX_PEERTUBE_RESULTS_DIR.
test_process_ops.sh asserts results land in results/ and OPS_DIR is empty after
draining (regression guard); test_ops_api monkeypatches both dirs.
Verified live on gk2: two consecutive pings drain cleanly, NRestarts=0, no
start-limit-hit, results root:secubox 0640 (secubox-readable).
* fix(peertube): upgrade CWD must be peertube-latest/scripts + surface the error (ref #798)
The webui upgrade button failed with a generic "upgrade script failed" and no detail.
Two bugs in cmd_upgrade:
1. Wrong CWD. PeerTube's v8 shim peertube-latest/scripts/upgrade.sh execs
`../dist/scripts/upgrade.sh` relative to CWD, and that dist script runs
`node -e "require('js-yaml')…"` whose resolution is also CWD-relative. Running from
`cd /var/www/peertube` made `../dist` → /var/www/dist (missing) → "cannot open
../dist/scripts/upgrade.sh"; a neutral CWD instead fails "Cannot find module
'js-yaml'". Fix: cd /var/www/peertube/peertube-latest/scripts (../dist resolves,
and require() walks up to peertube-latest/node_modules).
2. Error was discarded to /dev/null, so the result only said "upgrade script failed".
Now capture to /run/secubox/peertube/upgrade-<id>.log and fold the last lines into
the error detail.
Verified live: with the corrected CWD, 8.2.0 → 8.2.2 upgraded cleanly (download + deps
+ migrations), service restarted, /api/v1/config 200, serverVersion 8.2.2.
---------
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.
webui.conf includes /etc/nginx/secubox-routes.d/*.conf, NOT secubox.d/. The
rework shipped only to secubox.d/, so on upgrade dpkg removed the old
secubox-routes.d/peertube.conf → /api/v1/peertube/ 404/unrouted → dashboard
tile red + cookie-relay POST failed. Ship to both dirs (mirror grafana).
The postinst's `install -d -o root -g root -m 755 /etc/secubox` reset the dir
secubox-core owns as secubox:secubox 0750, so the users-engine could no longer
create its atomic-save temp file → TOTP verification crashed with
PermissionError → all logins rejected. Guard: only create /etc/secubox as a
fallback if missing, never reset an existing dir. (ref #388)
Lets the SecuBox Companion extension install YouTube cookies with one click.
- api/main.py: POST /import/cookies (require_jwt) — accepts the Netscape
cookies body, spools to /run/secubox/peertube-yt-cookies.txt, then escalates
via a single narrowly-scoped sudoers rule to `peertubectl set-youtube-cookies`
(installs into the LXC, enables import cookies, restarts PeerTube).
- debian/secubox-peertube.sudoers: secubox → NOPASSWD exactly
`peertubectl set-youtube-cookies /run/secubox/peertube-yt-cookies.txt`.
- service unit: drop NoNewPrivileges (would block sudo); documented why.
- rules: install sudoers 0440; control: Depends sudo.
Deployed + verified on gk2 (route 401s unauth, sudoers scoped, secubox can
spool, NoNewPrivileges=no).
YouTube hard-blocks server IPs ("Sign in to confirm you're not a bot") — no
yt-dlp version or player_client bypass works (tested); cookies from a logged-in
browser are the only reliable fix. New verb installs a Netscape cookies.txt into
the LXC at storage/tmp-persistent/youtube-cookies.txt (where PeerTube's
wrapWithCookiesOptions reads it), enables import.videos.http.cookies.enabled,
and restarts. Operator: export cookies.txt → scp to board →
`peertubectl set-youtube-cookies cookies.txt`.
The dashboard runs as the unprivileged 'secubox' user, which can't read the
root-owned LXC via lxc-info (reports 'absent'), making the webui wrongly show
the Install banner for a running instance. Trust http_reachable as the primary
running signal; fall back to lxc_state only when unreachable.
Aligns the package with what's actually deployed on gk2 (validated live at
https://peertube.gk2.secubox.in/, upload confirmed) and adds yt-dlp URL import.
- lib/peertube/install-lxc.sh (NEW): idempotent native PeerTube install in a
dedicated Debian LXC (10.100.0.120 on br-lxc), mirroring grafana/yacy. Bakes
in every fix from the live bring-up: Node 22 via signed NodeSource repo (8.x
needs >=22, no curl|bash); pnpm with MSGPACKR_NATIVE_ACCELERATION_DISABLED=1
(arm64); debian.common.conf template (postgres-15 needs it); bind mounts
/data/peertube/{storage,config,postgres,redis} chown'd to LXC root UID;
production.yaml patch (listen 0.0.0.0:9000, https/hostname/443, secret, db,
admin email, import.videos.http.enabled=true); captures first-boot root
password to /etc/secubox/secrets/peertube-admin.
- sbin/peertubectl (NEW): install/status/start/stop/restart/logs/reload.
- conf/peertube.nginx.conf (folds #390, port corrected to LXC :9000 not the
stale 127.0.0.1:9001 Docker-on-host plan): public vhost :9080 -> 10.100.0.120
:9000, 8G uploads, 7d streaming timeouts, WS, ACME; WAF-bypass (gitea pattern).
- api/main.py: talk to the native instance over HTTP at <lxc_ip>:<http_port>
with Host: <public_hostname> header (PeerTube 403s on raw-IP Host) instead of
podman/docker exec; status reports real LXC state + reachability; /container/*
drive the LXC via peertubectl; NEW POST /import + GET /imports.
- www/peertube/index.html: new "Import" tab (URL + channel + privacy + recent
imports); status/labels/links corrected for native-LXC.
- debian/control + service: reframe Docker/Podman -> native-LXC; drop
Wants=podman/docker. conf/peertube.toml.example (NEW). README + changelog 1.1.0.
secubox-peertube 1.0.0 had been removed from gk2 and could not be
cleanly reinstalled: hardcoded /srv/peertube path (predates #319),
service unit blocked from starting by ConditionPathExists, broken
Requires=secubox-runtime.service, root user with ExecStartPost
chmod hacks, postinst didn't tolerate masked units, no /data
migration block.
Reworked to current SecuBox patterns (mirror gitea/wazuh):
api/main.py — default data_path /srv/peertube → /data/peertube
(5 hardcoded fallbacks updated in lockstep, Pydantic model
default updated, top-level DEFAULT_CONFIG updated).
debian/secubox-peertube.service — drop ConditionPathExists gate
so the API daemon is always reachable to serve the dashboard
(the heavy PeerTube container is still operator-deployed on
demand via the dashboard); User=root → User=secubox + Group=
secubox; replace ExecStartPost chmod/chown hacks with
RuntimeDirectory=secubox + RuntimeDirectoryPreserve=yes +
RuntimeDirectoryMode=0775 + UMask=0000 (mass-redeploy safety);
drop the broken Requires=secubox-runtime.service (no such
service); reorder Wants= to podman.service docker.service;
add ReadWritePaths covering /run/secubox /var/lib/secubox
/etc/secubox /var/log/secubox /data/peertube.
debian/postinst — add #319 /data migration block (mirror gitea
pattern from aba60ae3); wrap systemctl enable in is-enabled
!= masked check (per wazuh fix 63284497) so a deliberately
masked unit no longer fails the postinst; drop the hardcoded
/srv/peertube/{storage,config,data} mkdir (the API creates
these on first container/install so PeerTube's UID/GID can own
them); nginx reload guarded by `nginx -t`.
debian/control — Description rewritten with cross-references
to the other federated/streaming/media SecuBox packages
(matrix, jitsi, jellyfin, gotosocial, simplex); Recommends
reordered to podman first (smaller, distro-native), docker.io
second; explicit note that the API daemon is lightweight and
always reachable while the container is operator-deployed.
Verified on gk2 (root@192.168.1.200):
- apt install podman secubox-peertube_1.0.1-1~bookworm1_all.deb
succeeded.
- systemctl is-active secubox-peertube → active
- /run/secubox/peertube.sock created srw-rw-rw- secubox:secubox
(no ExecStartPost chmod hacks needed)
- curl --unix-socket /run/secubox/peertube.sock http://x/health
→ {"status":"ok","module":"deb"}
- podman 4.3.1 installed and ready for container/install when
operator triggers it via dashboard
- /data/peertube preserved (existing symlink /srv/peertube →
/data/peertube intact)
Not done in this rework (out of scope per issue):
- Actually installing the PeerTube container on gk2 (1G RAM
free, operator's call).
- peertubectl LXC controller — package is Docker/Podman-based
by design.
- Frontend rewrite (1154-LOC dashboard stays as-is).
- Adding to secubox-full/-lite metapackages.
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>
PR #169 moved login.html out of secubox-hub to /usr/share/secubox/www/
under secubox-portal's ownership, but 67 source files across the
package set still redirected to /portal/login.html which no longer
exists on disk. nginx fell back to /index.html (hub root) on every
hit, whose checkAuth() bounced back to /portal/login.html, producing
an infinite "Redirecting to login..." loop with no way for any user
to authenticate.
sed -i 's|/portal/login\.html|/login.html|g' across all packages/*/www
sources (82 occurrences). The two prior partial fixes that triggered
this investigation (bcc6a781, 464e3379) only covered a subset and
missed the kiosk root checkAuth in secubox-hub/www/index.html plus
65 module index.html files.
Verified: grep -rl '/portal/login\.html' packages/ | grep -v /debian/
returns empty.
Discovered while testing the firstboot v2-schema+argon2 chain end-to-end
in a VBox amd64 boot of CI run 26082003883 (the #218 image build fix)
— firstboot now works, users.json gets a valid argon2 hash for
admin/secubox, the portal API runs, but the login UI is unreachable
because of this dangling URL.
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>
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>
Changed /usr/local/bin/uvicorn to /usr/bin/python3 -m uvicorn
for compatibility across different Python installation methods.
Also fixed JWT auth import in eye-remote boot_media router.
Affected: 31 service files across 30 packages
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>
1. Fix slipstream path: Look for packages in output/ not just output/debs/
- build-image.sh and build-live-usb.sh now check both locations
- 73 packages now properly detected and installed
2. Add ConditionPathExists to 22 optional services:
- Services only start if /etc/secubox/<module>/enabled exists
- Prevents boot failures when backend apps aren't installed
- Affected: ollama, jellyfin, zigbee, lyrion, localai, hexo,
peertube, newsbin, webradio, gotosocial, torrent, domoticz,
simplex, photoprism, homeassistant, matrix, jitsi, voip,
magicmirror, mmpm, jabber, redroid
3. Add explicit parted/fdisk install in live USB build
- Ensures disk tools available for secubox-install
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>
- LXC-based PeerTube container with PostgreSQL, Redis, Node.js, ffmpeg
- FastAPI backend for install, user, video management
- Web UI with stats grid, videos/users/logs tabs
- Systemd service on Unix socket
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>