Commit Graph

372 Commits (d3b2ad2f237bcfbe7a67423c57e1b5d3b4db032c)

Author SHA1 Message Date
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
boban 8caa78b2b3 fix(wg): harden clusev-wg.sh — read EOF, awk ENVIRON, endpoint + wg.env guards (review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 22:33:37 +02:00
boban dd964a2e8b feat(wg): clusev-wg.sh — WireGuard gate + peer CLI (host) 2026-06-20 22:25:40 +02:00
boban f154ee8aaf docs(wg): SP1 implementation plan — WireGuard gate + clusev wg CLI
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:20:51 +02:00
boban 084c21a869 feat(update): real phase progress feed + deep-linkable changelog series/page
Update-progress phases were a pure time guess, so a fast update sat on "fetch"
then snapped all four green at once. Now the host updater publishes its real
macro-stage and the page tracks it:

- update.sh / install.sh write the current stage (fetch|build|restart|migrate|
  done) to run/update-phase.json — best-effort (|| true, never aborts an update).
- new public GET /update-status.json serves that stage (whitelisted) to the page.
- update-progress.blade.php drives the checklist from the feed in REAL order
  (fetch → build → restart → migrate), falling back to the time heuristic when no
  feed is present, and completes on the feed's 'done' (or the version flip, with a
  25s grace fail-safe). Like the 502 fix, the live experience lands one update
  after this ships (the page shown DURING an update is the old version's).

Also: Versions changelog series + page are now #[Url]-synced (?series=&page=),
so a series/page is shareable and survives reload / back-button.

Tests: /update-status.json (null / whitelisted / rejected stage), the page polls
the feed, and the changelog deep-link reads ?series=&page=.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:08:41 +02:00
boban 552417fd4b feat(versions): grouped + paginated changelog browser (no endless scroll)
Replace the flat 48-item changelog list with a series browser: releases are
grouped by major.minor (e.g. "0.9"), a rail (vertical from xl, horizontal pills
below) selects the series, and the active series is paginated (8/page) with the
newest release open by default. The installed series is marked; each row shows
its change categories as toned glance-dots.

Also fixes, surfaced by an adversarial review of the change:
- icon: add the missing 'chevron-right' Lucide path — the accordion disclosure
  caret and the pagination "Next" button were rendering an empty <svg>.
- blade: wire:ignore.self on each <details> so a user's open/closed toggle is not
  reset by an unrelated re-render (autoCheck / check-updates / in-update poll); a
  series/page change still re-defaults to first-open via a fresh wire:key.
- blade: 44px touch targets (series pills + pagination) below lg (R7).
- i18n: localised 'Other' series label; pluralised series_count (no "1 Releases").
- Index: render() is the single clamp authority for changelogPage.

Index.php: groupBySeries() + pageWindow() + selectSeries/gotoChangelogPage; render
builds the series list, resolves the active series, paginates. New DE+EN keys.
VersionsChangelogTest covers grouping order, default series, paging, clamp, the
installed-series cue, and the chevron-right icon regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:47:45 +02:00
boban 05150bf89f fix(update): wait for actual new version before redirect (no more 502)
The update-progress page declared completion on two consecutive 200s from
the /up health route. But the OLD container keeps answering /up 200
throughout the minutes-long rebuild, so the page redirected prematurely
back into the teardown → 502 / white screen.

Now the page polls a lightweight public /version.json probe and finishes
only when the running version has actually moved past the pre-update one
(passed as ?from=), or after an observed down→up cycle for a same-version
redeploy. The 10-minute timeout + manual-reload fallback are unchanged.

- routes/web.php: add public GET /version.json; sanitise+pass ?from= to the view
- Versions/Index.php: include the installed version as ?from= in the redirect
- update-progress.blade.php: version-gated completion (fromVersion/sawDown)
- UpdateProgressTest: probe endpoint, embedded baseline, from-sanitisation, no /up poll

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:02:21 +02:00
boban 37cb449af5 chore: release 0.9.29 — changelog accordion, responsive update card, server link, pro phase labels 2026-06-20 20:45:25 +02:00
boban 9bb349dd9c feat(dashboard): link the active server name + IP to its detail page
When a server is active, the server name and IP status-pill are now clickable
links to that server's detail page using wire:navigate. When no server is active,
both display as plain text to maintain readability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:41:59 +02:00
boban a1b37751a0 feat(versions): professional update phase labels, responsive update card, changelog accordion
Also update UpdateProgressTest assertions to match the new phase label strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 20:40:35 +02:00
boban 542ad1a4d8 chore: release 0.9.28 — graceful update page + checkbox/2FA-badge UI fixes 2026-06-20 20:26:50 +02:00
boban df5d9e2112 harden(update): drop inline spinner style (R4), server-side return-path sanitise
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:22:40 +02:00
boban d2f70900ff fix(update): graceful Livewire-less /update-progress page — no more 502/whitescreen on update
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 20:14:11 +02:00
boban ffc17966ef fix(ui): unify the 2FA status badge into one x-two-factor-badge component
Replace three divergent 2FA indicator styles (cyan x-badge in users list,
custom inline-flex chip in settings header, plain mono text in sidebar) with a
single x-two-factor-badge component using the cyan x-badge + shield icon style
and the canonical settings.two_factor_on/off lang keys throughout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 20:07:59 +02:00
boban 001f44d959 fix(ui): themed x-checkbox component (accent, visible check) for login-protection + remember-me
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 20:04:47 +02:00
boban f0210c302b docs: spec — WireGuard panel gate + clusev wg CLI (SP1)
Optional host-level WireGuard gate for the Clusev panel: install.sh installs
wireguard-tools (off), a 'clusev wg setup/up/down/status/add-peer/remove-peer'
host CLI sets up wg0 + gates TCP 80/443 to the WG subnet via a dedicated
CLUSEV-WG-GATE iptables chain off DOCKER-USER, with 'clusev wg down' as the
escape hatch and SSH/WG-UDP never gated. Hardened after an adversarial review:
exact ordered firewall rules, persistence skips if wg0 is down (no reboot
brick), down isolates Clusev's chain, setup idempotency, precise install
wiring (CLUSEV_DIR exec + install_host_watchers). SP1 of 2; dashboard
status/traffic is SP2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:48:47 +02:00
boban 77d50d44e5 chore: release 0.9.27 — Anmeldeschutz (control-plane brute-force IP ban) 2026-06-20 18:58:24 +02:00
boban 6c81ea7fd1 harden(auth): fold IPv4-mapped IPv6, dedupe unbanAll, limit bans render, forged-token test 2026-06-20 18:53:52 +02:00
boban cabad0e770 harden(auth): scope whitelist purge to active bans, cover unbanAll, hoist ip 2026-06-20 18:20:19 +02:00
boban 7b4215d055 feat(auth): Anmeldeschutz settings tab + unban confirm flow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:13:12 +02:00
boban 96f86e2761 feat(auth): clusev:unban CLI + host wrapper escape hatch 2026-06-20 18:07:43 +02:00
boban 01867dba09 harden(auth): capture login IP once, assert 2fa-failed audit 2026-06-20 18:06:11 +02:00
boban 968d8c48c9 feat(auth): feed BruteforceGuard from login + 2FA failures, audit them
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:01:54 +02:00
boban 2936af82bd feat(auth): guests-only BlockBannedIp middleware + 403 page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 17:45:16 +02:00
boban 7645241a7a feat(auth): ValidIpOrCidr validation rule 2026-06-20 17:42:06 +02:00
boban df32275fd9 test(auth): BruteforceGuard record/ban/unban/cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 17:40:40 +02:00
boban 49d5c0aed3 harden(auth): BruteforceGuard review fixes (hit count, mask bounds, once-guard, mapped loopback)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:39:01 +02:00
boban ce98fbdba0 feat(auth): BruteforceGuard with inet_pton CIDR exemptions 2026-06-20 17:29:00 +02:00
boban 1f7b06c576 feat(auth): BannedIp model + migration 2026-06-20 17:26:28 +02:00
boban 9756f9d463 docs: implementation plan — control-plane brute-force ban (Anmeldeschutz)
9-task TDD plan: BannedIp model, BruteforceGuard (inet_pton CIDR + cache),
ValidIpOrCidr rule, guests-only BlockBannedIp middleware + 403 page, Login/2FA
failure hooks + audit, clusev:unban CLI, Anmeldeschutz settings tab with R5
confirm-modal unban. Spec erratum: middleware is appended (not prepended) so
Auth::guest() resolves for the guests-only check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:20:52 +02:00
boban fe8c25c824 docs: spec — control-plane brute-force IP ban (Anmeldeschutz)
App-level persistent IP ban for Clusev's own login (distinct from the remote-fleet
Fail2banService): BannedIp model, BruteforceGuard service, guests-only BlockBannedIp
middleware, Login/2FA hooks + audit, a new Anmeldeschutz settings tab, clusev:unban CLI.
Hardened after an adversarial multi-lens review: documented IP trust model (Caddy sole
proxy, prod-only trustProxies), guests-only enforcement so an authed operator is never
locked out and can self-unban, current-IP self-whitelist, concrete cache strategy,
inet_pton CIDR matching, R5 confirm modals, errors.php for the 403 strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:05:52 +02:00
boban d58073086c chore: release 0.9.26 — 2FA backup code as its own view 2026-06-20 16:42:30 +02:00
boban 4ea4050c80 fix(auth): R15 review — restore one-time-code autofill, memoise pendingUser in verify
- backup field: autocomplete=off -> one-time-code (backup codes ARE one-time codes;
  matches the main challenge field, the rest of the app, and the spec; restores
  password-manager OTP autofill that the earlier polish broke)
- both verify() use the trait's memoised $this->pendingUser() instead of a fresh
  User::find(), saving a redundant query on the failed-attempt re-render (Pint dropped
  the now-unused User import in TwoFactorBackup)
- spec: align trait backing-state visibility note to the implemented 'private'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:19:40 +02:00
boban 7c1a32518d i18n(auth): drop orphaned 2FA recovery hint strings 2026-06-20 15:47:26 +02:00
boban 8a8232a355 test(auth): cover TOTP+key challenge branch; clarify mount comment
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 15:46:37 +02:00
boban 67ce2de538 feat(auth): split backup code out of the main 2FA challenge view
Mount now redirects key-only users without a secure context straight to
the dedicated backup view. The main challenge form renders only for TOTP
users; a subordinate button links to two-factor.challenge.backup instead
of embedding the field inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 15:41:19 +02:00