Commit Graph

23 Commits (v0.9.4)

Author SHA1 Message Date
boban 21c087a11f chore(release): v0.9.4
Dashboard "request certificate" button: DNS pre-check + internal SNI handshake that
triggers Caddy's on-demand TLS, with a persisted status, per-user throttle, and
external-TLS/bare-IP gating — all auto-expiring, never locking out the control plane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:41:03 +02:00
boban d6d37583d0 chore(release): v0.9.3
Reliable modal triggers (@persist + spinner/error-toast across all views) and a
brute-force / rate-limiting / auth-DoS hardening pass (per-IP + per-account login
caps, constant-time login, TOTP replay protection, per-account 2FA backstops, a
global /livewire/update throttle, re-auth + SMTP-test throttles, and a fast-fail
SSH connect timeout) — all auto-expiring, never locking out the control plane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:03:44 +02:00
boban 3d86c8db8d chore(release): v0.9.2
One-command installer (Docker install, clusev user, DNS check) + themed MOTD,
professional README, and constant-time password-reset (account-enumeration
timing fix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:22:03 +02:00
boban 02741539ca chore(release): v0.9.1
UI: system-page redesign, full-width lone firewall/fail2ban panel, mobile-scrollable
settings tabs, instant lazy fail2ban-bans modal, file-manager image preview.
Auth: SMTP-aware forgot-password (15-min link + 2FA fallback), Security-Keys copy fix,
backup codes show-once.
Security hardening: signed single-use confirm-action tokens; airtight backup-code reveal
(transient, snapshot-replay-proof, grant-gated download).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:02:27 +02:00
boban 10f594c08d chore(release): v0.9.0
Multi-user admin accounts, database-backed session management (list + per-user/global
revoke), auto-restart sentinel (no docker socket), SMTP configuration, audit-log
retention, plus the Settings split into per-tab components and the UI batch (image
preview, fail2ban bans modal, show-password, system polish).

BREAKING: sessions move from Redis to the database — all existing sessions are
invalidated on update (log in once again).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 23:51:04 +02:00
boban dc885e293b chore(release): v0.8.0
External reverse-proxy TLS mode (Caddy stops ACME + app stops forcing HTTPS;
host check + bare-IP recovery preserved; trusted_proxies via TRUSTED_PROXY_CIDR)
and EnsureSecurityOnboarded as persistent Livewire middleware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:51:30 +02:00
boban cc8e60af5a chore(release): v0.7.0
Optional pluggable 2FA (TOTP and/or WebAuthn, or off) + recovery-codes modal,
live capped journal, command-palette server search, settings tabs in URL,
fail2ban lone-box layout, and SSH-key auto-provision with safe password-login
disable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:24:03 +02:00
boban 3bbd6bf096 chore(release): v0.6.1
Sidebar server-switcher syncs with the viewed server (race-free, mount-only).
Documented bfcache/multi-tab edge. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:46:05 +02:00
boban 5ae0b7d135 chore(release): v0.6.0
WebAuthn/YubiKey as an optional login 2nd factor (web-auth/webauthn-lib), gated
on domain+HTTPS. TOTP + backup codes stay the required 2FA. Register/use/remove
keys in Settings + at the challenge; 2FA reset (UI + CLI) clears keys. Real
YubiKey end-to-end is browser-verified on a domain host. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 18:50:07 +02:00
boban 7faa5c7d00 chore(release): v0.5.0
Account recovery: 2FA backup codes (show-once/download/regenerate, usable at the
login challenge), forgot-password via 2FA or backup code (email link gated on a
configured mailer), and a clusev:reset-admin CLI lockout fallback. Resets rotate
remember_token; codes consumed atomically. WebAuthn/YubiKey remains a Phase-2
spec. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 17:14:47 +02:00
boban a0ef0e6d9b chore(release): v0.4.6
Bilingual input-field validation messages: added lang/{de,en}/validation.php
(full Laravel 13 key set + localized attribute names), fixing the English-only
fallback. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:47:03 +02:00
boban 75dcfc11d3 chore(release): v0.4.5
Branding + custom error pages: full favicon/PWA icon set wired into both
layouts, and branded bilingual error pages (403/404/419/429/500/503) replacing
Laravel's defaults with APP_DEBUG pinned off in prod — no raw status text or
stack traces. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:37:25 +02:00
boban 07f9110f90 chore(release): v0.4.4
Stop iOS zoom-on-focus: an unlayered @media (pointer: coarse) rule forces 16px
on input/textarea/select for touch devices (phone + tablet of any size), keeping
the dense desktop sizing for mouse pointers. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:25:47 +02:00
boban cc00aa7d87 chore(release): v0.4.3
Responsive audit (R7) over all views at 375/768: hardened mobile overflow from
long panel domains (break-all/min-w-0) and banned IPv6 addresses in the fail2ban
list. Live pages were already overflow-free; only data-dependent edge cases
fixed. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:07:09 +02:00
boban e706b1e922 chore(release): v0.4.2
Server-Details UX + hardening polish: uniform bordered buttons (danger-soft),
installed-only firewall/fail2ban grid, auto-updates as a neutral preference,
SSH-verified server creation with a "pending" (Initialisierung) status, and an
SSH key-only checklist hint — plus the fail2ban/UFW "nicht installiert"
false-negative fix. See CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 11:01:41 +02:00
boban f5f903046d fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1)
Two fixes/hardenings on top of v0.4.0:

1) Blade raw-block bug (R17). The topbar's @php block had a COMMENT containing the literal
   tokens @php/@endphp; Blade's non-greedy raw-block matcher took the @endphp in the comment
   as the block end and dumped the remainder ("$title ??= …") as plain text into <header> on
   every authenticated page (and left the page title empty). R12 missed it: the page still
   returned 200 with no console error and the <title> comes from the Livewire component. Fixed
   the comment; added rule R17 (no directive tokens in Blade comments; block over inline @php;
   R12 must inspect the rendered DOM, not just status/console) to rules.md + CLAUDE.md.

2) Broadcast channels are now PRIVATE (security hardening — closes the follow-up flagged in
   v0.4.0). MetricsTicked rides a PrivateChannel authorized via /broadcasting/auth
   (withBroadcasting + routes/channels.php), so fleet metrics can no longer be subscribed to by
   anyone holding the bundled app key — including over a stale Caddy host after a domain change.
   The channel callback requires User::securityOnboarded() (rotated password + 2FA), mirroring
   the panel's EnsureSecurityOnboarded gate — authentication alone is not enough. Echo sends the
   CSRF token for the auth handshake. Convention documented (CLAUDE.md §3 / channels.php): every
   channel is private.

Bump 0.4.0 -> 0.4.1; CHANGELOG.

Verified: Pint clean; npm build; R12 all routes 200 + 0 console errors (both locales); topbar
<header> rendered text clean (no @/{{ }}/$var/key leaks); private broadcast publishes to
reverb:8080 and /broadcasting/auth + the onboarding-gated callback authorize correctly; Codex
review clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 08:34:07 +02:00
boban a44e29426a feat(domain): change the panel domain from the dashboard (v0.4.0)
The panel domain is editable on the System page again — safely this time. It is
stored in the DB (Setting `panel_domain`, overriding install-time APP_DOMAIN) and
APPLIED ON RESTART, never mid-session: a snapshot file (storage/framework/active-domain)
is frozen at container start by the entrypoint (`clusev:snapshot-domain`), so saving a
new domain only takes effect after `docker compose ... restart`.

How it works
- DeploymentService: pending (configuredDomain, DB) vs active (domain(), snapshot file);
  setDomain() persists; restartPending() drives the UI notice. Snapshot reads the DB
  DIRECTLY (cache-independent) and retries until the settings table is readable; if it
  never is, it freezes the env fallback so the active domain is always a FIXED value
  (never a live one that could shift after startup).
- AppServiceProvider: derives app.url from the active domain at boot; pins server->Reverb
  publishing to the internal reverb:8080 (domain/cert-independent).
- Caddy: on-demand TLS gated by /_caddy/ask (issues a cert only for the configured
  domain); HTTP always served for bare-IP recovery; /app,/apps forced to HTTPS for any
  hostname (plaintext only on a bare IP).
- Reverb client endpoint is derived from the live request and rides the same front door
  (/app tunnel — Caddy in prod, nginx in dev), so realtime follows a domain change with
  no JS rebuild and no stale .env value.
- System page: domain form + R5 confirm + "restart required" notice with the exact
  command; DE/EN strings (R16).

Anti-lockout / security
- session.secure + the HTTPS redirect follow the real request scheme; bare-IP HTTP is
  always a recovery path. trustProxies('*') only in production (dev can't be tricked into
  faking HTTPS via X-Forwarded-*). When a domain is active only that domain (HTTPS) and
  the literal server IP (HTTP) serve the panel; any other/stale host is refused (404),
  and IP-recovery redirects stay on the IP.

Bump 0.3.0 -> 0.4.0; CHANGELOG. Follow-up tracked: make the public `metrics` broadcast
channel private (wire broadcasting auth).

Verified: Pint clean; npm build; Caddyfile validates; R12 all routes 200 + 0 console
errors; Echo connects via the unified /app tunnel; domain set/clear + restart-gating +
/_caddy/ask (200 active / 403 other) + host-enforcement matrix all confirmed in dev;
Codex review iterated to no actionable in-scope findings; 14-agent adversarial
lockout/security review (real trustProxies finding fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 01:38:16 +02:00
boban ec1516184e i18n: complete DE/EN localization (modals, shell, backend) + v0.3.0
- modals: wire 5 hard-coded views + 10 components to lang/{de,en}/modals.php
- shell: command-palette (incl. app.js cmdk(nav,actions) refactor), server-item,
  toaster fallback
- backend: new lang/{de,en}/backend.php; localize Hardening/Firewall/Fail2ban/
  Maintenance services, OsProfile, plus FleetService + CredentialVault errors
- fix: localized page <title> on Dashboard/Servers index+show (->title(), the
  Livewire-3 way; the stray title() methods were never auto-called)
- fix: topbar inline @php() was swallowed by Blade's raw-block precompiler up to a
  later @endphp -> 500 on every authed page; converted to block @php ... @endphp
- versions: localize changelog section labels via a canonical heading map
- translation-quality pass: Unlock, Add credentials, clearer auth/notify copy

Bump 0.2.1 -> 0.3.0; CHANGELOG.

Verified: Pint clean; npm build OK; R12 all routes HTTP 200 + 0 console errors in
BOTH locales (titles/nav/labels switch language); 589 used keys resolve in de+en
with full group parity; Codex review clean; adversarial DE/EN audit (0 high).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 23:27:42 +02:00
boban c34ced0c0e fix(tls/ui): fully automatic TLS (status-only) + uniform hardening buttons
Per operator feedback ("das soll automatisch sein, die Caddy-Config muss man nicht
sehen, nur 'SSL steht'"):

- TLS is fully automatic via Caddy (auto-issue + auto-renew + HTTP→HTTPS). The System
  page now shows only the STATUS (domain + access URL) — the generated Caddy config and
  the manual `caddy reload` command are gone, and the domain/email edit form is removed.
- The panel domain is an INSTALL-TIME value (APP_DOMAIN → config('clusev.domain')), so it
  stays consistent with APP_URL, the Reverb (wss) endpoint and cookie security — none of
  which can be re-derived at runtime (Caddy's site address + the browser's Reverb host are
  fixed at install/build, and .env is never rewritten). DeploymentService is now read-only.
- Removed AppServiceProvider's runtime app.url override (it let a stale DB value shadow the
  install-time URL). Migration drops the inert empty dashboard_domain/dashboard_email rows
  (a real configured value is preserved as history; re-apply via install.sh — see CHANGELOG).
- Hardening checklist: all toggle buttons share one style (secondary/bordered) instead of
  varying with state (R10).

Bump 0.2.0 -> 0.2.1 + CHANGELOG. Pint clean; Codex reviewed (the remaining note is a
deliberate install-time-domain tradeoff — a runtime/DB domain cannot be served by the
templated Caddy without a false "TLS active" status); R12 — system/server-show/dashboard
HTTP 200, 0 console errors, raw config + edit form gone, buttons uniform.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 21:17:26 +02:00
boban 4a4d107bfc chore(release): v0.2.0 — System TLS rationale + version bump + CHANGELOG
- System page: collapsible "Warum läuft TLS über Caddy?" explaining the single
  host-exposed TLS terminator (auto Let's Encrypt + auto-renew + wss for Reverb,
  decoupled from app updates) — answers the operator's question in the UI; Caddy stays.
- Bump 0.1.1 -> 0.2.0 (cross-OS management, firewall + fail2ban control, command
  palette) + CHANGELOG entry.

Pint clean; Codex review clean; R12 — all 9 routes HTTP 200, 0 console errors,
TLS note renders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:43:07 +02:00
boban 0f57074e6f chore(audit): full code sweep — fix open-redirect, drop dead code, clean docs
Full-codebase audit + automatic cleanup (no behavioural change for users).

Security
- ServerSwitcher: post-switch redirect is now reduced to a SAME-ORIGIN
  relative path. Rejects off-host referers, non-http(s) schemes
  (javascript:/data:), different ports (https://host:444/x -> /x), and
  protocol-relative tricks (//host, /\host). Closes a Referer open-redirect
  (Codex P2).
- EditCredential: a key passphrase is stored only for key auth, never for
  password auth.

Removed dead code
- FirewallService::status/allow/deny (+ orphaned clampPort)
- Server::auditEvents(), SshCredential::scopeActive(), unused 'bell' icon

Cleanups / improvements
- Files list + editor use a dedicated 'file' icon (was the audit icon)
- Import RuntimeException/Throwable instead of FQN; fix stale docblocks
  (FirewallService, HardeningService) and the Files delete comment
- Quote base64 in the sudo command path (defensive, consistent)
- Null-safe Auth::user()?->email in Settings; single domain() read in hasTls()

Release
- Bump 0.1.0 -> 0.1.1 + CHANGELOG entry

Verified: Pint clean, Codex review clean (no actionable regressions),
R12 browser check — 9 routes HTTP 200, 0 console errors, lazy pages loaded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 12:54:26 +02:00
boban 0b2dffe0de fix+feat(release/security): apt timeout, real version/release model, domain in DB not .env, R15
- fix(hardening): fail2ban/unattended apt installs failed because the 12s SSH timeout
  aborted the package download. runPrivileged/runPlain take a $timeout; apt actions use
  600s. Live-verified: fail2ban installs + is active/enabled on 10.10.90.162.
- feat(release): cut the foundation as the first real semantic release v0.1.0 (no more
  -dev). CHANGELOG is Keep-a-Changelog BY VERSION; the Versions page parses it by version,
  reads the newest git TAG as "latest release", and honestly compares installed vs latest
  in the channel. Channels reduced to stable|beta (no user-facing dev); System clamps a
  legacy value back to stable. Tagged v0.1.0.
- fix(domain/.env): the panel domain lives in the DATABASE (Settings); the app never
  rewrites .env. AppServiceProvider overrides config('app.url') at runtime from the Setting
  (guarded against a missing table); /system states this explicitly. Caddy config is a
  standalone generated file; a Caddy reload is the only infra step.
- docs(arch): Caddy is the prod-only TLS reverse proxy IN FRONT of the app container's
  nginx (Internet -> Caddy:443 -> nginx:80 -> php-fpm); no Caddy in dev.
- R15 (rules.md + CLAUDE.md): Codex must review every change for errors + security before
  "done". The Codex CLI is not installed in this runtime (needs `sudo npm i -g @openai/codex`
  + `/codex:setup`); an independent adversarial security review of this diff returned CLEAN
  (no vulnerabilities, no bugs) as the interim gate.

Verified (R12): /system + /versions 200 / 0 console errors; channels stable|beta only,
DB-not-.env callout; versions by-version with v0.1.0; app.url override resolves from the
Setting; fail2ban live install ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 03:06:35 +02:00
boban 875678d0cd feat(versions)+fix: password-sudo service control, honest login, real versions page
- Service control now works with a sudo-PASSWORD credential (not only NOPASSWD):
  FleetService::sudoFn() defines a remote priv() that feeds the vaulted password to
  `sudo -S` over stdin (base64 over the encrypted channel, never on the argv) when not
  root, else falls back to sudo -n. Verified LIVE on 10.10.90.162: cron restart/stop/
  start executed for real (ActiveEnterTimestamp advanced to now, is-active=inactive
  after stop, restarted clean), journal reads real entries via sudo.
- Login brand panel: removed fabricated telemetry — fake "24 Hosts erreichbar",
  "cpu 31% mem 48% load 0.86", "clusev connect 10.10.90.0/24". Replaced with true
  capability lines (agentless SSH/phpseclib, TOFU host-key pinning, 2FA, audit log,
  AGPL). Only real claims now (per "nur eintragen was auch wirklich geht").
- New Version & Releases page (/versions, EN route per R13) with REAL data only:
  version from config/clusev.php, build SHA + branch read from .git at runtime,
  changelog parsed from a real CHANGELOG.md, real Gitea repo + AGPL license, honest
  update path (deploy commands) — NO fake updater / stars / forks / CVEs / "update
  available v2.5.0". Sidebar nav + tag/git-branch icons added.
- Dummy-data sweep (12-auditor workflow + adversarial verify): 1 confirmed finding —
  removed the unused sine/cosine series() fake-sparkline generator in Dashboard.php;
  also fixed a stale "static seed" chart comment and a stale "mock listing" comment;
  derived the versions repo label from config (DRY). 5 false positives dismissed.

Verified (R12): /login + /versions + all 7 routes 200 / 0 console errors; login
fake-metrics gone + honest lines + fonts load; versions shows real version/build/
changelog/repo with zero fabricated stats.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 01:43:48 +02:00