mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 10:00:52 +00:00
Compare commits
16 Commits
05d6135e53
...
c9397e3008
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9397e3008 | ||
|
|
206157047e | ||
|
|
bed4c1c6d3 | ||
|
|
9ba49e3bf7 | ||
| ebf714f123 | |||
| 5763aa3a73 | |||
| 2a8c1b33de | |||
| 9d1b0abade | |||
| 41d78ef455 | |||
| fbd474b2c3 | |||
| c47e454532 | |||
| e12790efbd | |||
| ce636273a6 | |||
| 4dd87eae2f | |||
| af02a9731c | |||
| 663715af0f |
|
|
@ -0,0 +1,56 @@
|
|||
<!-- SPDX-License-Identifier: LicenseRef-CMSD-1.0 -->
|
||||
# Spec — HAProxy complete dynamic vhost auto-discovery
|
||||
|
||||
*2026-06-17 · landed for later (per user) · found while fixing #626/#627*
|
||||
|
||||
## Problem
|
||||
`haproxyctl generate` must produce a COMPLETE config so regen never drops
|
||||
hand-maintained vhosts. Today ~10 vhosts live only in the hand-edited
|
||||
`/etc/haproxy/haproxy.cfg` — not in `haproxy.toml` or `cfg.d/`:
|
||||
|
||||
- `kbin.gk2.secubox.in` → `toolbox_landing` (backend also live-only)
|
||||
- `matrix`, `gitea`, `peertube`, `photoprism` (.gk2.secubox.in) → `nginx_vhosts`
|
||||
|
||||
These bypass the WAF today (route direct, not through `mitmproxy_inspector`).
|
||||
A clean regen omits them. PR #627 added a **drift guard** so regen refuses to
|
||||
clobber when its output has fewer vhosts/backends than live — safe, but not
|
||||
complete.
|
||||
|
||||
## Design (approved direction: auto-discover from modules/LXC)
|
||||
1. **Drop-in registry.** Generator aggregates `haproxy.toml [vhosts.*]` **plus**
|
||||
`/etc/secubox/vhosts.d/*.toml` — one file per module/LXC, dropped by that
|
||||
module's postinst (self-registration). New modules appear automatically.
|
||||
2. **Per-vhost routing intent** replaces the blanket `waf_enabled` override
|
||||
(which currently forces *every* vhost through the WAF):
|
||||
```toml
|
||||
[vhost]
|
||||
domain = "peertube.gk2.secubox.in"
|
||||
backend = "nginx_vhosts" # or a module/LXC backend
|
||||
ssl = true
|
||||
inspect = false # true → mitmproxy_inspector (WAF); false → direct
|
||||
```
|
||||
3. **One-time migration.** Seed `vhosts.d/` from the ~10 drifted live entries
|
||||
with their real backends + `inspect=false`; register `toolbox_landing` as a
|
||||
known backend. After this, regen output == live → drift guard passes.
|
||||
4. **Module-registration helper** for postinsts (e.g. `haproxyctl vhost register
|
||||
--from-file` or a tiny library) so each LXC/module declares its vhost.
|
||||
5. **Keep the drift guard** as the transition safety net.
|
||||
|
||||
## Validation gate (non-negotiable)
|
||||
Never apply a regenerated cfg to production until a diff proves it reproduces all
|
||||
~100 live vhosts/backends 1:1 (drift-guard counts match).
|
||||
|
||||
## Related: finish the traversal-footgun sweep (#623)
|
||||
The systemic `install -d -m 0750 .../secubox` footgun is broader than first
|
||||
swept: **multi-arg** forms like `install -d -m 0750 /run/secubox /var/lib/secubox
|
||||
…` (e.g. secubox-haproxy, fixed in #627) were missed by the earlier grep
|
||||
(`…/secubox/[a-z]` required a leaf). Re-sweep with a pattern that catches bare
|
||||
`/var/{lib,log,cache}/secubox` arguments, and add a **tmpfiles.d + periodic
|
||||
guard** so the shared parents self-heal to `0755` regardless of which package
|
||||
clobbers them (this is the root of the recurring kbin/toolbox 500s).
|
||||
|
||||
## Status
|
||||
- Generator no longer crashes (set -e + dup-backend fixed, #627).
|
||||
- Drift guard prevents clobbering (#627).
|
||||
- Error pages live + served from `/etc/haproxy/secubox-errors/` (#627).
|
||||
- This auto-discovery rework + the #623 re-sweep are the remaining work.
|
||||
|
|
@ -1,3 +1,14 @@
|
|||
secubox-core (1.1.7-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(postinst): /var/lib/secubox + /usr/share/secubox/www were set 0750,
|
||||
breaking traversal for non-secubox daemons (kbin/toolbox 500) — now 0755
|
||||
(same reasoning the postinst already applied to /etc/secubox).
|
||||
* feat: secubox-dirs-guard timer — re-asserts the shared /…/secubox parents
|
||||
to 0755 every minute, self-healing the recurring 0750 clobber from module
|
||||
postinsts (closes #630).
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Tue, 17 Jun 2026 11:00:00 +0200
|
||||
|
||||
secubox-core (1.1.6-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* postinst: relax /etc/secubox to 0755 (was 0750). Non-secubox daemons
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@ case "$1" in
|
|||
# secubox:secubox), so opening the parent dir does NOT leak secrets.
|
||||
install -d -o secubox -g secubox -m 755 /etc/secubox
|
||||
install -d -m 1777 /run/secubox # World-writable for all service sockets
|
||||
install -d -o secubox -g secubox -m 750 /var/lib/secubox
|
||||
install -d -o secubox -g secubox -m 750 /usr/share/secubox/www
|
||||
# Same reasoning as /etc/secubox above: these SHARED parents must be 0755 so
|
||||
# every secubox-* daemon (running as its own user, not in group secubox) can
|
||||
# traverse to its own subtree. 0750 here broke kbin/toolbox repeatedly
|
||||
# (#626/#630). Per-module leaves + secrets stay restricted.
|
||||
install -d -o secubox -g secubox -m 755 /var/lib/secubox
|
||||
install -d -o secubox -g secubox -m 755 /usr/share/secubox/www
|
||||
|
||||
# ── tmpfiles.d for persistent /run/secubox ──
|
||||
systemd-tmpfiles --create 2>/dev/null || true
|
||||
|
|
@ -70,6 +74,11 @@ case "$1" in
|
|||
# Enable runtime directory service (ensures /run/secubox exists before other services)
|
||||
systemctl enable --now secubox-runtime.service 2>/dev/null || true
|
||||
|
||||
# Shared-dir traversal guard (#630): self-heals the recurring 0750 clobber
|
||||
# (from this and other module postinsts) that breaks kbin/toolbox.
|
||||
systemctl enable --now secubox-dirs-guard.timer 2>/dev/null || true
|
||||
systemctl start secubox-dirs-guard.service 2>/dev/null || true
|
||||
|
||||
# ── Python dependencies (ensure compatible versions) ──
|
||||
# Debian bookworm ships old pydantic v1 and fastapi, upgrade via pip
|
||||
if command -v pip3 >/dev/null 2>&1; then
|
||||
|
|
|
|||
|
|
@ -58,6 +58,18 @@ override_dh_auto_install:
|
|||
install -m 644 tmpfiles.d/secubox.conf \
|
||||
debian/secubox-core/usr/lib/tmpfiles.d/
|
||||
|
||||
# Shared-dir traversal guard (#630): keep /var/{lib,log,cache} + /etc +
|
||||
# /usr/share /secubox parents at 0755 so every secubox-* user can traverse.
|
||||
# Counters the recurring `install -d -m 0750 …/secubox` clobber in module
|
||||
# postinsts that breaks kbin/toolbox.
|
||||
install -d debian/secubox-core/usr/sbin
|
||||
install -m 755 usr/sbin/secubox-dirs-guard.sh \
|
||||
debian/secubox-core/usr/sbin/secubox-dirs-guard.sh
|
||||
install -m 644 systemd/secubox-dirs-guard.service \
|
||||
debian/secubox-core/usr/lib/systemd/system/
|
||||
install -m 644 systemd/secubox-dirs-guard.timer \
|
||||
debian/secubox-core/usr/lib/systemd/system/
|
||||
|
||||
# LED scripts and services
|
||||
install -d debian/secubox-core/usr/sbin
|
||||
install -m 755 usr/sbin/secubox-led-heartbeat \
|
||||
|
|
|
|||
7
packages/secubox-core/systemd/secubox-dirs-guard.service
Normal file
7
packages/secubox-core/systemd/secubox-dirs-guard.service
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[Unit]
|
||||
Description=SecuBox shared-dir traversal guard (keep /…/secubox parents 0755)
|
||||
Documentation=https://github.com/CyberMind-FR/secubox-deb
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/secubox-dirs-guard.sh
|
||||
Nice=10
|
||||
8
packages/secubox-core/systemd/secubox-dirs-guard.timer
Normal file
8
packages/secubox-core/systemd/secubox-dirs-guard.timer
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[Unit]
|
||||
Description=Re-assert SecuBox shared-dir perms every minute
|
||||
[Timer]
|
||||
OnCalendar=*:0/1
|
||||
AccuracySec=10s
|
||||
Persistent=true
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
13
packages/secubox-core/usr/sbin/secubox-dirs-guard.sh
Executable file
13
packages/secubox-core/usr/sbin/secubox-dirs-guard.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
# SecuBox-Deb :: shared-dir traversal guard
|
||||
# Keep the shared SecuBox parent dirs traversable (0755) so EVERY secubox-* user
|
||||
# can reach its own subtree. Counters the recurring `install -d -m 0750 …/secubox`
|
||||
# clobber in various module postinsts that breaks kbin/toolbox (#626/#630).
|
||||
# chmod-only (owner-agnostic); runs every minute via secubox-dirs-guard.timer.
|
||||
for d in /var/lib/secubox /var/log/secubox /var/cache/secubox /etc/secubox /usr/share/secubox; do
|
||||
[ -d "$d" ] || continue
|
||||
[ "$(stat -c %a "$d" 2>/dev/null)" = "755" ] || chmod 0755 "$d" 2>/dev/null || true
|
||||
done
|
||||
exit 0
|
||||
|
|
@ -1,3 +1,28 @@
|
|||
secubox-haproxy (1.3.1-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* feat(errors): smart self-healing HAProxy error pages (closes #626).
|
||||
- errors/{502,503,504}.http: branded pages that poll the URL and
|
||||
auto-reload when the backend recovers (transient blips heal in-browser),
|
||||
with live status + manual retry. {400,403,408,500}.http: branded static.
|
||||
- haproxyctl generator: wire errorfile directives (durable across regen)
|
||||
and persist retries 3 + option redispatch in defaults.
|
||||
- Ship pages to /etc/haproxy/secubox-errors/ (own dir; /etc/haproxy/errors
|
||||
is owned by the haproxy package). errorfile wired there.
|
||||
* fix(generator): `haproxyctl generate` was broken (exited 1, produced no
|
||||
backends) — root causes: (a) `set -e` + `[ ] && [ ] && { }` vhost-loop
|
||||
chains aborted generation on the first non-SSL vhost; (b) duplicate
|
||||
`backend mitmproxy_inspector` (auto + user TOML) → fatal. Converted the
|
||||
chains to if/then/fi and dedup user backends already emitted.
|
||||
* fix(generator): drift guard — refuse to install a generated cfg with fewer
|
||||
vhosts/backends than the live one (prevents a successful regen from silently
|
||||
dropping hand-maintained vhosts like kbin/gitea/matrix not yet in
|
||||
haproxy.toml). Surfaces the drift instead of clobbering.
|
||||
* fix(postinst): keep /run/secubox + /var/lib/secubox at 0755 (was set 0750,
|
||||
breaking directory traversal for other secubox-* daemons — kbin/toolbox 500).
|
||||
Only the haproxy-private leaves are 0750.
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Tue, 17 Jun 2026 09:00:00 +0200
|
||||
|
||||
secubox-haproxy (1.3.0-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* sbin/haproxyctl: rewrite cmd_generate to be safe + complete (#286).
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ case "$1" in
|
|||
configure)
|
||||
id -u secubox >/dev/null 2>&1 || \
|
||||
adduser --system --group --no-create-home --home /var/lib/secubox --shell /usr/sbin/nologin secubox
|
||||
install -d -o secubox -g secubox -m 750 /run/secubox /var/lib/secubox /var/lib/secubox/haproxy /var/lib/secubox/haproxy/config_backups
|
||||
# Shared parents stay 0755 (traversable by every secubox-* daemon — setting
|
||||
# them 0750 here broke kbin/toolbox by blocking traversal, #626). Only the
|
||||
# haproxy-private leaves are restricted.
|
||||
install -d -o secubox -g secubox -m 755 /run/secubox /var/lib/secubox
|
||||
install -d -o secubox -g secubox -m 750 /var/lib/secubox/haproxy /var/lib/secubox/haproxy/config_backups
|
||||
chmod 0755 /var/lib/secubox /run/secubox 2>/dev/null || true
|
||||
# Create /etc/haproxy if not present (haproxy is Recommends, not Depends)
|
||||
# Required for systemd namespace setup
|
||||
install -d -m 755 /etc/haproxy
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ override_dh_auto_install:
|
|||
[ -d www ] && cp -r www/. debian/secubox-haproxy/usr/share/secubox/www/ || true
|
||||
install -d debian/secubox-haproxy/usr/share/secubox/menu.d
|
||||
[ -d menu.d ] && cp -r menu.d/. debian/secubox-haproxy/usr/share/secubox/menu.d/ || true
|
||||
# Smart self-healing HAProxy error pages (#626) — wired via errorfile in
|
||||
# haproxyctl. Own dir (NOT /etc/haproxy/errors, which the haproxy package owns).
|
||||
install -d debian/secubox-haproxy/etc/haproxy/secubox-errors
|
||||
[ -d errors ] && install -m 644 errors/*.http debian/secubox-haproxy/etc/haproxy/secubox-errors/ || true
|
||||
# Modular nginx config
|
||||
install -d debian/secubox-haproxy/etc/nginx/secubox.d
|
||||
[ -f nginx/haproxy.conf ] && cp nginx/haproxy.conf debian/secubox-haproxy/etc/nginx/secubox.d/ || true
|
||||
|
|
|
|||
35
packages/secubox-haproxy/errors/400.http
Normal file
35
packages/secubox-haproxy/errors/400.http
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
HTTP/1.1 400 Bad Request
|
||||
Cache-Control: no-store
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bad request — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(230,57,70,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(110,64,201,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#c9a84c;text-shadow:0 0 30px rgba(201,168,76,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
button{margin-top:22px;background:transparent;color:#00d4ff;border:1px solid #00d4ff;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#00d4ff;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code">400</div>
|
||||
<h1>Bad request</h1>
|
||||
<p>The request could not be understood. Check the URL and try again.</p>
|
||||
<button type="button" onclick="history.length>1?history.back():location.reload()">Go back</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
35
packages/secubox-haproxy/errors/403.http
Normal file
35
packages/secubox-haproxy/errors/403.http
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
HTTP/1.1 403 Forbidden
|
||||
Cache-Control: no-store
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blocked — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(230,57,70,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(110,64,201,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#e63946;text-shadow:0 0 30px rgba(230,57,70,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
button{margin-top:22px;background:transparent;color:#00d4ff;border:1px solid #00d4ff;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#00d4ff;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code">403</div>
|
||||
<h1>Blocked</h1>
|
||||
<p>This request was blocked by the SecuBox WAF. If you believe this is a mistake, contact the operator.</p>
|
||||
<button type="button" onclick="history.length>1?history.back():location.reload()">Go back</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
35
packages/secubox-haproxy/errors/408.http
Normal file
35
packages/secubox-haproxy/errors/408.http
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
HTTP/1.1 408 Request Timeout
|
||||
Cache-Control: no-store
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Request timeout — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(230,57,70,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(110,64,201,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#c9a84c;text-shadow:0 0 30px rgba(201,168,76,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
button{margin-top:22px;background:transparent;color:#00d4ff;border:1px solid #00d4ff;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#00d4ff;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code">408</div>
|
||||
<h1>Request timeout</h1>
|
||||
<p>The request took too long. Check your connection and try again.</p>
|
||||
<button type="button" onclick="history.length>1?history.back():location.reload()">Go back</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
35
packages/secubox-haproxy/errors/500.http
Normal file
35
packages/secubox-haproxy/errors/500.http
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
HTTP/1.1 500 Internal Server Error
|
||||
Cache-Control: no-store
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Internal error — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(230,57,70,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(110,64,201,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#e63946;text-shadow:0 0 30px rgba(230,57,70,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
button{margin-top:22px;background:transparent;color:#00d4ff;border:1px solid #00d4ff;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#00d4ff;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code">500</div>
|
||||
<h1>Internal error</h1>
|
||||
<p>Something went wrong on our side. The incident is logged. Try again shortly.</p>
|
||||
<button type="button" onclick="history.length>1?history.back():location.reload()">Go back</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
70
packages/secubox-haproxy/errors/502.http
Normal file
70
packages/secubox-haproxy/errors/502.http
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
HTTP/1.1 502 Bad Gateway
|
||||
Cache-Control: no-store, no-cache, must-revalidate
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Service recovering — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(201,168,76,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(0,212,255,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#c9a84c;text-shadow:0 0 30px rgba(201,168,76,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px;color:#e8e6d9}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;background:#c9a84c;margin-right:8px;animation:pulse 1.2s ease-in-out infinite;vertical-align:middle}
|
||||
@keyframes pulse{0%,100%{opacity:.3;transform:scale(.85)}50%{opacity:1;transform:scale(1.15)}}
|
||||
.status{margin:24px 0 8px;font-family:'JetBrains Mono',monospace;font-size:.9rem;color:#00d4ff;min-height:1.4em}
|
||||
.status.ok{color:#00ff41}
|
||||
.bar{height:3px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden;margin:16px 0}
|
||||
.bar>i{display:block;height:100%;width:30%;background:linear-gradient(90deg,transparent,#00d4ff,transparent);animation:scan 1.6s linear infinite}
|
||||
@keyframes scan{0%{transform:translateX(-120%)}100%{transform:translateX(400%)}}
|
||||
button{margin-top:18px;background:transparent;color:#c9a84c;border:1px solid #c9a84c;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#c9a84c;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code" id="code">502</div>
|
||||
<h1><span class="dot"></span>Service recovering</h1>
|
||||
<p>This service is temporarily unavailable. It's being inspected by the SecuBox WAF and is recovering.</p>
|
||||
<p>This page checks automatically and will reload the moment it's back — no need to refresh.</p>
|
||||
<div class="bar"><i></i></div>
|
||||
<div class="status" id="status">⟳ checking…</div>
|
||||
<button id="retry" type="button">Retry now</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge — self-healing</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
"use strict";
|
||||
var s=document.getElementById('status'),attempt=0,delay=4000,timer=null;
|
||||
function recovered(st){return st>0&&st<500;}
|
||||
function check(){
|
||||
attempt++;
|
||||
s.className='status';s.textContent='⟳ checking… (attempt '+attempt+')';
|
||||
fetch(location.href,{method:'GET',cache:'no-store',redirect:'manual',headers:{'x-secubox-healthcheck':'1'}})
|
||||
.then(function(r){
|
||||
if(recovered(r.type==='opaqueredirect'?200:r.status)){
|
||||
s.className='status ok';s.textContent='✓ back online — reloading…';
|
||||
clearTimeout(timer);setTimeout(function(){location.reload();},600);
|
||||
}else{schedule();}
|
||||
})
|
||||
.catch(function(){schedule();});
|
||||
}
|
||||
function schedule(){
|
||||
delay=Math.min(delay+1000,15000);
|
||||
timer=setTimeout(check,delay);
|
||||
}
|
||||
document.getElementById('retry').addEventListener('click',function(){clearTimeout(timer);delay=4000;check();});
|
||||
timer=setTimeout(check,2500);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
70
packages/secubox-haproxy/errors/503.http
Normal file
70
packages/secubox-haproxy/errors/503.http
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
HTTP/1.1 503 Service Unavailable
|
||||
Cache-Control: no-store, no-cache, must-revalidate
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Service recovering — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(201,168,76,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(0,212,255,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#c9a84c;text-shadow:0 0 30px rgba(201,168,76,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px;color:#e8e6d9}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;background:#c9a84c;margin-right:8px;animation:pulse 1.2s ease-in-out infinite;vertical-align:middle}
|
||||
@keyframes pulse{0%,100%{opacity:.3;transform:scale(.85)}50%{opacity:1;transform:scale(1.15)}}
|
||||
.status{margin:24px 0 8px;font-family:'JetBrains Mono',monospace;font-size:.9rem;color:#00d4ff;min-height:1.4em}
|
||||
.status.ok{color:#00ff41}
|
||||
.bar{height:3px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden;margin:16px 0}
|
||||
.bar>i{display:block;height:100%;width:30%;background:linear-gradient(90deg,transparent,#00d4ff,transparent);animation:scan 1.6s linear infinite}
|
||||
@keyframes scan{0%{transform:translateX(-120%)}100%{transform:translateX(400%)}}
|
||||
button{margin-top:18px;background:transparent;color:#c9a84c;border:1px solid #c9a84c;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#c9a84c;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code" id="code">503</div>
|
||||
<h1><span class="dot"></span>Service recovering</h1>
|
||||
<p>This service is temporarily unavailable. It's being inspected by the SecuBox WAF and is recovering.</p>
|
||||
<p>This page checks automatically and will reload the moment it's back — no need to refresh.</p>
|
||||
<div class="bar"><i></i></div>
|
||||
<div class="status" id="status">⟳ checking…</div>
|
||||
<button id="retry" type="button">Retry now</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge — self-healing</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
"use strict";
|
||||
var s=document.getElementById('status'),attempt=0,delay=4000,timer=null;
|
||||
function recovered(st){return st>0&&st<500;}
|
||||
function check(){
|
||||
attempt++;
|
||||
s.className='status';s.textContent='⟳ checking… (attempt '+attempt+')';
|
||||
fetch(location.href,{method:'GET',cache:'no-store',redirect:'manual',headers:{'x-secubox-healthcheck':'1'}})
|
||||
.then(function(r){
|
||||
if(recovered(r.type==='opaqueredirect'?200:r.status)){
|
||||
s.className='status ok';s.textContent='✓ back online — reloading…';
|
||||
clearTimeout(timer);setTimeout(function(){location.reload();},600);
|
||||
}else{schedule();}
|
||||
})
|
||||
.catch(function(){schedule();});
|
||||
}
|
||||
function schedule(){
|
||||
delay=Math.min(delay+1000,15000);
|
||||
timer=setTimeout(check,delay);
|
||||
}
|
||||
document.getElementById('retry').addEventListener('click',function(){clearTimeout(timer);delay=4000;check();});
|
||||
timer=setTimeout(check,2500);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
70
packages/secubox-haproxy/errors/504.http
Normal file
70
packages/secubox-haproxy/errors/504.http
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
HTTP/1.1 504 Gateway Timeout
|
||||
Cache-Control: no-store, no-cache, must-revalidate
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=utf-8
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Service recovering — SecuBox</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{min-height:100vh;background:#0a0a0f;color:#e8e6d9;font-family:system-ui,-apple-system,'Segoe UI',sans-serif;display:flex;align-items:center;justify-content:center;padding:24px;
|
||||
background-image:radial-gradient(ellipse at 30% 20%,rgba(201,168,76,.06)0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(0,212,255,.05)0%,transparent 50%)}
|
||||
.card{max-width:520px;width:100%;text-align:center;background:rgba(17,23,32,.85);border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:40px 32px;box-shadow:0 8px 40px rgba(0,0,0,.5)}
|
||||
.code{font-family:'JetBrains Mono',monospace;font-size:5.5rem;font-weight:700;line-height:1;color:#c9a84c;text-shadow:0 0 30px rgba(201,168,76,.4)}
|
||||
h1{font-size:1.5rem;font-weight:600;margin:12px 0 8px;color:#e8e6d9}
|
||||
p{color:#8a9aa8;font-size:.95rem;line-height:1.6;margin:6px 0}
|
||||
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;background:#c9a84c;margin-right:8px;animation:pulse 1.2s ease-in-out infinite;vertical-align:middle}
|
||||
@keyframes pulse{0%,100%{opacity:.3;transform:scale(.85)}50%{opacity:1;transform:scale(1.15)}}
|
||||
.status{margin:24px 0 8px;font-family:'JetBrains Mono',monospace;font-size:.9rem;color:#00d4ff;min-height:1.4em}
|
||||
.status.ok{color:#00ff41}
|
||||
.bar{height:3px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden;margin:16px 0}
|
||||
.bar>i{display:block;height:100%;width:30%;background:linear-gradient(90deg,transparent,#00d4ff,transparent);animation:scan 1.6s linear infinite}
|
||||
@keyframes scan{0%{transform:translateX(-120%)}100%{transform:translateX(400%)}}
|
||||
button{margin-top:18px;background:transparent;color:#c9a84c;border:1px solid #c9a84c;border-radius:8px;padding:10px 20px;font:inherit;font-size:.9rem;cursor:pointer;transition:all .2s}
|
||||
button:hover{background:#c9a84c;color:#0a0a0f}
|
||||
.brand{margin-top:28px;font-family:'JetBrains Mono',monospace;font-size:.72rem;color:#6b6b7a;letter-spacing:1px}
|
||||
.brand b{color:#0a5840;filter:brightness(1.6)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="code" id="code">504</div>
|
||||
<h1><span class="dot"></span>Service is slow — recovering</h1>
|
||||
<p>This service took too long to respond through the SecuBox WAF and is recovering.</p>
|
||||
<p>This page checks automatically and will reload the moment it's back — no need to refresh.</p>
|
||||
<div class="bar"><i></i></div>
|
||||
<div class="status" id="status">⟳ checking…</div>
|
||||
<button id="retry" type="button">Retry now</button>
|
||||
<div class="brand">SecuBox<b>·</b> secured edge — self-healing</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
"use strict";
|
||||
var s=document.getElementById('status'),attempt=0,delay=4000,timer=null;
|
||||
function recovered(st){return st>0&&st<500;}
|
||||
function check(){
|
||||
attempt++;
|
||||
s.className='status';s.textContent='⟳ checking… (attempt '+attempt+')';
|
||||
fetch(location.href,{method:'GET',cache:'no-store',redirect:'manual',headers:{'x-secubox-healthcheck':'1'}})
|
||||
.then(function(r){
|
||||
if(recovered(r.type==='opaqueredirect'?200:r.status)){
|
||||
s.className='status ok';s.textContent='✓ back online — reloading…';
|
||||
clearTimeout(timer);setTimeout(function(){location.reload();},600);
|
||||
}else{schedule();}
|
||||
})
|
||||
.catch(function(){schedule();});
|
||||
}
|
||||
function schedule(){
|
||||
delay=Math.min(delay+1000,15000);
|
||||
timer=setTimeout(check,delay);
|
||||
}
|
||||
document.getElementById('retry').addEventListener('click',function(){clearTimeout(timer);delay=4000;check();});
|
||||
timer=setTimeout(check,2500);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -620,9 +620,19 @@ defaults
|
|||
timeout connect 5s
|
||||
timeout client 30s
|
||||
timeout server 30s
|
||||
retries 3
|
||||
option redispatch
|
||||
option httplog
|
||||
option dontlognull
|
||||
option forwardfor
|
||||
# Smart self-healing error pages (#626) — shipped by secubox-haproxy.
|
||||
errorfile 400 /etc/haproxy/secubox-errors/400.http
|
||||
errorfile 403 /etc/haproxy/secubox-errors/403.http
|
||||
errorfile 408 /etc/haproxy/secubox-errors/408.http
|
||||
errorfile 500 /etc/haproxy/secubox-errors/500.http
|
||||
errorfile 502 /etc/haproxy/secubox-errors/502.http
|
||||
errorfile 503 /etc/haproxy/secubox-errors/503.http
|
||||
errorfile 504 /etc/haproxy/secubox-errors/504.http
|
||||
|
||||
frontend stats
|
||||
bind *:${stats_port}
|
||||
|
|
@ -661,16 +671,18 @@ EOF
|
|||
local backend=$(echo "$section" | grep -E '^backend\s*=' | cut -d'"' -f2)
|
||||
local enabled=$(echo "$section" | grep -E '^enabled\s*=' | grep -q 'false' && echo "0" || echo "1")
|
||||
# WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX}
|
||||
[ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ] && continue
|
||||
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
||||
|
||||
[ "$enabled" = "1" ] && [ -n "$domain" ] && {
|
||||
# NB: use if/then/fi, not `[ ] && [ ] && { }` — under `set -e` a
|
||||
# short-circuited &&-chain (e.g. a non-SSL vhost) aborts generation.
|
||||
if [ "$enabled" = "1" ] && [ -n "$domain" ]; then
|
||||
echo " acl host_$name hdr(host) -i $domain"
|
||||
if [ "$waf_enabled" = "1" ]; then
|
||||
echo " use_backend mitmproxy_inspector if host_$name"
|
||||
else
|
||||
echo " use_backend $backend if host_$name"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
done >> "$out"
|
||||
fi
|
||||
|
||||
|
|
@ -709,16 +721,17 @@ EOF
|
|||
local ssl=$(echo "$section" | grep -E '^ssl\s*=' | grep -q 'true' && echo "1" || echo "0")
|
||||
local enabled=$(echo "$section" | grep -E '^enabled\s*=' | grep -q 'false' && echo "0" || echo "1")
|
||||
# WebUI strict-regex already covers admin.${SECUBOX_HOSTNAME}.${SECUBOX_DOMAIN_SUFFIX}
|
||||
[ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ] && continue
|
||||
if [ -n "$_admin_skip_domain" ] && [ "$domain" = "$_admin_skip_domain" ]; then continue; fi
|
||||
|
||||
[ "$enabled" = "1" ] && [ "$ssl" = "1" ] && [ -n "$domain" ] && {
|
||||
# if/then/fi (not `&& { }`) — set -e safe for non-SSL vhosts.
|
||||
if [ "$enabled" = "1" ] && [ "$ssl" = "1" ] && [ -n "$domain" ]; then
|
||||
echo " acl host_$name hdr(host) -i $domain"
|
||||
if [ "$waf_enabled" = "1" ]; then
|
||||
echo " use_backend mitmproxy_inspector if host_$name"
|
||||
else
|
||||
echo " use_backend $backend if host_$name"
|
||||
fi
|
||||
}
|
||||
fi
|
||||
done >> "$out"
|
||||
fi
|
||||
|
||||
|
|
@ -728,7 +741,8 @@ EOF
|
|||
EOF
|
||||
|
||||
# WAF inspector backend (mitmproxy LXC container)
|
||||
[ "$waf_enabled" = "1" ] && cat >> "$out" << EOF
|
||||
# if/then/fi (not `&& cat`) — under set -e a false test here aborts generation.
|
||||
if [ "$waf_enabled" = "1" ]; then cat >> "$out" << EOF
|
||||
# WAF Inspector Backend (mitmproxy LXC at $waf_ip:$waf_port)
|
||||
backend mitmproxy_inspector
|
||||
mode http
|
||||
|
|
@ -738,6 +752,7 @@ backend mitmproxy_inspector
|
|||
server waf ${waf_ip}:${waf_port} check
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
# WebUI direct backend (issue #44 — for the strict-regex ACL above)
|
||||
# Only emit if SECUBOX_HOSTNAME is set (i.e., the strict ACL was injected).
|
||||
|
|
@ -753,6 +768,9 @@ EOF
|
|||
if [ -f "$CONF_PATH" ]; then
|
||||
grep '^\[backends\.' "$CONF_PATH" | while read -r line; do
|
||||
local name=$(echo "$line" | sed 's/\[backends\.//;s/\]//')
|
||||
# Skip backends already emitted (mitmproxy_inspector/webui_direct are
|
||||
# auto-generated above) to avoid duplicate-backend fatal errors.
|
||||
if grep -q "^backend $name\$" "$out"; then continue; fi
|
||||
local section=$(sed -n "/^\[backends\.$name\]/,/^\[/p" "$CONF_PATH" | head -n -1)
|
||||
local mode=$(echo "$section" | grep -E '^mode\s*=' | cut -d'"' -f2 || echo "http")
|
||||
local balance=$(echo "$section" | grep -E '^balance\s*=' | cut -d'"' -f2 || echo "roundrobin")
|
||||
|
|
@ -765,11 +783,11 @@ backend $name
|
|||
EOF
|
||||
local i=0
|
||||
echo "$servers" | while read -r srv; do
|
||||
[ -n "$srv" ] && {
|
||||
if [ -n "$srv" ]; then
|
||||
srv=$(echo "$srv" | tr -d ' ')
|
||||
echo " server srv$i $srv check" >> "$out"
|
||||
i=$((i + 1))
|
||||
}
|
||||
fi
|
||||
done
|
||||
echo "" >> "$out"
|
||||
done
|
||||
|
|
@ -824,6 +842,22 @@ EOF
|
|||
# to the live cfg and only validated at the end, leaving the file in a
|
||||
# broken state on failure (recurring "broken-by-vhost-add" backups).
|
||||
if haproxy -c -f "$out" 2>/dev/null; then
|
||||
# Drift guard (#626): refuse to overwrite a live config that has MORE
|
||||
# vhosts/backends than we just generated — that means the live cfg holds
|
||||
# entries absent from our inputs (haproxy.toml / cfg.d). Without this,
|
||||
# a successful regen would silently drop hand-maintained vhosts (kbin,
|
||||
# gitea, …). `local x=$(...)` masks grep's exit so set -e stays happy.
|
||||
if [ -f "$CONFIG_DIR/haproxy.cfg" ]; then
|
||||
local _nh=$(grep -c '^[[:space:]]*acl host_' "$out")
|
||||
local _oh=$(grep -c '^[[:space:]]*acl host_' "$CONFIG_DIR/haproxy.cfg")
|
||||
local _nb=$(grep -c '^backend ' "$out")
|
||||
local _ob=$(grep -c '^backend ' "$CONFIG_DIR/haproxy.cfg")
|
||||
if [ "${_nh:-0}" -lt "${_oh:-0}" ] || [ "${_nb:-0}" -lt "${_ob:-0}" ]; then
|
||||
error "Drift guard: generated cfg has fewer vhosts/backends than live (acl ${_nh}<${_oh} or backend ${_nb}<${_ob}) — refusing to clobber. Migrate the missing entries into haproxy.toml/cfg.d first."
|
||||
rm -f "$out"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
install -m 0644 -o root -g root "$out" "$CONFIG_DIR/haproxy.cfg"
|
||||
rm -f "$out"
|
||||
log "Configuration generated, validated and installed: $CONFIG_DIR/haproxy.cfg"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
secubox-hub (1.4.5-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* feat(health): Health Monitor page (/health/) — live status of vital +
|
||||
common services from public/health-batch, grouped + severity-sorted,
|
||||
auto-refresh, dark hybrid-skin. Sidebar menu entry (closes #628).
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Tue, 17 Jun 2026 10:00:00 +0200
|
||||
|
||||
secubox-hub (1.4.4-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(perf): dashboard/services cache never warmed under the aggregator,
|
||||
|
|
|
|||
9
packages/secubox-hub/menu.d/05-health.json
Normal file
9
packages/secubox-hub/menu.d/05-health.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "health",
|
||||
"name": "Health Monitor",
|
||||
"category": "root",
|
||||
"icon": "💚",
|
||||
"path": "/health/",
|
||||
"order": 5,
|
||||
"description": "Live status of vital and common SecuBox services"
|
||||
}
|
||||
51
packages/secubox-hub/www/health/health.css
Normal file
51
packages/secubox-hub/www/health/health.css
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
SecuBox-Deb :: Hub Health Monitor — dark hybrid-skin styles */
|
||||
:root {
|
||||
--gmb-h: 48px; --sp-s: 8px; --sp-m: 16px; --sp-l: 24px; --sp-xl: 40px; --r-card: 14px;
|
||||
--bg0: var(--bg-dark, #0A0E14); --bg1: var(--surface-dark, #141A24);
|
||||
--bd: var(--border-dark, #2A3444); --tx: var(--text-dark, #E8E6E0); --mut: var(--muted-dark, #8A9AA8);
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: var(--font-body, 'Space Grotesk', system-ui, sans-serif); font-size: 14px;
|
||||
line-height: 1.6; background: var(--bg0); color: var(--tx); min-height: 100vh; }
|
||||
.main { margin-left: 220px; padding: calc(var(--gmb-h) + var(--sp-l)) var(--sp-l) var(--sp-xl); max-width: 1280px; }
|
||||
@media (max-width: 768px) { .main { margin-left: 0; padding: calc(var(--gmb-h) + var(--sp-m)) var(--sp-m) var(--sp-l); } }
|
||||
|
||||
header.page { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-m);
|
||||
margin-bottom: var(--sp-l); padding-bottom: var(--sp-m); border-bottom: 1px solid var(--bd); }
|
||||
header.page h1 { font-size: 24px; font-weight: 600; letter-spacing: -0.4px; color: var(--root-light, #148C66); }
|
||||
header.page .ver { font-family: var(--font-mono, monospace); font-size: 12px; color: var(--mut); margin-left: var(--sp-s); }
|
||||
.actions { display: flex; gap: var(--sp-m); align-items: center; }
|
||||
.updated { font-family: var(--font-mono, monospace); font-size: 11px; color: var(--mut); }
|
||||
.btn { background: var(--bg1); color: var(--tx); border: 1px solid var(--bd); border-radius: 6px;
|
||||
padding: 6px 12px; cursor: pointer; font: inherit; font-size: 13px; }
|
||||
.btn:hover { border-color: var(--root-light, #148C66); }
|
||||
h2 { font-size: 16px; font-weight: 600; margin: var(--sp-xl) 0 var(--sp-m); }
|
||||
.count { font-family: var(--font-mono, monospace); font-size: 12px; color: var(--mut); font-weight: 400; }
|
||||
.banner { padding: var(--sp-m); border-radius: 8px; margin-bottom: var(--sp-m); font-size: 13px; }
|
||||
.banner.info { background: var(--bg1); color: var(--mut); border: 1px solid var(--bd); }
|
||||
.banner.err { background: rgba(192,64,64,.12); color: #E8845A; border: 1px solid #803018; }
|
||||
|
||||
/* Summary */
|
||||
.summary { display: flex; gap: var(--sp-m); flex-wrap: wrap; }
|
||||
.sum { flex: 1; min-width: 120px; background: var(--bg1); border: 1px solid var(--bd);
|
||||
border-radius: var(--r-card); padding: var(--sp-m); text-align: center; }
|
||||
.sum b { display: block; font-size: 2rem; font-weight: 700; font-family: var(--font-mono, monospace); line-height: 1; }
|
||||
.sum span { font-size: 12px; color: var(--mut); }
|
||||
.sum.ok b { color: #2ecc8f; } .sum.warn b { color: #f0b94c; } .sum.err b { color: #ff7a6b; } .sum.total b { color: var(--tx); }
|
||||
.sum.ok { border-left: 3px solid #0A5840; } .sum.warn { border-left: 3px solid #9A6010; } .sum.err { border-left: 3px solid #803018; }
|
||||
|
||||
/* Service grid */
|
||||
.svc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--sp-s); }
|
||||
.svc { display: flex; align-items: center; gap: var(--sp-s); background: var(--bg1); border: 1px solid var(--bd);
|
||||
border-radius: 8px; padding: 10px 12px; min-width: 0; }
|
||||
.svc .led { width: 9px; height: 9px; border-radius: 50%; flex: none; box-shadow: 0 0 6px currentColor; }
|
||||
.svc.ok .led { background: #2ecc8f; color: #2ecc8f; }
|
||||
.svc.warn .led, .svc.unknown .led { background: #f0b94c; color: #f0b94c; }
|
||||
.svc.error .led { background: #ff7a6b; color: #ff7a6b; animation: pulse 1.2s infinite; }
|
||||
@keyframes pulse { 50% { opacity: .4; } }
|
||||
.svc.error { border-left: 3px solid #803018; }
|
||||
.svc-name { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.svc-msg { margin-left: auto; font-size: 11px; color: var(--mut); font-family: var(--font-mono, monospace);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
|
||||
.empty { color: var(--mut); font-size: 13px; padding: var(--sp-m); }
|
||||
89
packages/secubox-hub/www/health/health.js
Normal file
89
packages/secubox-hub/www/health/health.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
// Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
// SecuBox-Deb :: Hub Health Monitor — CyberMind https://cybermind.fr
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const BATCH = '/api/v1/hub/public/health-batch';
|
||||
const INFO = '/api/v1/hub/public/info';
|
||||
const REFRESH_MS = 15000;
|
||||
|
||||
// Vital services — the security/serving spine; everything else is "common".
|
||||
const VITAL = ['waf', 'crowdsec', 'mitmproxy', 'haproxy', 'aggregator', 'hub',
|
||||
'system', 'vortex-dns', 'dns-guard', 'certs', 'wireguard', 'soc',
|
||||
'metrics', 'core'];
|
||||
const VITAL_SET = new Set(VITAL);
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const esc = (s) => String(s == null ? '' : s)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
async function getJSON(u) {
|
||||
const r = await fetch(u, { headers: { 'Accept': 'application/json' }, cache: 'no-store' });
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
}
|
||||
|
||||
function chip(id, st) {
|
||||
const status = (st && st.status) || 'unknown';
|
||||
const msg = (st && st.msg) || '';
|
||||
return `<div class="svc ${status}" title="${esc(id)}: ${esc(msg)}">
|
||||
<span class="led"></span>
|
||||
<span class="svc-name">${esc(id)}</span>
|
||||
<span class="svc-msg">${esc(msg)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function render(modules) {
|
||||
const ids = Object.keys(modules).sort();
|
||||
let ok = 0, warn = 0, err = 0;
|
||||
ids.forEach((id) => {
|
||||
const s = (modules[id] || {}).status;
|
||||
if (s === 'ok') ok++; else if (s === 'error') err++; else warn++;
|
||||
});
|
||||
|
||||
$('summary').innerHTML =
|
||||
`<div class="sum ok"><b>${ok}</b><span>healthy</span></div>` +
|
||||
`<div class="sum warn"><b>${warn}</b><span>degraded</span></div>` +
|
||||
`<div class="sum err"><b>${err}</b><span>down</span></div>` +
|
||||
`<div class="sum total"><b>${ids.length}</b><span>services</span></div>`;
|
||||
|
||||
const vital = ids.filter((id) => VITAL_SET.has(id));
|
||||
const common = ids.filter((id) => !VITAL_SET.has(id));
|
||||
// Sort each: errors first, then warn, then ok, so problems surface.
|
||||
const rank = (id) => ({ error: 0, warn: 1, unknown: 1, ok: 2 }[(modules[id] || {}).status] ?? 1);
|
||||
const bySeverity = (a, b) => rank(a) - rank(b) || a.localeCompare(b);
|
||||
|
||||
$('vital').innerHTML = vital.sort(bySeverity).map((id) => chip(id, modules[id])).join('')
|
||||
|| '<div class="empty">no vital services reported</div>';
|
||||
$('common').innerHTML = common.sort(bySeverity).map((id) => chip(id, modules[id])).join('')
|
||||
|| '<div class="empty">none</div>';
|
||||
$('vitalCount').textContent = '(' + vital.length + ')';
|
||||
$('commonCount').textContent = '(' + common.length + ')';
|
||||
}
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const batch = await getJSON(BATCH);
|
||||
// health-batch returns {modules: {id: {status,msg}}, count: N}
|
||||
const modules = (batch && batch.modules) || batch || {};
|
||||
render(modules);
|
||||
$('updated').textContent = 'updated ' + new Date().toLocaleTimeString();
|
||||
$('loading').hidden = true; $('error').hidden = true; $('content').hidden = false;
|
||||
getJSON(INFO).then((i) => {
|
||||
if (i && i.hostname) $('ver').textContent = esc(i.hostname);
|
||||
}).catch(() => {});
|
||||
} catch (e) {
|
||||
$('error').hidden = false;
|
||||
$('error').textContent = 'Could not load health: ' + e.message;
|
||||
$('loading').hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
$('refresh').addEventListener('click', load);
|
||||
load();
|
||||
setInterval(load, REFRESH_MS);
|
||||
});
|
||||
})();
|
||||
54
packages/secubox-hub/www/health/index.html
Normal file
54
packages/secubox-hub/www/health/index.html
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
SecuBox · Health Monitor — live status of vital + common services.
|
||||
Consumes /api/v1/hub/public/health-batch. Dark hybrid-skin via /shared/sidebar.js.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
|
||||
<title>SecuBox · Health Monitor</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/shared/design-tokens.css">
|
||||
<link rel="stylesheet" href="/shared/sidebar.css">
|
||||
<link rel="stylesheet" href="health.css">
|
||||
</head>
|
||||
<body class="module-root">
|
||||
<nav class="sidebar" id="sidebar"></nav>
|
||||
<main class="main">
|
||||
<header class="page">
|
||||
<h1>💚 Health Monitor <span class="ver" id="ver">v1.0</span></h1>
|
||||
<div class="actions">
|
||||
<span class="updated" id="updated">—</span>
|
||||
<button class="btn" id="refresh" type="button">↻ Refresh</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="error" class="banner err" hidden></div>
|
||||
<div id="loading" class="banner info">Loading service health…</div>
|
||||
|
||||
<div id="content" hidden>
|
||||
<!-- Summary strip -->
|
||||
<section class="summary" id="summary"></section>
|
||||
|
||||
<!-- Vital services -->
|
||||
<section>
|
||||
<h2>Vital services <span class="count" id="vitalCount"></span></h2>
|
||||
<div class="svc-grid" id="vital"></div>
|
||||
</section>
|
||||
|
||||
<!-- Common services -->
|
||||
<section>
|
||||
<h2>Common services <span class="count" id="commonCount"></span></h2>
|
||||
<div class="svc-grid" id="common"></div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="/shared/sidebar.js"></script>
|
||||
<script src="health.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
46
packages/secubox-mitmproxy/bin/secubox-waf-watchdog.sh
Executable file
46
packages/secubox-mitmproxy/bin/secubox-waf-watchdog.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
# Source-Disclosed License — All rights reserved except as expressly granted.
|
||||
# See LICENCE-CMSD-1.0.md for terms.
|
||||
|
||||
# SecuBox-Deb :: WAF inspector watchdog (#624)
|
||||
# Auto-recover the mitmproxy LXC if its inspector port is down for MAXFAIL
|
||||
# consecutive checks. Rate-limited by COOLDOWN to avoid restart flap. The whole
|
||||
# point: an inspector crash becomes a ~3-minute auto-recovery instead of a
|
||||
# multi-hour board-wide 503.
|
||||
set -uo pipefail
|
||||
|
||||
readonly HOST="${SECUBOX_WAF_HOST:-10.100.0.60}"
|
||||
readonly PORT="${SECUBOX_WAF_PORT:-8080}"
|
||||
readonly CTN="${SECUBOX_WAF_CTN:-mitmproxy}"
|
||||
readonly STATE="/run/secubox-waf-watchdog.state"
|
||||
readonly MAXFAIL="${SECUBOX_WAF_MAXFAIL:-3}"
|
||||
readonly COOLDOWN="${SECUBOX_WAF_COOLDOWN:-600}"
|
||||
|
||||
now="$(date +%s)"
|
||||
fails=0
|
||||
last=0
|
||||
if [ -f "$STATE" ]; then
|
||||
read -r fails last < "$STATE" 2>/dev/null || true
|
||||
fi
|
||||
[[ "$fails" =~ ^[0-9]+$ ]] || fails=0
|
||||
[[ "$last" =~ ^[0-9]+$ ]] || last=0
|
||||
|
||||
# Healthy → reset the failure counter and exit.
|
||||
if timeout 4 bash -c "echo >/dev/tcp/$HOST/$PORT" 2>/dev/null; then
|
||||
echo "0 $last" > "$STATE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
fails=$((fails + 1))
|
||||
logger -t secubox-waf-watchdog "inspector $HOST:$PORT DOWN (fail $fails/$MAXFAIL)"
|
||||
|
||||
if [ "$fails" -ge "$MAXFAIL" ] && [ $((now - last)) -ge "$COOLDOWN" ]; then
|
||||
logger -t secubox-waf-watchdog "auto-recovering container $CTN"
|
||||
timeout 45 lxc-stop -n "$CTN" -k 2>/dev/null || true
|
||||
timeout 45 lxc-start -n "$CTN" -d 2>/dev/null || true
|
||||
echo "0 $now" > "$STATE"
|
||||
else
|
||||
echo "$fails $last" > "$STATE"
|
||||
fi
|
||||
|
|
@ -1,3 +1,17 @@
|
|||
secubox-mitmproxy (1.0.9-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* feat(robustness): self-healing WAF inspector watchdog (closes #624).
|
||||
- secubox-waf-watchdog.{sh,service,timer}: every 60s check inspector
|
||||
:8080; after 3 consecutive failures auto-recover the mitmproxy LXC
|
||||
(lxc-stop -k / lxc-start), rate-limited once/10min. Turns an inspector
|
||||
crash from a multi-hour board-wide 503 into a ~3-minute auto-recovery.
|
||||
- Enabled in postinst, stopped/disabled in prerm.
|
||||
* fix(service): secubox-mitmproxy.service ran /usr/bin/uvicorn (absent →
|
||||
203/EXEC crash-loop). Use portable `python3 -m uvicorn` + unlink stale
|
||||
socket on (re)start.
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Tue, 17 Jun 2026 08:00:00 +0200
|
||||
|
||||
secubox-mitmproxy (1.0.8-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(waf): live-reload haproxy-routes.json on change (#609). The addon now
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ EOF
|
|||
systemctl enable secubox-mitmproxy.service
|
||||
systemctl start secubox-mitmproxy.service || true
|
||||
|
||||
# WAF inspector self-healing watchdog (#624)
|
||||
systemctl enable secubox-waf-watchdog.timer 2>/dev/null || true
|
||||
systemctl start secubox-waf-watchdog.timer 2>/dev/null || true
|
||||
|
||||
# Reload nginx if installed
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ case "$1" in
|
|||
remove|purge)
|
||||
systemctl stop secubox-mitmproxy.service || true
|
||||
systemctl disable secubox-mitmproxy.service || true
|
||||
systemctl stop secubox-waf-watchdog.timer 2>/dev/null || true
|
||||
systemctl disable secubox-waf-watchdog.timer 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
#DEBHELPER#
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ override_dh_auto_install:
|
|||
install -m 644 debian/mitmproxy.toml $(CURDIR)/debian/secubox-mitmproxy/etc/secubox/
|
||||
install -d $(CURDIR)/debian/secubox-mitmproxy/usr/lib/systemd/system
|
||||
install -m 644 debian/secubox-mitmproxy.service $(CURDIR)/debian/secubox-mitmproxy/usr/lib/systemd/system/
|
||||
# WAF inspector watchdog (#624) — auto-recover the mitmproxy LXC if :8080 dies
|
||||
install -m 755 bin/secubox-waf-watchdog.sh $(CURDIR)/debian/secubox-mitmproxy/usr/sbin/
|
||||
install -m 644 debian/secubox-waf-watchdog.service $(CURDIR)/debian/secubox-mitmproxy/usr/lib/systemd/system/
|
||||
install -m 644 debian/secubox-waf-watchdog.timer $(CURDIR)/debian/secubox-mitmproxy/usr/lib/systemd/system/
|
||||
install -d $(CURDIR)/debian/secubox-mitmproxy/etc/nginx/secubox.d
|
||||
install -m 644 nginx/mitmproxy.conf $(CURDIR)/debian/secubox-mitmproxy/etc/nginx/secubox.d/
|
||||
# Cookie audit (issue #156) hardening — AppArmor abstraction + logrotate (issue #170)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# Automatically added by dh_installtmpfiles/13.14.1ubuntu5
|
||||
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
|
||||
if [ -x "$(command -v systemd-tmpfiles)" ]; then
|
||||
systemd-tmpfiles ${DPKG_ROOT:+--root="$DPKG_ROOT"} --create secubox-thp.conf || true
|
||||
fi
|
||||
fi
|
||||
# End automatically added section
|
||||
|
|
@ -8,10 +8,13 @@ Type=simple
|
|||
User=secubox
|
||||
Group=secubox
|
||||
WorkingDirectory=/usr/lib/secubox/mitmproxy
|
||||
ExecStart=/usr/bin/uvicorn api.main:app \
|
||||
ExecStartPre=-/bin/rm -f /run/secubox/mitmproxy.sock
|
||||
ExecStart=/usr/bin/python3 -m uvicorn api.main:app \
|
||||
--uds /run/secubox/mitmproxy.sock \
|
||||
--log-level warning
|
||||
ExecStartPost=/bin/chmod 660 /run/secubox/mitmproxy.sock
|
||||
# Wait for uvicorn to bind the uds before chmod; non-fatal so a race never
|
||||
# fails the unit (Type=simple runs ExecStartPost without waiting for readiness).
|
||||
ExecStartPost=-/bin/sh -c 'for i in 1 2 3 4 5 6; do [ -S /run/secubox/mitmproxy.sock ] && exec chmod 660 /run/secubox/mitmproxy.sock; sleep 0.5; done'
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=SecuBox WAF inspector watchdog (auto-recover mitmproxy LXC)
|
||||
Documentation=https://github.com/CyberMind-FR/secubox-deb
|
||||
ConditionPathExists=/usr/sbin/secubox-waf-watchdog.sh
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/secubox-waf-watchdog.sh
|
||||
Nice=10
|
||||
10
packages/secubox-mitmproxy/debian/secubox-waf-watchdog.timer
Normal file
10
packages/secubox-mitmproxy/debian/secubox-waf-watchdog.timer
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Run the SecuBox WAF inspector watchdog every minute
|
||||
|
||||
[Timer]
|
||||
OnBootSec=120
|
||||
OnUnitActiveSec=60
|
||||
AccuracySec=10s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
secubox-toolbox (2.6.41-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* perf(ttfb): stream_inject now defaults ON (#630) — phase-2 streaming loader
|
||||
inject is the default (workers ~71MB/8% vs ~100MB/28%). Operators can still
|
||||
toggle it off via /etc/secubox/toolbox/filters.json.
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Tue, 17 Jun 2026 11:00:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.40-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(postinst): stop clobbering shared parent dir modes (#620). The
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ DEFAULTS: Dict = {
|
|||
"ad_ghost": True, # R3+/R4 silent ad/banner/widget ghosting
|
||||
"ad_ghost_block": True, # 204 known ad/tracker hosts (save bandwidth)
|
||||
"media_cache": False, # #577 shared media proxy-cache (opt-in)
|
||||
"stream_inject": False, # #620 stream loader inject (TTFB) — opt-in, phase 2
|
||||
"stream_inject": True, # #620/#630 stream loader inject (TTFB) — default on
|
||||
"autolearn": True, # #589 also block auto-learned bad hosts
|
||||
"ad_ghost_categories": { # cosmetic ghost groups
|
||||
"ads": True,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user