Commit Graph

307 Commits (v0.9.45)

Author SHA1 Message Date
boban 60e1edc0d5 feat(wg): configurable DNS + Server/Peers tabs + faster offline (v0.9.45)
Three things the operator asked for on the WireGuard page:

- DNS is no longer hardcoded to 1.1.1.1. New host action set-dns (_set_dns +
  cmd_set_dns + write-bridge branch + WgBridge validation, IPv4 list only),
  stored as WG_DNS in wg.env (appended for pre-existing tunnels), surfaced via
  the collector + WgStatus, baked into new peer configs (DNS = ${WG_DNS:-1.1.1.1}).
  Editable in the Server tab; e.g. point clients at your own gateway.
- The page is split into two independent tabs: "Peers" (list / add / remove /
  traffic) and "Server configuration" (endpoint / DNS / port / subnet / gate /
  SSH lock + server identity). #[Url] $tab makes it deep-linkable (?tab=server).
- Offline detection: ONLINE_WITHIN 180s -> 150s (PersistentKeepalive=25 keeps
  active peers re-handshaking well within it, so no flapping; a disconnect now
  shows offline ~2.5 min sooner). Endpoint hint clarified: the port is optional
  (auto-appended), no need to repeat the listen port.

Tests: set-dns (write+audit, reject), tab switch/clamp, dns in status; the two
configured-render tests now switch to the server tab for server content. 353
pass, shellcheck clean, Pint clean, both tabs load 200 with no console errors,
lang parity 89/89.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 22:48:46 +02:00
boban 920dfb02bf feat(wg): compact QR + meaningful download filename (v0.9.44)
- QR: 0.9.42 over-enlarged it; bring the show-once peer QR back to a balanced
  ~240px (still crispEdges on a white quiet-zone frame) so it scans on a phone
  without dominating the modal.
- Download filename now becomes a useful tunnel name on import: build it from
  the endpoint host + peer name (e.g. "10.10.90.165-laptop.conf") instead of the
  generic "clusev-wireguard.conf" — WireGuard apps name a tunnel after the file.
  A QR scan can't carry a name (the app prompts for one); the import hint now
  explains both paths.

Tests: download-filename cases (host+peer, fallback); 349 pass, Pint clean,
/wireguard loads 200, lang parity 83/83.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 22:25:41 +02:00
boban dc6d2fae59 fix(wg): endpoint auto-gets the listen port when given as a bare host (v0.9.43)
If the operator entered an endpoint without a port (e.g. "10.10.90.165"
instead of "10.10.90.165:51820"), that value was written verbatim into every
peer config. WireGuard requires the endpoint as host:port, so the WireGuard app
rejected the QR/file import as "keine gültige WireGuard-Konfiguration" — the
real cause of the invalid-config errors (the QR-size fix in 0.9.42 was secondary).

Normalise host-side: append the listen port when the endpoint has no colon, in
both _write_server_conf (setup — covers the dashboard and interactive paths) and
_set_endpoint (later changes). Verified: bare host -> host:port, host:port left
as-is, DNS host -> host:port; write-bridge smoke test still passes; shellcheck
clean. setup_endpoint_hint (de/en) now documents the host:port format.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 22:11:25 +02:00
boban 9bb9f2561b fix(wg): peer QR is now scannable on a phone (v0.9.42)
The show-once peer config QR rendered at 176px (~2.7px per module) with
anti-aliasing on. A WireGuard config QR is dense (~57 modules), so a phone
camera misread it and the WireGuard app rejected the result as "keine gültige
WireGuard-Konfiguration". Render it large (up to ~300px) on a white framed
quiet zone with shape-rendering:crispEdges, so modules stay sharp and ~4.6px
each. The Download button (.conf import on a PC) remains as the alternative.

Verified: old-vs-new render comparison (2.7 → 4.6 px/module, crisp modules);
Tailwind compiles the arbitrary shape-rendering property; /wireguard loads 200
with no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 22:06:42 +02:00
boban 7794e98997 fix(wg): write-bridge handler no longer dies before replying (v0.9.41)
The host handler `clusev-wg.sh serve-request` aborted under `set -euo pipefail`
BEFORE writing run/wg-result-<id>.json, so the dashboard waited ~30s and showed
"Keine Antwort vom Host" even though the systemd watcher was running correctly.
The write-bridge had therefore never actually worked on a real deployment.

Two root causes, both fixed:

1. Field extraction: each `id/action/name/endpoint/port/subnet` value is pulled
   with `grep -oE | head | sed`. A field that is empty or absent (an "auto"
   endpoint at setup; gate/add-peer/remove-peer payloads that carry no
   endpoint/port/subnet) makes grep fail → with pipefail the whole pipeline
   fails → `set -e` aborts the handler before _write_result. Appended `|| true`
   to every extraction so a non-match yields "" instead of killing the handler.

2. Action dispatch: gate-up/gate-down/gate-ssh-on/off and set-endpoint/port/
   subnet (and remove-peer) called helpers that run require_setup, which `die`s
   (exit) when the tunnel isn't configured yet — aborting serve-request before
   the reply. Each handler now runs in a subshell `( ... )` so an internal exit
   becomes a caught non-zero and still yields a clean failure result.
   (add-peer/setup were already isolated via their $(...) command substitution.)

Verified end-to-end: all ten actions now write a result for minimal/empty-field
payloads instead of timing out. New smoke test docker/wg/serve-request.test.sh
(root-bypassed, no WireGuard needed) guards both regressions. shellcheck clean,
348 PHP tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:53:25 +02:00
boban 20aec565ed docs: enterprise README for Gitea — branded hero + architecture diagram
Rebuild the README around the product's "Tactical Terminal" design system
(dark graphite, signal-orange, cyan, mono type) so it reads as a professional,
self-hosted enterprise product instead of a plain text file:

- docs/assets/clusev-banner.svg — hero banner with the Clusev wordmark, tagline,
  brand chips, and a live "fleet" node-graph (control-plane hub → server cards
  with the online/warning/offline status triad + CPU bars and animated data
  pulses). Self-contained: the real product fonts (Chakra Petch + JetBrains Mono)
  are embedded base64 so Gitea's <img>-rendered SVG shows pixel-correct type.
- docs/assets/clusev-architecture.svg — Operator → Control plane → Fleet diagram
  (HTTPS/WS · Reverb in, SSH+SFTP · phpseclib out), same brand language.
- README.md — centered hero, brand-matched shields badges, anchor nav, a
  "why" three-up, a Features table, the architecture diagram + a tech-stack
  table, and the existing install / domain-TLS / updating / security-recovery
  content kept verbatim (now with a WireGuard feature row). All graphics are
  external SVG files (Gitea strips inline <svg>); relative paths resolve on the
  default branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:52:00 +02:00
boban 9c0d390365 refactor(wireguard): remove dead code + DRY the WG surface (no behavior change)
Verified dead-code + duplication cleanup over the WireGuard feature surface
(found via an audit workflow, every item adversarially re-verified by grep):

Dead code removed:
- lang keys not_configured_title / not_configured_body (both locales) — orphaned
  when the unconfigured empty state became the in-page setup form (setup_title /
  setup_intro). 0 references anywhere. de/en parity kept (83/83).
- WgTraffic::series() 'buckets' return key — written, never read by blade/route/test.
- blade $fmtB byte-formatter — byte-identical duplicate of the top-level $fmtBytes
  already in scope; collapsed onto $fmtBytes.

DRY / readability (behavior-preserving):
- Index: PEER_NAME_RE constant (was the same regex literal at 5 sites), portInRange()
  + clampWindow() helpers (window default now self::WINDOWS[0]), openConfirm() helper
  (5 confirm-modal openers shared one dispatch), onFlag() helper (2 apply handlers).
- WgBridge: validPort() helper (port check was duplicated verbatim).
- clusev-wg.sh: _write_server_conf() shared by both setup paths (the byte-identical
  server bring-up was copy-pasted); single CIDR_RE constant for the 3 subnet checks;
  fixed a stale header comment that still claimed SSH is "NEVER matched" (the v0.9.40
  ssh-lock can match 22); documented the defence-in-depth re-validation in _set_*.
- WgStatus: documented server.pubkey is part of the /wg-status.json contract (kept).

No DB/migration changes (peer_name write-only column left intact — needs a
coordinated migration). 348 tests pass, shellcheck clean, Pint clean, R12 verified
in both the configured + unconfigured states (200, no console errors, no leaked
keys, traffic formatter renders 1.5 MB / 200 KB correctly).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:33:41 +02:00
boban 2b245c3d3a feat(wireguard): SSH lock + peer config download (v0.9.40)
Two dashboard additions to the WireGuard page:

- Peer config download: the show-once new-client view gets a "Download"
  button next to the QR code (streams clusev-wireguard.conf) plus an
  inline hint explaining QR-on-phone vs download-on-PC import.

- SSH lock (port 22): an optional toggle in WireGuard settings that walls
  off host SSH to the tunnel via a dedicated CLUSEV-WG-SSH chain on the
  host INPUT chain. Fail-open by construction: ESTABLISHED,RELATED + lo +
  wg0 + WG-subnet RETURN before the DROP (live sessions survive, SSH over
  the tunnel stays open); _ssh_gate_on refuses while wg0 is down; the boot
  unit's ConditionPathExists=wg0 and an in-function self-guard keep a
  broken tunnel from ever applying the DROP; ssh_gate_apply re-validates
  the subnet shape and fails open on a corrupt wg.env. Strong lock-out
  warning shown inline AND in the confirm modal (recommend a backup peer).
  New CLI escapes: clusev wg ssh-lock / ssh-unlock; clusev wg down clears
  both gates. Panel gate and SSH gate are independent toggles.

Write-bridge: new gate-ssh-on/gate-ssh-off actions (no args, host
whitelist + ConfirmToken wgSshGate, single-use uid-bound). Status collector
now reports gate.ssh. Adversarial lock-out review: all 8 failure modes
refuted. 348 tests pass, shellcheck clean, Pint clean, R12 verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 09:25:19 +02:00
boban 94425f93e8 fix(wg): remove SSH hint from setup form + time out a non-responding host (no endless spinner)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:38:43 +02:00
boban a064e8eeb8 chore: release 0.9.38 — WireGuard first-time setup from the dashboard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:16:40 +02:00
boban 8e05cf62ec feat(wg): set WireGuard up from the dashboard (setup form + bridge action)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 08:09:35 +02:00
boban 01778bde80 feat(wg): non-interactive setup action for the dashboard write-bridge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 08:06:00 +02:00
boban 6f9091d3fa docs(wg): SP2 Phase 5 plan — first-time setup from the dashboard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:04:21 +02:00
boban 2d12978616 chore: release 0.9.37 — WireGuard settings + gate from the dashboard (SP2 phase 4, complete)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:03:05 +02:00
boban 858a00955a fix(wg): single audit per confirm action (no descriptor double-audit)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 01:00:26 +02:00
boban 7ccefa5890 feat(wg): gate toggle + endpoint/port/subnet settings from the dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 00:53:29 +02:00
boban 7ff02c4e68 feat(wg): set-endpoint/set-port/set-subnet host actions + serve-request branches 2026-06-21 00:49:55 +02:00
boban 95a5811544 docs(wg): SP2 Phase 4 plan — settings + gate from the UI
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:48:11 +02:00
boban e006de4962 chore: release 0.9.36 — WireGuard peer management from the dashboard (SP2 phase 3)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:45:10 +02:00
boban b1387bd654 fix(wg): umask 077 in serve-request + UMask in unit — no 0644 race on key files (security review)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:42:07 +02:00
boban ee8f2bac91 feat(wg): add/remove peers from the dashboard (show-once config + QR)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 00:36:26 +02:00
boban ed5506e558 feat(wg): WgBridge — app side of the write-bridge (validate + request + result) 2026-06-21 00:32:02 +02:00
boban a6bb8b7da4 feat(wg): clusev-wg-request watcher (path+service) + install wiring
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:30:12 +02:00
boban 695b33cf37 feat(wg): serve-request write-bridge + peer/gate cores (host-side validation)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:28:34 +02:00
boban f91d8ed834 docs(wg): SP2 Phase 3 plan — peer management + write-bridge
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:24:44 +02:00
boban 037d285148 chore: release 0.9.35 — WireGuard traffic history + scheduler (SP2 phase 2)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:13:30 +02:00
boban 0c9aa12aab feat(wg): traffic-history SVG chart + window selector on /wireguard 2026-06-21 00:07:18 +02:00
boban e1fb24203d feat(ops): add a scheduler service (schedule:work) — runs wg-sample + prune-audit
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:05:14 +02:00
boban 49f70c0c9f fix(wg): WgTraffic up = throughput delta (not raw tx) — consistent with down
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:04:07 +02:00
boban 130e1aeaf6 feat(wg): WgTraffic — bucketed down/up throughput series 2026-06-21 00:02:35 +02:00
boban 9e6f3775b7 feat(wg): clusev:wg-sample command (sample peers + prune) + schedule 2026-06-21 00:00:06 +02:00
boban ae4937654d feat(wg): wg_traffic_samples table + model 2026-06-20 23:59:11 +02:00
boban eff836a1fb docs(wg): SP2 Phase 2 plan — traffic history + SVG chart
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:56:51 +02:00
boban 16b854ae50 chore: release 0.9.34 — WireGuard dashboard live-status (SP2 phase 1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:44:22 +02:00
boban 96349808e2 chore(wg): drop unused wireguard lang keys (server_key, transfer)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:41:18 +02:00
boban e640a96ca0 feat(wg): /wireguard live-status page (read-only) + nav (DE/EN)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 23:35:58 +02:00
boban 9b835e9dbe feat(wg): WgStatus reader + auth-gated /wg-status.json route 2026-06-20 23:32:44 +02:00
boban 9aabd7fce5 feat(wg): install + enable the WireGuard status collector timer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 23:30:47 +02:00
boban 3d73489717 feat(wg): clusev-wg-collect timer+service — 5s status collection 2026-06-20 23:29:21 +02:00
boban c164c0aec3 feat(wg): clusev-wg.sh collect — emit run/wg-status.json for the dashboard 2026-06-20 23:27:58 +02:00
boban dfdb0ce732 docs(wg): SP2 Phase 1 plan — live-status dashboard page
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:26:15 +02:00
boban 19fe1b8915 docs(wg): SP2 design — full WireGuard management in the dashboard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:21:45 +02:00
boban bc82cdaff5 chore: release 0.9.33 — WireGuard gate + clusev wg CLI (SP1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:59:07 +02:00
boban 287f962745 harden(wg): validate peer name charset in add-peer/remove-peer (final review)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:57:59 +02:00
boban 0fa11bd0ea style(install): quote set_stage "done" arg to silence shellcheck SC1010
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:52:50 +02:00
boban fa7e1c428e docs(wg): manual runbook for the WireGuard gate (SP1)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:50:54 +02:00
boban c9556bffbf feat(wg): WireGuard help topic (registration + DE/EN content + test) 2026-06-20 22:46:04 +02:00
boban 0342d4c6e0 feat(wg): wire 'clusev wg' into the host CLI wrapper 2026-06-20 22:42:16 +02:00
boban 51c9702dcf feat(wg): install wireguard-tools/qrencode + enable the gate unit (inert by default) 2026-06-20 22:38:00 +02:00
boban 1e9834fc0d feat(wg): clusev-wg-gate.service — boot/docker-restart gate persistence 2026-06-20 22:35:25 +02:00