970 lines
60 KiB
Markdown
970 lines
60 KiB
Markdown
# Changelog
|
||
|
||
Alle nennenswerten Änderungen an Clusev. Format nach
|
||
[Keep a Changelog](https://keepachangelog.com), Versionierung nach
|
||
[SemVer](https://semver.org).
|
||
|
||
**Release-Prozess:** Feature-Commits sammeln sich auf dem Branch. Ist ein Stand
|
||
reif, wird er nach `main` gemerged, als `vX.Y.Z` getaggt und hier unter einer
|
||
eigenen Versionsüberschrift dokumentiert. Nutzer beziehen ausschließlich
|
||
getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Builds.
|
||
|
||
## [Unreleased]
|
||
|
||
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
|
||
|
||
## [0.9.33] - 2026-06-20
|
||
|
||
### Hinzugefügt
|
||
- **WireGuard-Zugang (SP1) — Panel hinter einem Tunnel.** Optionaler Netzwerk-Schutz *zusätzlich* zu
|
||
2FA und Anmeldeschutz: Der Server wird zum WireGuard-Server, und die Panel-Ports 80/443 lassen sich
|
||
auf das WG-Subnetz beschränken — komplett über die Host-CLI
|
||
`clusev wg setup|up|down|status|add-peer|remove-peer` (per SSH). Standardmäßig **aus**. Sicherheit:
|
||
SSH (22) und der WG-Port sind von der Sperre nie betroffen, `clusev wg down` (per SSH) ist der
|
||
Notausgang, und ein nach Neustart fehlgeschlagenes `wg0` lässt das Panel offen statt auszusperren.
|
||
Die Sperre ist eine isolierte iptables-Chain (`CLUSEV-WG-GATE`) an `DOCKER-USER`; eine systemd-Unit
|
||
wendet sie nach Boot/Docker-Neustart wieder an — nur wenn Tunnel **und** Marker aktiv sind.
|
||
`install.sh` installiert `wireguard-tools`+`qrencode` (inert, bis der Operator `clusev wg setup`
|
||
ausführt). Neuer Hilfe-Eintrag „WireGuard-Zugang" (DE+EN).
|
||
*Rein host-seitig (WireGuard/Firewall laufen außerhalb des Containers). Die Shell-Skripte sind
|
||
shellcheck-sauber und mehrfach reviewt, aber nicht durch die PHPUnit-Suite testbar — die Laufzeit
|
||
(setup/up/down/Gate) ist per Runbook auf einer VM zu verifizieren (`docs/superpowers/runbooks/`).*
|
||
|
||
## [0.9.32] - 2026-06-20
|
||
|
||
### Hinzugefügt
|
||
- **Serie & Seite als URL-Parameter.** Die gewählte Versionsreihe und die Seite des Änderungs
|
||
protokolls stehen jetzt in der Adresse (`?series=0.8&page=2`) — teilbar und per Reload/Zurück-Taste
|
||
erhalten.
|
||
- **Echter Update-Fortschritt statt Zeit-Schätzung.** Der Host-Updater meldet seine aktuelle Phase
|
||
über eine Datei (`run/update-phase.json`); die Update-Seite liest sie und schaltet die Schritte
|
||
**abrufen → Image bauen → Dienste neu starten → migrieren** der Reihe nach echt weiter, statt alle
|
||
vier am Ende auf einmal grün springen zu lassen. Abschluss erfolgt über das reale „done"-Signal des
|
||
Updaters (Fallback: Versions-Wechsel). Greift — wie der 502-Fix — erst beim Update, das von einer
|
||
bereits mit 0.9.32 laufenden Instanz gestartet wird (die während eines Updates angezeigte Seite ist
|
||
stets noch die der alten Version); ohne Feed bleibt die bisherige Zeit-Heuristik als Fallback.
|
||
|
||
### Geändert
|
||
- Update-Phasen in realer Reihenfolge (Dienste-Neustart vor Datenbank-Migration).
|
||
|
||
## [0.9.31] - 2026-06-20
|
||
|
||
### Geändert
|
||
- **Versions-Verlauf als Serien-Browser statt langer Liste.** Releases sind jetzt nach
|
||
Hauptversion (major.minor, z. B. „0.9") gruppiert: eine Serien-Navigation (ab großen Bildschirmen
|
||
eine seitliche Leiste, darunter horizontale Pills) wählt die Reihe, rechts erscheinen deren
|
||
Releases als Accordion (das neueste offen) mit **Seitenblätterung** — kein endloses Scrollen über
|
||
alle Versionen mehr. Die installierte Reihe ist markiert, jede Zeile zeigt die Änderungs-Kategorien
|
||
als farbige Punkte auf einen Blick.
|
||
|
||
### Behoben
|
||
- **Aufklapp-Pfeil & „Weiter"-Knopf sichtbar.** Das `chevron-right`-Icon fehlte im Icon-Satz, sodass
|
||
der Accordion-Pfeil jeder Zeile und der „Weiter"-Knopf der Seitenblätterung als leeres SVG
|
||
gerendert wurden (unsichtbare Bedienelemente). Icon ergänzt.
|
||
- **Aufgeklappter Eintrag bleibt offen.** Ein selbst aufgeklapptes Release wird nicht mehr durch
|
||
einen Hintergrund-Re-Render (Update-Prüfung) wieder zugeklappt (`wire:ignore.self`).
|
||
- **Touch-Zielgrößen.** Serien-Pills und Seiten-Knöpfe erfüllen auf kleinen Bildschirmen jetzt die
|
||
44-px-Touch-Mindestgröße.
|
||
|
||
## [0.9.30] - 2026-06-20
|
||
|
||
### Behoben
|
||
- **Update löst keinen 502 mehr aus.** Die Fortschritts-Seite erkannte „fertig" bislang an zwei
|
||
aufeinanderfolgenden `200` auf der Health-Route `/up` — doch der **alte** Container antwortet dort
|
||
während des minutenlangen Neuaufbaus weiter mit `200`, sodass zu früh zurückgeleitet wurde,
|
||
mitten in den Stack-Neustart hinein → 502/Weißbild. Die Seite pollt jetzt einen schlanken
|
||
Versions-Endpoint (`/version.json`) und gilt erst dann als fertig, wenn die **laufende Version
|
||
tatsächlich über die Ausgangsversion hinaus** gewechselt ist (bzw. nach einem beobachteten
|
||
down→up-Zyklus bei gleicher Version). Erst dann erfolgt der automatische Rücksprung.
|
||
|
||
## [0.9.29] - 2026-06-20
|
||
|
||
### Geändert
|
||
- **Versions-Verlauf als Accordion.** Das Änderungsprotokoll zeigt pro Eintrag nur noch
|
||
Versionsnummer + Datum; ältere Versionen sind eingeklappt und öffnen per Klick, die neueste ist
|
||
immer offen — bei vielen Releases deutlich übersichtlicher.
|
||
- **„Update verfügbar"-Karte responsive entstaucht** — auf schmalen Bildschirmen stapeln Version,
|
||
Badges, Aktion (Button volle Breite) und Prüfzeitpunkt sauber untereinander statt gequetscht.
|
||
- **Professionellere Phasen-Beschriftung** auf der Update-Fortschritts-Seite („Update wird
|
||
abgerufen / Image wird erstellt / Datenbank wird migriert / Dienste werden neu gestartet").
|
||
|
||
### Hinzugefügt
|
||
- **Direkter Server-Link im Dashboard** — Name und IP der aktiven Maschine verlinken jetzt direkt
|
||
auf deren Detailseite (vorher: erst „Server", dann auswählen).
|
||
|
||
## [0.9.28] - 2026-06-20
|
||
|
||
### Behoben
|
||
- **Update bricht nicht mehr mit 502 / Weißbild ab.** Beim Auslösen eines Updates erscheint jetzt
|
||
eine eigenständige Fortschritts-Seite (`/update-progress`, ohne Livewire), die den Neuaufbau des
|
||
Stacks im Browser übersteht: sie pollt die Health-Route, zeigt die Phasen
|
||
(Holen/Bauen/Migrieren/Neustart) + verstrichene Zeit und leitet nach der Wiederherstellung
|
||
automatisch dorthin zurück, wo man war. (Open-Redirect-sicher: das Rücksprung-Ziel wird auf einen
|
||
gleich-origin Relativpfad beschränkt.)
|
||
- **Checkbox im Reiter „Anmeldeschutz"** nutzt jetzt das Design-System (Signal-Orange + sichtbarer
|
||
Haken über eine wiederverwendbare `x-checkbox`-Komponente; ebenso „Angemeldet bleiben" im Login)
|
||
statt der blauen Browser-Standard-Checkbox.
|
||
- **2FA-Status-Badge vereinheitlicht.** Identity-Header, Benutzer-Liste und Sidebar zeigen den
|
||
„2FA aktiv/aus"-Status jetzt im selben Stil (eine `x-two-factor-badge`-Komponente) statt zuvor
|
||
drei verschiedener Darstellungen.
|
||
|
||
### Hinweis
|
||
- **Security-Key (WebAuthn/YubiKey) erfordert eine Domain mit HTTPS.** Bei Bare-IP-Zugriff ohne
|
||
konfigurierte Domain ist ein Security-Key nicht nutzbar (eine IP kann keine WebAuthn-rpId sein);
|
||
ein Schlüssel-only-Konto wird dann korrekt auf den Backup-Code geleitet. Für den
|
||
Security-Key-Button das Panel unter einer Domain über HTTPS betreiben.
|
||
|
||
## [0.9.27] - 2026-06-20
|
||
|
||
### Hinzugefügt
|
||
- **Anmeldeschutz — Brute-Force-Sperre für das Panel-Login selbst.** Nach zu vielen fehlgeschlagenen
|
||
Anmelde- oder 2FA-Versuchen wird die Quell-IP zeitlich gesperrt (Standard: 10 Versuche in 10 Min →
|
||
60 Min Sperre). Durchgesetzt auf HTTP-Ebene für **nicht angemeldete** Anfragen — eingeloggte
|
||
Operatoren werden nie geblockt und können ihre eigene IP weiterhin im Panel entsperren. (Eigenständig
|
||
von der Flotten-fail2ban-Verwaltung, die entfernte Server härtet.)
|
||
- **Neuer Einstellungs-Reiter „Anmeldeschutz"** (Einstellungen → Anmeldeschutz): An/Aus, max. Versuche,
|
||
Zeitfenster, Bann-Dauer, Whitelist (IP/CIDR), Liste aktiver Sperren mit Entsperren / Alle entsperren,
|
||
plus „Meine IP zur Whitelist hinzufügen".
|
||
- **Host-Befehl `clusev unban <ip>` (`--all`)** als Notfall-Ausweg, falls man sich aussperrt — analog
|
||
zu `clusev reset-admin`.
|
||
- Fehlgeschlagene Logins und 2FA-Versuche sowie Sperren/Entsperrungen landen jetzt im **Audit-Log**
|
||
(`auth.login_failed`, `auth.2fa_failed`, `auth.ip_banned`, `auth.ip_unbanned`).
|
||
|
||
### Sicherheit
|
||
- Loopback und private Netze sind standardmäßig ausgenommen (gegen Selbst-Aussperrung). Der
|
||
IP-Vergleich erfolgt kanonisch (inet_pton; IPv4-mapped-IPv6 wird auf die IPv4-Form zusammengefasst),
|
||
damit eine Sperre nicht über zwei Schreibweisen derselben Adresse umgangen werden kann. Sperren sind
|
||
immer zeitlich begrenzt — es gibt keinen permanenten Lockout.
|
||
|
||
## [0.9.26] - 2026-06-20
|
||
|
||
### Hinzugefügt
|
||
- **Eigene Backup-Code-Seite im Zwei-Faktor-Login** (`/two-factor-challenge/backup`). Über den Button
|
||
„Backup-Code verwenden" auf dem Bestätigungs-Screen erreichbar; trennt den seltenen
|
||
Wiederherstellungs-Fall sauber vom Alltags-Login.
|
||
|
||
### Geändert
|
||
- **Der 2FA-Bestätigungs-Screen zeigt nur noch die primäre Methode** (Authenticator-Code *oder*
|
||
Security-Key) — der Backup-Code ist nicht mehr inline neben der Eingabe, sondern auf der eigenen Seite.
|
||
- **Security-Key-only ohne Secure-Context** (bare-IP/HTTP, wo WebAuthn nicht läuft): der Login leitet
|
||
direkt zur Backup-Code-Seite weiter, statt einen Screen ohne nutzbare Methode zu zeigen.
|
||
- Gemeinsame Challenge-Logik (Pending-User, Brute-Force-Zähler, Login-Abschluss) in einen Trait
|
||
ausgelagert; beide Ansichten teilen denselben Topf — das Backup-Code-Rate-Limit (5/60 s pro
|
||
Nutzer+IP, 20/900 s pro Nutzer) gilt unverändert auch auf der neuen Seite.
|
||
|
||
## [0.9.25] - 2026-06-19
|
||
|
||
### Hinzugefügt
|
||
- **Host-Befehl `clusev`** (vom Installer nach `/usr/local/bin` gelegt): kurze Wrapper für
|
||
`update`, `reset-admin`, `restart`, `logs`, `ps`, `migrate`, `artisan`, `version`. Statt langer
|
||
`docker compose`-Aufrufe mit der Prod-Compose-Datei genügt z. B. `sudo clusev update` oder
|
||
`clusev reset-admin`. `clusev help` zeigt die Übersicht.
|
||
- **Hilfe-Thema „Befehle / CLI" (DE/EN):** dokumentiert jeden `clusev`-Befehl ausführlich und zeigt,
|
||
was im Hintergrund läuft.
|
||
|
||
### Geändert
|
||
- **Hilfe-Tabs stehen in der URL** (`#[Url]` am Thema) — Reload/Lesezeichen/Teilen behalten das aktive
|
||
Tab, wie bei den Einstellungen.
|
||
- Alle Operator-Flächen (Update-Hinweis unter „Version & Releases", Hilfe-Wiederherstellung/-Updates/
|
||
-Domain-TLS, Reset-Hinweise in Einstellungen/System, Sentinel-Fehlermeldungen, MOTD) zeigen jetzt die
|
||
kurzen `clusev …`-Befehle; der lange Compose-Dateiname taucht in der Oberfläche nicht mehr auf.
|
||
|
||
## [0.9.24] - 2026-06-19
|
||
|
||
### Hinzugefügt
|
||
- **In-Panel-Hilfe-Seite (`/help`, Nav „Hilfe", `g h`).** Linke Themen-Navigation wie die
|
||
Einstellungen, zweisprachig (DE/EN, folgt dem Umschalter). Themen: Überblick, Domain/TLS &
|
||
Reverse-Proxy, Sicherheit & 2FA, Updates & Versionen, Server & SSH, Sitzungen & Benutzer, E-Mail,
|
||
Audit-Log, Konto-Wiederherstellung. Enthält u. a. eine **generische Schritt-für-Schritt-Anleitung
|
||
für den Betrieb hinter einem externen Reverse-Proxy** (was im Proxy eintragen: HTTP-Ziel,
|
||
`X-Forwarded-Proto: https`, WebSocket, `TRUSTED_PROXY_CIDR`) und den **2FA-Zugangspfad-Hinweis**
|
||
(Security-Key nur über die HTTPS-Domain; über Bare-IP/HTTP Backup-Code nötig; TOTP überall).
|
||
|
||
### Geändert
|
||
- TLS-Modus-Hinweis nennt keinen Produktnamen mehr — „ein vorgelagerter Proxy Manager terminiert TLS".
|
||
|
||
## [0.9.23] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Konsolen-Warnung „resource was preloaded but not used".** Vite legte zu jeder Seite einen
|
||
`<link rel="preload">` für CSS/JS zusätzlich zum normalen Stylesheet/Script an. Bei nur einem
|
||
CSS- und einem JS-Bundle bringt der Preload nichts (die Tags stehen ohnehin direkt daneben), löste
|
||
aber Chromes „preloaded but not used"-Warnung aus — verstärkt durch DevTools „Cache deaktivieren",
|
||
das die CSS doppelt lädt. Die Preload-Hints werden jetzt weggelassen (`Vite::usePreloadTagAttributes(false)`);
|
||
Stylesheet und Script laden unverändert.
|
||
|
||
## [0.9.22] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Passwortmanager (Bitwarden) kaperte die Security-Key-Registrierung.** Trotz `cross-platform`
|
||
fing Bitwardens Browser-Erweiterung `navigator.credentials.create` ab und bot „Passkey speichern"
|
||
an. Die Optionen enthalten jetzt zusätzlich den **WebAuthn-L3-Hint `hints: ['security-key']`** (bei
|
||
Registrierung und Login), den moderne Browser/Manager respektieren und daher beiseite treten — der
|
||
YubiKey wird direkt abgefragt. (Ältere Bitwarden-Versionen, die den Hint nicht kennen, kapern
|
||
evtl. weiter — dann in Bitwarden „Nach Passkeys fragen" für die Seite/global deaktivieren.)
|
||
|
||
## [0.9.21] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Security-Key-Registrierung zielte nicht auf einen Hardware-Schlüssel** → der Browser bot das
|
||
Passkey-Speichern (z. B. Bitwarden) an statt den YubiKey abzufragen. Die Registrierung setzt jetzt
|
||
`authenticatorSelection`: **cross-platform** (externer/roaming Schlüssel statt Plattform-/
|
||
Passwortmanager-Passkey), **residentKey discouraged** (nicht-auffindbarer Zweitfaktor — genau das,
|
||
was die Passkey-Manager-UI vermeidet) und **userVerification discouraged** (nur Anwesenheit = ein
|
||
Tippen, kein PIN/Biometrie). Login fragt den Schlüssel ebenfalls mit „discouraged" ab → **einstecken,
|
||
einmal tippen, fertig**.
|
||
|
||
## [0.9.20] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Security-Keys (WebAuthn/YubiKey) waren hinter einem externen TLS-Proxy gesperrt.** Die
|
||
Verfügbarkeit hing an `request()->isSecure()` — das im Externer-Proxy-Modus `false` ist (der
|
||
weitergereichte HTTPS-Scheme wird ohne `TRUSTED_PROXY_CIDR` nicht vertraut), obwohl das Panel
|
||
über die Domain per HTTPS läuft. Security-Keys werden jetzt freigeschaltet, sobald das Panel über
|
||
die **aktive Domain** erreicht wird (immer HTTPS über die Vordertür), unabhängig vom hier
|
||
erkannten Request-Scheme. Der Bare-IP-Recovery-Pfad (kein Secure-Context) bleibt korrekt gesperrt.
|
||
Ein Kern-Sicherheitsfeature hängt damit nicht mehr an einer optionalen Proxy-Einstellung.
|
||
|
||
## [0.9.19] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Caddyfile-Änderungen wurden bei einem Update nicht übernommen.** Die Caddy-Config war als
|
||
**einzelne Datei** gemountet; ein `git pull` ersetzt die Datei (neue Inode), aber der laufende
|
||
Container hängt weiter an der alten — und `docker compose up -d` baut Caddy bei reiner
|
||
Inhaltsänderung nicht neu. Dadurch kamen Caddy-Fixes (z. B. der WebSocket-Fix aus 0.9.18) ohne
|
||
manuelles Eingreifen nie an. Jetzt wird das **Verzeichnis** `docker/caddy` gemountet (spiegelt die
|
||
Datei live) und `install.sh` baut den Caddy-Container bei jedem Deploy gezielt neu
|
||
(`up -d --force-recreate caddy`), sodass Caddyfile-Änderungen zuverlässig wirksam werden.
|
||
|
||
## [0.9.18] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Realtime (WebSocket) hinter einem externen TLS-Proxy lief nicht.** Der Echo/Reverb-Endpunkt
|
||
wurde aus dem (hinter dem Proxy als `http:80` erscheinenden) Request abgeleitet → `wss://host:80`,
|
||
was scheiterte. Bei aktiver Domain wird der Reverb-Client jetzt fest auf **`wss://<domain>:443`**
|
||
(die HTTPS-Vordertür) gesetzt; der Bare-IP-Recovery-Pfad bleibt einfaches `ws`. Zusätzlich leitet
|
||
Clusevs Caddy die WS-Pfade (`/app/*`, `/apps/*`) **nicht mehr auf https um, wenn der Upstream
|
||
bereits TLS terminiert hat** (`X-Forwarded-Proto: https`) — sonst brach der Redirect die
|
||
WebSocket-Verbindung im Externer-Proxy-Modus.
|
||
- **TLS-Status zeigte im Externer-Proxy-Modus fälschlich „Let's Encrypt".** Die „TLS aktiv"-Karte
|
||
meldet jetzt korrekt „TLS aktiv — über externen Reverse-Proxy; das Panel stellt kein Zertifikat
|
||
aus" statt einer automatischen Let's-Encrypt-Ausstellung, die in diesem Modus nicht stattfindet.
|
||
|
||
## [0.9.17] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Panel über die Domain hinter einem externen TLS-Proxy war unbenutzbar (CSS/JS per CSP
|
||
blockiert).** Im Externer-Proxy-Modus terminiert der Upstream HTTPS und reicht das Schema per
|
||
`X-Forwarded-Proto` weiter; Clusevs eigener Caddy vertraut dem aber nur, wenn `TRUSTED_PROXY_CIDR`
|
||
gesetzt ist. War es das nicht, sah die App `http`, erzeugte `http://`-Asset-URLs — und die über
|
||
HTTPS ausgelieferte Seite (CSP `'self'`) blockierte diese → kein CSS/JS, kein Login. Bei aktiver
|
||
Domain wird die URL-Erzeugung (Assets, Routen) jetzt fest auf `https://<domain>` gezwungen
|
||
(`URL::forceRootUrl`), unabhängig vom (evtl. nicht vertrauten) Request-Schema. Der Bare-IP-
|
||
Recovery-Pfad (HTTP) bleibt unberührt. Hinweis: für korrekte `Secure`-Cookies im Externer-Proxy-
|
||
Modus weiterhin `TRUSTED_PROXY_CIDR` auf die Proxy-Adresse setzen.
|
||
|
||
## [0.9.16] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Neustart-Knopf meldet einen fehlgeschlagenen Sentinel-Schreibvorgang** — konsistent mit dem
|
||
Update-Knopf (0.9.15). Konnte die Sentinel-Datei nicht geschrieben werden (z. B. bei einem
|
||
uid-Mismatch vor dem Fix), zeigte „Jetzt neu starten" nur „Neustart wird ausgeführt …" ohne dass
|
||
etwas passierte; jetzt erscheint stattdessen ein Fehler-Toast mit dem Hinweis auf `sudo ./update.sh`.
|
||
`DeploymentService::requestRestart()` gibt nun `bool` zurück.
|
||
|
||
## [0.9.15] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Dashboard-Update/-Neustart hingen auf Hosts, deren `clusev`-Nutzer nicht uid 1002 hat.** Das
|
||
Prod-Image baute den `app`-Nutzer fest mit uid 1002, während `install.sh` das bind-gemountete
|
||
`./run` dem `clusev`-Nutzer (z. B. uid 1000) übergibt. Dadurch konnte der App-Prozess die
|
||
Sentinel-Datei nicht schreiben — der Knopf zeigte „läuft", aber auf dem Server passierte nichts.
|
||
Das Image wird jetzt mit `APP_UID/APP_GID = HOST_UID/HOST_GID` (dem `clusev`-Nutzer) gebaut, sodass
|
||
App-Prozess und `./run` übereinstimmen. **Einmalig per `sudo ./update.sh` neu bauen**, danach
|
||
funktioniert der Knopf (der konnte sich nicht selbst reparieren, da genau das Schreiben kaputt war).
|
||
- **Fehlgeschlagenes Sentinel-Schreiben wird jetzt gemeldet** statt eines ewigen „läuft": geht das
|
||
Schreiben schief, erscheint ein Fehler-Toast mit dem Hinweis auf `sudo ./update.sh`.
|
||
|
||
## [0.9.14] - 2026-06-19
|
||
|
||
### Geändert
|
||
- **Updates werden beim Öffnen der Seite automatisch geprüft** (System → Version & Releases). Bisher
|
||
zeigte nur das Badge eine neuere Version; der „Jetzt aktualisieren"-Knopf erschien erst nach Klick
|
||
auf „Nach Updates suchen". Die Seite prüft jetzt direkt beim Laden selbst (asynchron via
|
||
`wire:init`, blockiert das Rendern nicht) und zeigt ein verfügbares Update samt Knopf sofort an —
|
||
kein manuelles Suchen mehr. Während eines laufenden Updates wird der Auto-Check übersprungen, damit
|
||
er den „läuft"-Zustand nicht überschreibt.
|
||
|
||
## [0.9.13] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Update-Status übersteht jetzt einen Reload (in Redis statt Sentinel gespeichert).** In 0.9.12
|
||
wurde der laufende Zustand aus der Sentinel-Datei abgeleitet — die der Host-Watcher aber **vor**
|
||
dem Update verbraucht, sodass ein Reload mitten im Update nichts mehr anzeigte. Der „läuft"-Status
|
||
wird jetzt als Redis-Marker (mit der Ausgangsversion) persistiert: er übersteht sowohl den Reload
|
||
als auch den Container-Neustart. Nach einem Reload wird entweder „Update läuft" wieder aufgenommen
|
||
(inkl. Polling) oder — falls das Update inzwischen durch ist — direkt „Aktuell — vX" mit
|
||
Erfolgsmeldung angezeigt und der Marker gelöscht (15-Min-Backstop-TTL, damit nichts hängen bleibt).
|
||
|
||
## [0.9.12] - 2026-06-19
|
||
|
||
### Hinzugefügt
|
||
- **Fertig-Rückmeldung beim Dashboard-Update.** Nach „Jetzt aktualisieren" zeigte die Seite nur
|
||
„Update läuft" ohne Ende. Sie pollt jetzt (alle 5 s) und erkennt den Abschluss daran, dass der
|
||
neu gebaute Container eine höhere Version meldet als beim Start des Updates — dann springt die
|
||
Karte automatisch zurück auf „Aktuell — vX" und ein Erfolgs-Toast erscheint. Übersteht die
|
||
Neustart-Downtime (fehlgeschlagene Polls werden einfach wiederholt) und bricht nach ~5 Min mit
|
||
einem Hinweis ab, falls das Update hängt — statt ewig zu drehen. Ein laufendes Update wird auch
|
||
nach einem Seiten-Reload wieder aufgenommen (solange die Sentinel-Datei besteht).
|
||
|
||
### Geändert
|
||
- **Verfügbares Update wird sofort beim Laden angezeigt.** Stand der neueste Tag bereits aus einer
|
||
vorherigen Prüfung im Cache, zeigte nur das Badge die neue Version — der „Jetzt aktualisieren"-
|
||
Knopf erschien aber erst nach erneutem „Nach Updates suchen". Jetzt erscheint er direkt beim
|
||
Seitenaufbau (ohne Netzwerkanfrage — rein aus dem Cache).
|
||
|
||
### Behoben
|
||
- **Zeitstempel in lokaler Zeit statt UTC.** `app.timezone` ist jetzt env-gesteuert; `install.sh`
|
||
ermittelt die Zeitzone des Hosts (systemd / `/etc/timezone` / `/etc/localtime`) und schreibt sie
|
||
als `APP_TIMEZONE` in die `.env`. Datenbank-Zeitstempel bleiben UTC; nur die Anzeige (z. B. die
|
||
„geprüft"-Zeit) folgt der Host-Zeitzone. Default UTC, wenn nicht ermittelbar.
|
||
|
||
## [0.9.11] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **„Neuestes Release" zeigt nie mehr eine ältere Version als die installierte.** Direkt nach einem
|
||
Update konnte der 30-Minuten-Cache der Update-Prüfung noch den vorherigen Stand halten, sodass die
|
||
Karte „Installiert" z. B. „Neuestes Release v0.9.9" neben „installiert v0.9.10" zeigte (wirkte
|
||
rückwärts). Ist der gecachte Wert kleiner als die installierte Version, wird er jetzt verworfen
|
||
(Anzeige „—"), bis „Nach Updates suchen" den echten neuesten Tag auflöst.
|
||
|
||
## [0.9.10] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Build-Commit & Branch werden jetzt auch in Produktion angezeigt** (System → Version & Releases,
|
||
Karte „Installiert"). Sie wurden zur Laufzeit aus `.git` gelesen — das im Docker-Prod-Image
|
||
fehlt — also stand dort „Build —" und als Branch der Kanal. `install.sh` bäckt den deployten
|
||
Commit (`git rev-parse --short HEAD`) und Branch jetzt aus dem `.git` des Hosts in die `.env`
|
||
(`CLUSEV_BUILD_SHA`/`CLUSEV_BUILD_BRANCH`); die Versions-Seite bevorzugt diese und liest im Dev
|
||
weiterhin direkt aus `.git`.
|
||
|
||
## [0.9.9] - 2026-06-19
|
||
|
||
### Hinzugefügt
|
||
- **„Jetzt aktualisieren" im Dashboard (System → Version & Releases).** Ist ein neueres Release im
|
||
Kanal verfügbar, lässt sich das Update jetzt per Knopf auslösen — ganz ohne SSH. Gleiches sichere
|
||
Muster wie der Neustart-Knopf: der Container schreibt nur eine Sentinel-Datei (kein Docker-Socket),
|
||
ein **als root laufender** Host-Watcher (`clusev-update.path`/`.service`) führt `update.sh` aus
|
||
(`git pull` + idempotenter Re-Install) und verbraucht die Sentinel **vor** dem Lauf, damit ein
|
||
dauerhafter Fehler nicht endlos wiederholt. Der Knopf erscheint nur, wenn eine neuere Version
|
||
gefunden wurde, ist per-Nutzer gedrosselt (3/10 Min, auto-ablaufend) und auditiert
|
||
(`deploy.update_request`). Hinweis im UI: das Dashboard ist während des Rebuilds ein bis zwei
|
||
Minuten nicht erreichbar; danach neu laden. `install.sh` installiert die neuen Host-Units mit.
|
||
|
||
## [0.9.8] - 2026-06-19
|
||
|
||
### Hinzugefügt
|
||
- **`update.sh` — Ein-Befehl-Update (`sudo ./update.sh`).** Holt den neuesten Stand
|
||
(`git pull --ff-only`, verwirft nie lokale Änderungen), **aktualisiert sich bei Bedarf selbst**
|
||
(hat sich `update.sh` im Pull geändert, startet es die neue Version neu) und übergibt dann an den
|
||
idempotenten `install.sh` (Build, Neustart, Migration, MOTD). Setzt für root automatisch
|
||
`safe.directory` (das Verzeichnis gehört dem `clusev`-Nutzer, sonst verweigert ein root-`git pull`)
|
||
und ruft `install.sh` **nicht-interaktiv** auf, sodass nichts neu abgefragt wird.
|
||
|
||
### Behoben
|
||
- **Re-Run von `install.sh` bewahrt jetzt Domain & ACME-E-Mail.** Bislang schrieb der Installer
|
||
`APP_DOMAIN`/`ACME_EMAIL` bei jedem Lauf — ein nicht-interaktiver Re-Run (also ein Update) ohne
|
||
gesetzte `CLUSEV_DOMAIN` hätte eine konfigurierte Domain **gelöscht**. Beide defaulten jetzt auf den
|
||
bestehenden `.env`-Wert; Erst-Installation (leer = Bare-IP) unverändert.
|
||
|
||
### Geändert
|
||
- **README-Update-Abschnitt** auf `sudo ./update.sh` umgestellt (der alte Zweischritt
|
||
`git pull && sudo ./install.sh` funktioniert weiterhin); MOTD zeigt den Update-Befehl an.
|
||
|
||
## [0.9.7] - 2026-06-19
|
||
|
||
### Behoben
|
||
- **Update-Prüfung (System → Version & Releases) funktioniert jetzt in Produktion.** Sie las das
|
||
neueste Release aus dem lokalen `.git` — das es im Docker-Prod-Image gar nicht gibt, daher meldete
|
||
„Nach Updates suchen" dort **immer** „Noch kein Release getaggt", selbst wenn längst eine neuere
|
||
Version veröffentlicht war. Der neueste Tag im Kanal wird jetzt über die **öffentliche, lesende
|
||
Tag-API des Git-Hosts** abgefragt (anonym — kein Token verlässt das Panel), mit lokalem `.git` nur
|
||
noch als Dev-Fallback; das Ergebnis ist gecacht, sodass ein Seitenaufruf nie eine Netzwerkanfrage
|
||
auslöst (nur der Knopf selbst). Kanal-Logik: `stable` bietet nur `vX.Y.Z`, `beta` auch Vorab-Tags.
|
||
Schlägt die Abfrage fehl (offline o. Ä.), degradiert sie still statt zu blockieren.
|
||
|
||
## [0.9.6] - 2026-06-19
|
||
|
||
### Geändert
|
||
- **Erst-Admin nutzt jetzt eine feste Standard-E-Mail `admin@clusev.local`** (statt einer aus dem
|
||
Hostnamen abgeleiteten Adresse, die man hätte raten müssen). Zusammen mit dem Standard-Passwort
|
||
`clusev` ist der Erst-Login auf jeder frischen Installation vorhersehbar (`admin@clusev.local` /
|
||
`clusev`, Zwangswechsel beim ersten Login). `--email` / `CLUSEV_ADMIN_EMAIL` überschreiben weiter;
|
||
später unter Einstellungen → Profil änderbar. Installer-Prompt, README und Banner aktualisiert.
|
||
|
||
### Behoben
|
||
- **Command-Palette öffnete sich nach dem ersten Login von selbst und ließ sich nicht schließen**
|
||
(nur im Prod-Build). Ursache: `app.js` baute `window.Echo` auf Top-Level; auf Auth-Seiten fehlt die
|
||
Laufzeit-Reverb-Config (`window.__clusev`) und der Prod-Build bäckt absichtlich keine
|
||
`VITE_REVERB_*`-Fallbacks ein, also warf der Echo-Konstruktor — und brach das Skript **vor** der
|
||
`alpine:init`-Registrierung ab. Dadurch waren `cmdk`/`modalTrigger`/`dualChart` nicht registriert,
|
||
Alpine konnte `x-data="cmdk(…)"` nicht auflösen, entfernte aber `x-cloak`, und das Palette-Overlay
|
||
fiel auf seine sichtbare Default-Darstellung zurück (undismissbar). Echo wird jetzt nur bei
|
||
vorhandener Config konstruiert, in `try/catch` gekapselt (ein Realtime-Fehler legt die UI nie lahm)
|
||
und bei `livewire:navigated` nachgezogen (Realtime auch auf dem per-SPA erreichten Dashboard). Im
|
||
Dev mit Vite trat es nie auf, da Vite die `VITE_REVERB_*` aus der Dev-`.env` einbäckt.
|
||
|
||
## [0.9.5] - 2026-06-17
|
||
|
||
### Geändert
|
||
- **Erst-Admin nutzt jetzt das Standard-Passwort `clusev`** (statt eines Einmal-Zufallspassworts) —
|
||
vorhersehbarer Login. **`must_change_password` erzwingt den Wechsel beim ersten Login**, und
|
||
Banner/MOTD/README weisen ausdrücklich darauf hin, es **sofort** zu ändern (ein bekanntes Default
|
||
ist nur durch den erzwungenen Wechsel vertretbar). Idempotenz unverändert (kein Admin bei Re-Run).
|
||
- **Host-MOTD deutlich aufgewertet.** Statt zwei Zeilen jetzt eine orange CLUSEV-Wortmarke, die
|
||
Version, ein **Live-Stack-Status** („X/Y Dienste aktiv", grün/rot — bei jedem Login frisch aus
|
||
`docker compose ps`), die Dashboard-URL, eine Login-Hinweiszeile (woher die Zugangsdaten kommen —
|
||
ohne Secret) und die Verwaltungs-Befehle. `install.sh` setzt URL, Version und den Compose-Pfad ein;
|
||
fällt bei fehlendem Docker robust auf „Status unbekannt" zurück (bricht nie einen Login ab).
|
||
|
||
### Behoben
|
||
- **README-Voraussetzungen:** `git` ist auf minimalen Debian/Ubuntu-Images nicht vorinstalliert, der
|
||
`git clone`-Schritt scheiterte dort vor dem Installer. Der Install-Block installiert `git` jetzt
|
||
explizit, und „Voraussetzungen" nennt es (ein Full-VM-Installtest hat das aufgedeckt).
|
||
|
||
## [0.9.4] - 2026-06-17
|
||
|
||
### Hinzugefügt
|
||
- **„DNS prüfen & Zertifikat anfordern" im Dashboard (System → Domain & TLS).** Wurde eine Domain
|
||
konfiguriert, deren DNS noch nicht auf den Server zeigte, lässt sich das Let's-Encrypt-Zertifikat
|
||
jetzt per Knopf anstoßen, sobald das DNS stimmt: eine DNS-Vorabprüfung (zeigt die Domain hierher?)
|
||
und — nur ohne klare Fehlanpassung — ein interner Handshake, der Caddys On-Demand-Ausstellung
|
||
auslöst, plus ein persistierter Status (aktiv / DNS zeigt woanders / ausstehend). Sichtbar nur im
|
||
eingebauten-TLS-Modus mit aktiver Domain (im Externer-Proxy-Modus ein Hinweis, bei Bare-IP nichts).
|
||
Per-Nutzer gedrosselt (5/10 Min, auto-ablaufend — sperrt die Control-Plane nie aus) + auditiert; der
|
||
Handshake ist fest auf den `caddy`-Dienst gepinnt (kein SSRF). Hinweis: das automatische On-Demand
|
||
greift weiterhin von selbst beim ersten HTTPS-Aufruf — der Knopf gibt nur explizite Kontrolle +
|
||
Statusrückmeldung.
|
||
|
||
## [0.9.3] - 2026-06-17
|
||
|
||
### Behoben
|
||
- **Modal-Trigger zuverlässig + mit Feedback.** Modale gingen direkt nach einer Navigation teils
|
||
erst beim 2.–3. Klick auf (Konsole: „Could not find Livewire component in DOM tree"), und der
|
||
Button gab kein Signal. Ursache: der globale Modal-Manager wurde bei jedem `wire:navigate` ab-
|
||
und neu aufgebaut, sodass sein `openModal`-Listener kurz fehlte. Er bleibt jetzt über `@persist`
|
||
für die ganze Sitzung bestehen — Modale öffnen beim ersten Klick. Zusätzlich zeigt **jeder**
|
||
Modal-Trigger (in allen Views) ab Klick sofort einen Spinner und blendet einen Fehler-Toast ein,
|
||
falls nach kurzer Zeit nichts aufgeht (neues `<x-modal-trigger>`-Element; der Toaster kennt jetzt
|
||
eine rote Fehler-Variante). So kann ein Klick nie tot wirken und ein echtes Fehlschlagen ist nie
|
||
stumm.
|
||
|
||
### Sicherheit
|
||
- **Passwort-Reset-Timing weiter abgeflacht.** Der Dummy-Verifikationspfad für unbekannte Konten
|
||
spiegelt jetzt exakt die Kosten der echten Recovery-Code-Prüfung (gesperrtes `SELECT` in einer
|
||
Transaktion statt eines schwereren bcrypt-Vergleichs), und Transportfehler beim Reset-Link werden
|
||
geloggt statt stillschweigend verschluckt.
|
||
- **Brute-Force-/Rate-Limiting-Härtung (Audit-Nachgang).** Alle neuen Limits laufen automatisch ab
|
||
und sperren die Control-Plane nie dauerhaft (Bare-IP-Recovery + `clusev:reset-admin` bleiben offen):
|
||
- **Login** zusätzlich pro IP (20/Min) und pro Konto (30/15 Min) begrenzt — ein verteilter
|
||
Mehr-IP-Angriff auf das eine Admin-Konto wird gedeckelt statt linear zu skalieren; und der
|
||
Login-Zweig für unbekannte E-Mails führt jetzt denselben bcrypt aus (Konstantzeit) und schließt
|
||
so das Konten-Enumerations-Timing-Orakel.
|
||
- **TOTP-Replay-Schutz:** jeder 6-stellige Code ist genau einmal gültig (zuletzt akzeptierter
|
||
Zeit-Schritt wird persistiert), ein abgefangener Code lässt sich nicht im 30-Sek-Fenster
|
||
wiederverwenden.
|
||
- **2FA-Challenge & 2FA-Beweis-Reset** zusätzlich pro Konto begrenzt (IP-unabhängiger Backstop),
|
||
sodass die TOTP-/Backup-Code-Brute-Force-Fläche auch verteilt gedeckelt ist.
|
||
- **Globaler `/livewire/update`-Throttle** (180/Min pro Identität) als Flut-Deckel über den
|
||
Per-Aktion-Limitern.
|
||
- **Re-Auth gedrosselt** (Passwortänderung/-Profil, 5/Min pro Nutzer) gegen Passwort-Brute-Force
|
||
über eine gekaperte Session; **SMTP-Testversand** auf 3/10 Min pro Nutzer begrenzt (kein
|
||
Spam-Relay).
|
||
- **DoS-Schutz:** SSH bekommt einen kurzen, separaten Connect-Timeout (~5 s), damit ein toter/
|
||
langsamer Host einen Worker nicht über mehrere sequentielle Reads × 15 s blockiert; die
|
||
Profil-Passwort-Policy auf 12 Zeichen + Groß/Klein + Ziffern angehoben (war 10).
|
||
|
||
## [0.9.2] - 2026-06-15
|
||
|
||
### Hinzugefügt
|
||
- **Ein-Befehl-Installer (`sudo ./install.sh`).** Bootstrappt einen frischen Debian/Ubuntu-Host
|
||
vollständig: installiert Docker aus dem offiziellen apt-Repo, legt einen dedizierten
|
||
`clusev`-Login-Benutzer (Zufallspasswort, `docker`-Gruppe) an, prüft per DNS-Abgleich, ob die
|
||
angegebene Domain auf diesen Host zeigt (sonst Warnung + Wahl „Domain trotzdem nehmen — HTTP bis
|
||
DNS stimmt" oder „auf der IP bleiben"), baut/startet den Stack, migriert und legt den ersten Admin
|
||
an. Das Abschluss-Banner zeigt ausschließlich Dashboard-URL, Admin-Login und — bei Erstanlage — den
|
||
`clusev`-Host-Benutzer; Passwörter erscheinen nur dieses eine Mal. Idempotent (Re-Run baut/migriert
|
||
neu, Secrets bleiben erhalten).
|
||
- **Themed Host-MOTD.** Bei jeder Host-Anmeldung erscheint ein Login-Banner in der
|
||
Produkt-Signalfarbe mit der Dashboard-Adresse.
|
||
|
||
### Geändert
|
||
- **Professionelles README** — Produktbeschreibung, Voraussetzungen, Installation (`git clone` +
|
||
`sudo ./install.sh`), Domain/TLS (inkl. externem Reverse-Proxy), Updates, Konto-Wiederherstellung
|
||
und AGPL-Lizenz; ohne interne Entwicklungs-Hinweise.
|
||
|
||
### Sicherheit
|
||
- **Passwort-Reset in konstanter Zeit.** Unbekannte E-Mail-Adressen durchlaufen jetzt denselben
|
||
Verifikationspfad (Dummy-Hash- bzw. Code-Prüfung) wie existierende Konten — die Antwortzeit verrät
|
||
nicht mehr, ob ein Konto existiert (Account-Enumeration über ein Timing-Orakel ausgeschlossen).
|
||
|
||
## [0.9.1] - 2026-06-15
|
||
|
||
### Geändert
|
||
- **Passwort-vergessen ist jetzt SMTP-bewusst.** Mit konfiguriertem SMTP ist der Standard ein
|
||
**E-Mail-Reset-Link (15 Minuten gültig)**; ein Zweit-Schalter „Kein E-Mail-Zugang? Mit 2FA-Code
|
||
zurücksetzen" blendet den Inline-2FA-Reset ein. Ohne SMTP nur der 2FA-Besitznachweis-Reset. Der
|
||
`clusev:reset-admin`-Hinweis ist von der öffentlichen Seite verschwunden und steht jetzt in
|
||
Einstellungen → Sicherheit (eingeloggt) + README.
|
||
- **System-Seite neu komponiert** statt nur schmaler: 2-Spalten-Layout (links Konfiguration —
|
||
Domain + TLS-Modus, rechts Status & Wiederherstellung), kritische Hinweise (Neustart, Bare-IP) als
|
||
Banner über dem Raster.
|
||
- **Firewall/fail2ban-Bereich:** eine einzeln installierte Box füllt jetzt die volle Breite mit
|
||
interner 2-Spalten-Aufteilung (kein leerer Raum mehr); zwei Boxen weiterhin nebeneinander.
|
||
|
||
### Sicherheit
|
||
- **Backup-Codes nur einmal sichtbar — wasserdicht.** Codes werden ausschließlich direkt nach der
|
||
Erzeugung (erster Faktor oder „Neu erzeugen") angezeigt; „Backup-Codes verwalten" zeigt sie NICHT
|
||
mehr an, sondern bietet nur „Neu erzeugen". Der Reveal läuft über einen transienten Kanal (nicht über
|
||
eine persistierte Livewire-Property), sodass ein abgefangener/replayter Snapshot sie nicht erneut
|
||
rendert; das Reveal-Flag hat eine 10-Minuten-TTL, und der Download-Endpunkt verlangt eine frische,
|
||
einmalige Berechtigung.
|
||
- **Bestätigungs-Dialoge signiert & einmalig.** Destruktive Aktionen (Konto/Session/2FA/Hardening/…)
|
||
laufen über server-ausgestellte, einmal gültige, signierte Tokens statt client-manipulierbarer
|
||
Event-/Audit-Felder — Bestätigungs-Bypass und Audit-Fälschung sind ausgeschlossen; per-Aktion
|
||
server-seitig auf das Ziel beschränkt.
|
||
|
||
### Behoben
|
||
- **fail2ban „Gesperrte IPs"-Modal öffnet sofort** — Skeleton + Lazy-Load statt blockierendem
|
||
SSH-Read beim Öffnen.
|
||
- **Security-Keys-Untertitel** korrigiert: ein Schlüssel kann alleiniger Faktor sein, mit TOTP oder
|
||
beides — nicht mehr „nur als zweiter Faktor".
|
||
- **System-Seite:** TLS-Modus-Schalter bricht nicht mehr um (kürzeres Label, Details in der
|
||
Beschreibung); Domain-Feld zeigt nur noch einen Platzhalter (kein vorausgefüllter Beispielwert).
|
||
- **Einstellungen mobil:** die Tab-Leiste ist breiter als der Schirm geworden — jetzt horizontal
|
||
scrollbar und sauber abgeschnitten (auf Desktop weiterhin vertikal).
|
||
|
||
## [0.9.0] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **Mehrere Benutzer-Accounts.** Einstellungen → Benutzer: weitere (gleichberechtigte) Admin-Konten
|
||
anlegen, auflisten, entfernen. Neuanlage erzeugt ein Einmal-Passwort (einmalig angezeigt) +
|
||
erzwungenen Passwortwechsel beim ersten Login. Das Audit-Log hält fest, wer was getan hat.
|
||
- **Sitzungsverwaltung.** Einstellungen → Sitzungen: aktive Sitzungen ansehen (Gerät, IP, zuletzt
|
||
aktiv), „andere Geräte abmelden" (eigene) und „alle abmelden (global)"; je Benutzer „überall
|
||
abmelden". Jede Abmeldung rotiert den remember-Token.
|
||
- **Automatischer Neustart.** „Jetzt neu starten" in System schreibt eine Anforderung, die ein
|
||
Host-Watcher (systemd) ausführt — kein Terminal-Befehl, kein Docker-Socket im Container.
|
||
- **SMTP-Konfiguration.** Einstellungen → E-Mail: SMTP-Server hinterlegen (Passwort verschlüsselt),
|
||
Testmail senden. Damit funktionieren Passwort-vergessen-Mails wirklich.
|
||
- **Audit-Log-Aufbewahrung.** Konfigurierbare Aufbewahrungsdauer + täglicher `clusev:prune-audit`.
|
||
- **Datei-Manager zeigt Bilder an.** Öffnet man eine Bilddatei (png/jpg/gif/webp/svg/…), wird sie
|
||
als Vorschau angezeigt statt im Texteditor (SFTP→base64, max. 5 MB, Speichern für Bilder aus).
|
||
- **fail2ban: gesperrte IPs im Modal.** Die lange Jail-/Bann-Liste wandert aus dem Panel in ein
|
||
scrollbares Modal ("Gesperrte IPs ansehen") — mit Entsperren je IP.
|
||
- **Login & Passwortfelder: Anzeigen/Verbergen-Umschalter** (Auge-Icon) in Login, Passwort ändern,
|
||
Reset und Passwort-vergessen.
|
||
|
||
### Geändert
|
||
- **Sitzungen liegen jetzt in der Datenbank** (statt Redis), damit sie auflist- und widerrufbar sind.
|
||
**Einmaliger Effekt:** beim Update werden alle bestehenden Sitzungen ungültig — einmal neu anmelden.
|
||
- **Einstellungen in eigene Tabs** (Profil · Sicherheit · Benutzer · Sitzungen · E-Mail), jeweils als
|
||
eigene Komponente.
|
||
- **System-Seite aufgeräumt:** Inhalt auf eine angenehme Lesebreite begrenzt (nicht mehr
|
||
randlos), echtes Speichern-Icon, „Caddy" in der Oberfläche durch neutrale Begriffe ersetzt,
|
||
plus Hinweis auf die SSH-Notfall-Wiederherstellung (`clusev:reset-admin`).
|
||
- **Eingabefelder:** E-Mail-Felder nutzen jetzt `type="text"` — die Validierung (inkl. Format)
|
||
passiert ausschließlich serverseitig in der Livewire-Klasse (einheitliche, lokalisierte Meldungen).
|
||
- **Backup-Codes-Modal:** Buttons sauber ausgerichtet; der Abschluss-Button heißt jetzt „Schließen"
|
||
(er schließt nur). Alle Modal-Textfelder sind nicht mehr in der Größe ziehbar.
|
||
|
||
## [0.8.0] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **TLS über externen Reverse-Proxy (z. B. Zoraxy).** Neuer TLS-Modus in System → Domain & TLS:
|
||
„Externer Reverse-Proxy" statt „Caddy (Let's Encrypt)". Im externen Modus holt Caddy **kein**
|
||
Zertifikat mehr (`/_caddy/ask` verweigert) und das Panel erzwingt **keinen** eigenen
|
||
HTTPS-Redirect — ein Proxy davor terminiert TLS und liefert an den HTTP-Port. Die Host-Prüfung
|
||
und der Bare-IP-Recovery-Pfad bleiben in beiden Modi aktiv; die Umstellung gilt nach Stack-Neustart.
|
||
Caddy vertraut dem `X-Forwarded-Proto` des Proxys über `TRUSTED_PROXY_CIDR` (Default `127.0.0.1/32`
|
||
= nichts Externes); Hinweis in der UI, den HTTP-Port per Firewall nur dem Proxy zugänglich zu machen.
|
||
|
||
### Behoben (Sicherheit)
|
||
- **Onboarding-Gate auch auf Livewire-Updates.** `EnsureSecurityOnboarded` ist jetzt persistente
|
||
Livewire-Middleware: das Gate greift auch bei `/livewire/update`-Aufrufen, nicht nur beim
|
||
Seiten-GET — eine während des Onboardings gemountete Komponente kann keine destruktiven Aktionen
|
||
mehr ausführen, nachdem das Konto mitten in der Sitzung auf „Passwort ändern erforderlich"
|
||
zurückgesetzt wurde.
|
||
|
||
## [0.7.0] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **Optionale, pluggbare 2FA (TOTP und/oder Security-Key — oder aus).** 2FA ist nicht mehr
|
||
erzwungen: Der Betreiber entscheidet selbst, ob er sein Konto absichert, und wählt TOTP, einen
|
||
Security-Key (YubiKey/WebAuthn) oder beides — oder lässt 2FA ganz aus. Empfohlen, nicht Pflicht.
|
||
- **Onboarding erzwingt nur noch die Passwort-Rotation** (kein 2FA-Zwang mehr). Nach dem
|
||
Passwortwechsel landet man direkt im Dashboard; 2FA wird in den Einstellungen angeboten
|
||
(Hinweis „empfohlen").
|
||
- **Ein Security-Key kann der einzige/erste Faktor sein** (vorher war TOTP Voraussetzung).
|
||
TOTP und Keys werden in Einstellungen → Sicherheit unabhängig hinzugefügt/entfernt.
|
||
- **Login-Challenge passt sich dem Faktor an:** TOTP-Nutzer sehen das Code-Feld, reine
|
||
Key-Nutzer führen mit dem Security-Key (Backup-Code als Rückfall) — kein irreführendes
|
||
6-stelliges Feld mehr.
|
||
- **Backup-Codes als Modal** statt eigener Seite: werden beim ersten Faktor erzeugt und einmal
|
||
gezeigt, später jederzeit über „Backup-Codes verwalten" wieder einsehbar / neu erzeugbar /
|
||
herunterladbar. Die alte `/two-factor/recovery-codes`-Seite + Route entfällt (Download bleibt).
|
||
- **Backup-Codes folgen den Faktoren:** beim Entfernen des letzten Faktors werden sie geleert,
|
||
bei verbleibendem Faktor behalten.
|
||
- **Passwort-vergessen ist key-only-sicher:** ein reiner Key-Nutzer setzt per Backup-Code zurück;
|
||
ohne 2FA per E-Mail-Link (falls SMTP) oder `clusev:reset-admin`.
|
||
- **SSH-Key automatisch erstellen & Passwort-Login sicher deaktivieren.** Statt der bisherigen
|
||
Sackgasse („Key zuerst hinterlegen") erzeugt das Panel auf Knopfdruck einen ed25519-Key,
|
||
installiert ihn, **prüft den Key-Login**, stellt die eigene gespeicherte Verbindung darauf um —
|
||
und schaltet Passwort-Login **erst danach** ab. Scheitert die Prüfung, bleibt Passwort-Login aktiv
|
||
(kein Aussperren, auch nicht der Control-Plane). Der Private-Key wird einmalig angezeigt
|
||
(Download) und verschlüsselt im Vault gehalten.
|
||
- **Schnellsuche (Strg/⌘ K) findet Server:** Tippen von Name oder IP springt direkt zur
|
||
Server-Detailseite.
|
||
- **Journal ist live:** `journalctl` wird per Cursor inkrementell nachgeladen (alle 5 s, lückenlos,
|
||
ohne Duplikate) statt eines einmaligen Schnappschusses; Anzeige auf 200 Zeilen begrenzt und
|
||
scrollbar (fixe Höhe), folgt automatisch dem neuesten Eintrag.
|
||
|
||
### Geändert
|
||
- **Metriken-Broadcast-Gate:** `securityOnboarded()` heißt jetzt „Passwort rotiert" (nicht mehr
|
||
„+ 2FA"), da 2FA optional ist — Metriken streamen an eine passwort-rotierte Session.
|
||
|
||
### Behoben
|
||
- **Alleinstehende Firewall-/fail2ban-Box** dehnte sich über die volle Breite; ist jetzt auf eine
|
||
sinnvolle Kartenbreite begrenzt, wenn nur ein Tool installiert ist.
|
||
- **Einstellungen-Tab in der URL** (`?tab=…`): Reload/Deep-Link bleibt auf dem gewählten Tab statt
|
||
auf „Profil" zurückzuspringen.
|
||
|
||
## [0.6.1] - 2026-06-14
|
||
|
||
### Behoben
|
||
- **Sidebar-Server-Switcher driftete von der Detailseite ab.** Es gab zwei „aktueller
|
||
Server"-Begriffe: den Switcher (Session) und die `/servers/{uuid}`-Detailseite. Jetzt setzt der
|
||
Aufruf einer Detailseite den Server aktiv (Sidebar spiegelt immer die Seite), und ein Wechsel im
|
||
Switcher auf einer Detailseite navigiert zum gewählten Server statt die alte Seite neu zu laden.
|
||
Geschrieben wird nur bei bewusster Navigation (race-frei). *Bekannte Rest-Kante:* nach
|
||
Back/Forward (wire:navigate-Cache, kein erneutes `mount()`) zeigt die gecachte Seite den richtigen
|
||
Server, aber die Session kann kurz auf den vorigen zeigen — löst sich bei der nächsten bewussten
|
||
Navigation; inhärent am Single-Session-Modell (auch Multi-Tab).
|
||
|
||
## [0.6.0] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **WebAuthn / Security-Keys (YubiKey) als optionaler 2. Login-Faktor.** Per
|
||
`web-auth/webauthn-lib`. TOTP + Backup-Codes bleiben der erforderliche 2FA und die einzige
|
||
Wiederherstellung; ein Security-Key ist eine zusätzliche Login-Option.
|
||
- **Gated auf Domain + HTTPS:** Die rpId ist die aktive Domain — auf Bare-IP/HTTP ist die
|
||
Funktion ausgeblendet (mit Hinweis), da eine IP keine gültige rpId ist.
|
||
- **Einstellungen → Sicherheit:** Security-Keys hinzufügen (benannt), auflisten, entfernen
|
||
(mit Bestätigung). Beim Login-2FA-Schritt „Mit Security-Key anmelden" neben TOTP/Backup-Code.
|
||
- 2FA deaktivieren (UI **und** `clusev:reset-admin --disable-2fa`) entfernt jetzt auch
|
||
Security-Keys + Backup-Codes, damit ein Reset den zweiten Faktor vollständig zurücksetzt.
|
||
|
||
### Sicherheit
|
||
- rpId strikt = aktive Domain (nie eine IP). Challenges serverseitig + einmalig.
|
||
- Signatur-Zähler-Prüfung mit Rollback-/Klon-Erkennung; Zähler 0 nur toleriert, wenn der
|
||
gespeicherte Zähler ebenfalls 0 ist (Plattform-Authenticator/Passkeys).
|
||
- Registrierung erfordert eine aktive 2FA-Anmeldung; Label wird vor der Browser-Ceremony
|
||
validiert (keine verwaisten Credentials). Alle Aktionen im Audit-Log.
|
||
|
||
> Hinweis: Der echte End-to-End-Test (YubiKey registrieren + anmelden) ist nur unter einer Domain
|
||
> mit HTTPS möglich; die Ceremony-Logik ist auf Options-/Speicher-/Gating-Ebene getestet.
|
||
|
||
## [0.5.0] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **Kontowiederherstellung.**
|
||
- **2FA-Backup-Codes:** 8 Einmal-Codes werden bei der 2FA-Einrichtung erzeugt, einmalig
|
||
angezeigt und als `.txt` herunterladbar; in den Einstellungen (Sicherheit) neu erzeugbar.
|
||
Beim Login-2FA-Schritt als Alternative zum Authenticator-Code verwendbar.
|
||
- **„Passwort vergessen":** Reset über einen 2FA-Code oder Backup-Code (ohne E-Mail nötig).
|
||
Zusätzlich ein E-Mail-Reset-Link, sobald ein echter Mailer konfiguriert ist
|
||
(`mail.default` ≠ `log`/`array`).
|
||
- **CLI-Notfall:** `php artisan clusev:reset-admin` setzt das Admin-Passwort (und optional 2FA
|
||
+ Backup-Codes) per Server-Shell zurück — der Last-Resort bei kompletter Aussperrung.
|
||
|
||
### Sicherheit
|
||
- Passwort-Resets (Formular, E-Mail-Link, CLI) rotieren das `remember_token` — eine gestohlene
|
||
„Angemeldet bleiben"-Cookie überlebt einen Reset nicht.
|
||
- Backup-Codes werden atomar (Row-Lock) verbraucht (kein Replay).
|
||
- Der Notify-Toaster liegt jetzt auch auf den Auth-Seiten; alle Reset-/Backup-Aktionen werden im
|
||
Audit-Log erfasst.
|
||
|
||
## [0.4.6] - 2026-06-14
|
||
|
||
### Behoben
|
||
- **Validierungsmeldungen waren nur Englisch.** Es gab keine `lang/{de,en}/validation.php`, also
|
||
fiel Laravel bei jedem Eingabefeld-Fehler auf seine eingebauten englischen Meldungen zurück —
|
||
unabhängig von der gewählten Sprache. Vollständige `validation.php` in Deutsch (Quelle) und
|
||
Englisch ergänzt, plus lokalisierte Attribut-Namen (z. B. „SSH-Port", „E-Mail-Adresse"). Leeres
|
||
Login-Formular zeigt jetzt z. B. „E-Mail-Adresse muss ausgefüllt werden.".
|
||
|
||
## [0.4.5] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **Favicons + PWA-Manifest.** Vollständiger Icon-Satz aus dem App-Brand-Mark (oranges
|
||
Server-Glyph auf Dunkel): `favicon.svg` (skalierbar), `favicon.ico` (16/32/48),
|
||
`apple-touch-icon.png`, `icon-192/512.png` und `site.webmanifest`. In beiden Layouts via
|
||
gemeinsames `partials/head-icons` eingebunden (incl. `theme-color`); nginx liefert das
|
||
Manifest als `application/manifest+json`.
|
||
- **Eigene Fehlerseiten.** Gebrandete, zweisprachige Seiten für 403/404/419/429/500/503 auf
|
||
einem eigenständigen Fehler-Layout (Design-Tokens, Brand-Mark) — ersetzen Laravels
|
||
Standard-Fehlerseiten. Es werden nie rohe Status-Texte oder Stacktraces gezeigt; in Prod ist
|
||
`APP_DEBUG` fest auf `false` gepinnt.
|
||
|
||
### Behoben
|
||
- **Roher Fehlertext beim Datei-Download.** Der Download-Fehler zeigte die unverpackte
|
||
Exception-Meldung; jetzt eine lokalisierte Meldung (`files.download_failed`), konsistent mit
|
||
Upload/Löschen.
|
||
- **Fehlerseiten-Sprache.** Früh geworfene Fehler (CSRF 419, gematchte Aborts) berücksichtigen
|
||
jetzt die Nutzersprache (locale-Auflösung im Exception-Hook, absturzsicher gekapselt).
|
||
|
||
## [0.4.4] - 2026-06-14
|
||
|
||
### Behoben
|
||
- **Zoom beim Fokus auf Eingabefelder (Handy/Tablet).** iOS Safari zoomt die Seite hinein, wenn
|
||
ein fokussiertes Eingabefeld eine Schriftgröße < 16px hat — durch das dichte Design (`text-xs`/
|
||
`text-sm` auf Inputs) trat das bei jedem Feld auf. Eine zentrale Regel (`@media (pointer:
|
||
coarse)`) erzwingt jetzt 16px für `input`/`textarea`/`select` auf Touch-Geräten (Handy + Tablet
|
||
jeder Größe, inkl. iPad Pro 1024/1366px), während das Desktop-Layout (Maus) unverändert klein
|
||
bleibt. Per Touch-Emulation über alle Eingabefelder verifiziert.
|
||
|
||
## [0.4.3] - 2026-06-14
|
||
|
||
### Behoben
|
||
- **Mobile Overflow durch lange URLs und IPv6-Bans.** Responsive-Audit (R7) über alle Views bei
|
||
375/768px: mit den aktuellen Daten lief nichts über, aber lange Werte konnten Zeilen über den
|
||
Viewport schieben. Behoben: die Panel-Domain (`break-all` am Link, `min-w-0` an der
|
||
Zugriffsadresse) im System-Bereich und gebannte IPv6-Adressen in der fail2ban-Liste
|
||
(`min-w-0 break-all`). Per injizierter 253-Zeichen-Domain und langer IPv6 bei 375px verifiziert.
|
||
|
||
## [0.4.2] - 2026-06-14
|
||
|
||
### Hinzugefügt
|
||
- **Server-Status „Initialisierung".** Neu angelegte Server starten in einem cyanfarbenen
|
||
`pending`-Zustand (nicht mehr sofort rot/Offline), bis der erste Kontakt sie auf online/offline
|
||
setzt.
|
||
- **SSH-Verbindungstest beim Anlegen.** Beim Server-Anlegen wird die SSH-Anmeldung in der
|
||
Anlage-Transaktion geprüft (Login + Befehls-Marker beweisen, dass Befehle ausführbar sind);
|
||
schlägt sie fehl, wird nichts gespeichert und der SSH-Grund im Formular angezeigt.
|
||
- **Hinweis „nur per SSH-Key".** Die SSH-Passwort-Login-Zeile der Härtungs-Checkliste weist
|
||
darauf hin, dass der Zugang nach dem Deaktivieren nur noch per SSH-Key möglich ist.
|
||
|
||
### Geändert
|
||
- **Einheitliche Buttons.** Alle Buttons haben durchgehend Rahmen + Hintergrund; Löschen-Aktionen
|
||
nutzen die neue `danger-soft`-Variante (dezent rot getönt, dauerhaft — nicht erst beim Hover).
|
||
Die rahmenlosen `ghost`/`ghost-danger`-Varianten sind entfallen.
|
||
- **Firewall- und fail2ban-Boxen** erscheinen nur noch, wenn das jeweilige Tool installiert ist
|
||
(oder die Statusabfrage fehlschlug), und liegen im responsiven 2-Spalten-Grid (eine Box → volle
|
||
Breite). Die redundante „nicht installiert"-Box entfällt — installiert wird über die
|
||
Härtungs-Checkliste.
|
||
- **Automatische Updates** gelten als Betreiber-Präferenz, nicht als Sicherheitsanforderung: der
|
||
Zustand wird neutral als aktiv/inaktiv angezeigt und „aus" nie mehr als unsicher (OFFEN) markiert.
|
||
|
||
### Behoben
|
||
- **fail2ban/UFW fälschlich „nicht installiert".** Die Härtungs-Checkliste erkennt ein Tool jetzt
|
||
auch dann als installiert, wenn nur die Binary auf dem PATH liegt oder der Dienst läuft (vorher
|
||
nur `dpkg -l … ^ii`) — konsistent mit den Status-Panels. Behebt den Widerspruch „Checkliste:
|
||
nicht installiert" vs. „Panel: aktiv, N gesperrt".
|
||
|
||
## [0.4.1] - 2026-06-14
|
||
|
||
### Behoben
|
||
- **Verstümmelter Header auf jeder eingeloggten Seite.** Ein Kommentar im `@php`-Block der
|
||
topbar enthielt die literalen Tokens `@php`/`@endphp`; Blades Raw-Block-Matcher nahm das
|
||
`@endphp` im Kommentar als Block-Ende und gab den Rest (inkl. `$title ??= …`) als Klartext im
|
||
`<header>` aus — der Seitentitel blieb zudem leer. Kommentar bereinigt; neue Regel **R17**
|
||
(keine Direktiv-Tokens in Blade-Kommentaren; gerenderte Ausgabe prüfen, nicht nur HTTP 200).
|
||
|
||
### Sicherheit
|
||
- **Realtime-Kanäle sind jetzt privat.** Der Live-Metrik-Broadcast (`MetricsTicked`) läuft über
|
||
einen `PrivateChannel`, autorisiert über `/broadcasting/auth` (im `web`-Group → PanelScheme +
|
||
Session). Damit kann niemand mehr allein mit dem (öffentlich gebündelten) App-Key Infrastruktur-
|
||
Metriken abonnieren — auch nicht über einen alten Caddy-Host nach einem Domain-Wechsel. Neue
|
||
Konvention (CLAUDE.md §3 / `routes/channels.php`): **jeder** Broadcast-Kanal ist privat.
|
||
|
||
## [0.4.0] - 2026-06-14
|
||
|
||
Die Panel-Domain ist wieder aus dem Dashboard änderbar — diesmal sicher: gespeichert wird in der Datenbank, angewendet beim Neustart, das Zertifikat holt Caddy automatisch.
|
||
|
||
### Hinzugefügt
|
||
- **Domain aus dem Dashboard ändern.** Auf der System-Seite lässt sich die Panel-Domain
|
||
eintragen oder leeren. Gespeichert wird als Einstellung (`panel_domain`, übersteuert die
|
||
Installations-Domain `APP_DOMAIN`); nach einem Stack-Neustart leiten sich URL, der
|
||
WebSocket-Endpunkt (Reverb), das TLS-Zertifikat und die Cookie-Sicherheit automatisch davon
|
||
ab. Kein Terminal-Editieren mehr — nur „speichern und neu starten".
|
||
- **Automatisches TLS per On-Demand (Caddy).** Caddy stellt das Zertifikat beim ersten
|
||
HTTPS-Zugriff aus und fragt vorher die App (`/_caddy/ask`), ob für genau diese Domain
|
||
ausgestellt werden darf — so kann keine fremde, auf die IP gezeigte Domain Zertifikate
|
||
erzwingen. Voraussetzung bleibt: die Domain zeigt per DNS auf den Server.
|
||
|
||
### Geändert
|
||
- Der Reverb-Client (Browser) bezieht seinen Endpunkt jetzt zur Laufzeit aus der aktuellen
|
||
Anfrage und läuft über dieselbe Adresse wie das Panel (Proxy/Reverse-Tunnel `/app/*`,
|
||
`/apps/*` — Caddy in Produktion, nginx in Dev). So folgt Realtime jeder Domain-Änderung
|
||
ohne Neu-Bauen der Assets und ohne separat geöffneten Port.
|
||
- Server-seitiges Event-Publishing läuft intern direkt zum `reverb`-Dienst, ist damit
|
||
unabhängig von der öffentlichen Domain/dem Zertifikat.
|
||
|
||
### Sicherheit
|
||
- **Kein Aussperren.** Sicherer-Cookie-Schalter und HTTPS-Redirect richten sich nach der
|
||
tatsächlichen Anfrage (hinter Caddy via `X-Forwarded-*`, das nur in Produktion vertraut
|
||
wird). Der Zugriff per **IP über HTTP bleibt immer als Rückfallweg erreichbar**, falls
|
||
eine neue Domain (DNS/Zertifikat) noch nicht bereit ist.
|
||
- **Nur die aktive Domain liefert das Panel aus.** Bei gesetzter Domain wird das Panel per
|
||
Hostname ausschließlich über genau diese Domain (HTTPS) bedient; eine alte/fremde, auf den
|
||
Server gezeigte Domain wird abgewiesen (404) — auch wenn Caddy ihr Zertifikat noch hält.
|
||
|
||
## [0.3.0] - 2026-06-13
|
||
|
||
Zweisprachige Oberfläche: die gesamte UI gibt es jetzt auf Deutsch und Englisch, frei umschaltbar pro Konto.
|
||
|
||
### Hinzugefügt
|
||
- **Sprache wählbar (Deutsch / Englisch).** Ein DE/EN-Umschalter (oben rechts, sowie auf der
|
||
Login-Seite) stellt die Sprache um; die Wahl wird pro Konto gespeichert und gilt sofort für
|
||
die gesamte Oberfläche. Ohne Wahl greift die Standardsprache (Deutsch).
|
||
- Übersetzungen liegen strukturiert unter `lang/{de,en}/<gruppe>.php` — eine Gruppe je Feature
|
||
plus geteiltes `common`, Schlüssel in beiden Sprachen identisch.
|
||
|
||
### Geändert
|
||
- **Alle sichtbaren Texte sind lokalisiert.** Views, Komponenten und Backend-Services geben
|
||
Text ausschließlich über `__('gruppe.schlüssel')` aus, nie mehr fest verdrahtet — neue,
|
||
verbindliche Regel **R16**. Native Technik-Begriffe (`nginx.service`, `SSH`, `2FA`, Ports)
|
||
bleiben unverändert.
|
||
|
||
### Entfernt
|
||
- Erläuternde „Warum …?"-Texte auf der System-Seite — die Seite zeigt nur noch Funktion und
|
||
Status.
|
||
|
||
## [0.2.1] - 2026-06-13
|
||
|
||
Verfeinerungen an 0.2.0: vollautomatisches TLS ohne sichtbare Konfiguration und einheitliche Buttons.
|
||
|
||
### Geändert
|
||
- **TLS ist vollautomatisch.** Caddy stellt das Zertifikat aus, erneuert es und leitet
|
||
HTTP→HTTPS um — ohne manuelle Schritte. Die System-Seite zeigt nur noch den **Status**
|
||
(Domain, Zugriffsadresse) statt der erzeugten Caddy-Konfiguration und des
|
||
`caddy reload`-Befehls. Die Panel-Domain ist eine **Installations-Entscheidung**
|
||
(`APP_DOMAIN`) und bleibt damit konsistent mit URL, WebSocket (wss) und Cookie-Sicherheit;
|
||
zum Ändern `install.sh` mit der neuen Domain erneut ausführen.
|
||
- Härtungs-Checkliste: alle Aktions-Buttons im selben Stil (einheitlich `secondary`,
|
||
mit Rahmen) statt zustandsabhängig mal mit, mal ohne Rahmen.
|
||
|
||
### Entfernt
|
||
- Laufzeit-Override der App-URL aus der Datenbank und das dashboard-seitige Bearbeiten
|
||
von Domain/E-Mail (konnte URL/WebSocket/Cookies nicht konsistent zur Laufzeit umstellen).
|
||
Eine Migration entfernt die leeren Alt-Einträge; ein real konfigurierter Wert bleibt als
|
||
Verlauf erhalten. **Upgrade-Hinweis:** eine zuvor im Dashboard gesetzte Domain bitte einmalig
|
||
über `install.sh` setzen.
|
||
|
||
## [0.2.0] - 2026-06-13
|
||
|
||
Mehr-Betriebssystem-Verwaltung, echte Firewall- und fail2ban-Steuerung aus dem Dashboard sowie eine Tastatur-/Befehls-Bedienung.
|
||
|
||
### Hinzugefügt
|
||
- **OS-Abstraktion**: Updates, Härtung und Firewall laufen jetzt über **apt, dnf und
|
||
zypper** (Debian/Ubuntu, RHEL/Fedora/Rocky/Alma, openSUSE). Das Betriebssystem wird
|
||
erkannt und auf der Server-Detailseite angezeigt (Paketverwaltung/Firewall). Arch
|
||
und Alpine werden erkannt; nicht unterstützte Funktionen werden mit deutschem
|
||
Hinweis sauber deaktiviert statt fehlzuschlagen.
|
||
- **Firewall-Regeln (UFW)**: Regeln ansehen, hinzufügen und löschen samt
|
||
Standard-Anzeige — mit Aussperr-Schutz (SSH-Regeln/-Port, deny ohne Port). firewalld
|
||
wird nur angezeigt (Laufzeit aller aktiven Zonen inkl. Rich Rules); Regelverwaltung
|
||
bleibt vorerst UFW.
|
||
- **fail2ban-Status**: Jails mit Zählern, **gebannte IPs sehen und entsperren**,
|
||
IP manuell sperren (R5-Dialog, Aussperr-Schutz für Loopback + eigenen Zugang) und
|
||
**Whitelist (ignoreip)** verwalten.
|
||
- **Befehls-Palette (Strg/⌘-K)** + Tastenkürzel: Schnell-Navigation (Leader „g“),
|
||
Seitensuche („/“), Hilfe („?“).
|
||
|
||
### Geändert
|
||
- System-Updates verwenden je Distribution den passenden Paketmanager
|
||
(Audit-Aktion `system.package_upgrade`).
|
||
- fail2ban-Tuning und -Whitelist liegen in getrennten Drop-ins, damit Änderungen sich
|
||
nicht gegenseitig überschreiben; das frühere Einzel-Drop-in wird migriert.
|
||
- Server-Detailseite zeigt erkanntes System, Paketverwaltung und Firewall-Werkzeug.
|
||
- System-Seite erklärt jetzt, warum TLS über Caddy terminiert wird.
|
||
|
||
### Sicherheit
|
||
- Firewall: race-freies Löschen per Regel-Spezifikation (keine Regelnummern),
|
||
Whitelisting gegen Befehls-Injection, firewalld-Lockout-Schutz (Ports vor dem Start
|
||
in der permanenten Konfiguration).
|
||
- fail2ban: kanonischer (inet_pton) Schutz gegen das Sperren des eigenen Zugangs,
|
||
ignoreip wird verbatim erhalten (inkl. Hostnames/Fortsetzungszeilen).
|
||
|
||
## [0.1.1] - 2026-06-13
|
||
|
||
Wartungs-Release: vollständige Code-Auditierung, Entfernung von totem Code und ein behobenes Open-Redirect. Keine funktionalen Änderungen für Nutzer.
|
||
|
||
### Behoben
|
||
- Open-Redirect im Server-Switcher: ein manipulierter `Referer` konnte die
|
||
Weiterleitung nach dem Server-Wechsel auf eine fremde Adresse lenken. Die
|
||
Weiterleitung wird jetzt auf einen **gleich-origin** relativen Pfad reduziert
|
||
(fremde Schemata wie `javascript:`/`data:`, andere Hosts/Ports sowie
|
||
protokoll-relative Tricks `//host` / `/\host` werden abgewiesen).
|
||
- SSH-Zugang: eine Passphrase wurde unabhängig vom Auth-Typ gespeichert; sie wird
|
||
jetzt nur noch bei Schlüssel-Authentifizierung hinterlegt.
|
||
|
||
### Geändert
|
||
- Datei-Manager + Editor verwenden ein eigenes `file`-Symbol statt des
|
||
Audit-Symbols (semantisch korrekt).
|
||
- Code-Hygiene: toten Code entfernt (ungenutzte `FirewallService`-Methoden
|
||
`status/allow/deny`, `Server::auditEvents()`, `SshCredential::scopeActive()`,
|
||
ungenutztes `bell`-Icon), veraltete Doc-Blöcke/Kommentare korrigiert,
|
||
Exceptions sauber importiert, base64-Argument im Sudo-Pfad defensiv gequotet,
|
||
Null-sichere Nutzerabfrage in den Einstellungen. Pint + Codex-Review sauber.
|
||
|
||
## [0.1.0] - 2026-06-13
|
||
|
||
Erste geschnittene Version — die komplette v1-Basis inklusive Server- und Panel-Härtung als ein Release.
|
||
|
||
### Hinzugefügt
|
||
- Dashboard mit Live-Sparklines und poll-getriebenem Dual-Chart (KPIs + Tabelle).
|
||
- Server-Index + Detailseite, Dienste (systemd: Start/Stop/Restart + Journal),
|
||
Datei-Manager (SFTP: Up-/Download, Editor), Audit-Log.
|
||
- SSH-Schicht über phpseclib (exec + SFTP) mit Host-Key-Pinning (TOFU) und
|
||
Multi-Server-Switcher samt Fleet-Kontext.
|
||
- Echte Metriken (CPU/RAM/Disk/Load) inkl. Absolutwerten über Reverb.
|
||
- Authentifizierung + 2FA (TOTP) mit erzwungenem Onboarding.
|
||
- R5-Bestätigungsmodals (wire-elements/modal) für destruktive Aktionen.
|
||
- System-Seite mit Domain & TLS (Caddy Auto-TLS) und Release-Kanal.
|
||
- Echte Versions-Seite, die Version, Build-Hash und dieses Änderungsprotokoll liest.
|
||
- Deploy: `install.sh` + Caddy (Auto-TLS) + Produktions-Härtung.
|
||
- Selbst-gehostete Fonts (kein CDN), Split-Brand Login/2FA.
|
||
|
||
### Geändert
|
||
- Dienst-Steuerung läuft über das hinterlegte Sudo-Passwort (`sudo -S`) statt nur
|
||
passwortlosem sudo — Start/Stop/Restart + Journal funktionieren live.
|
||
- Lazy-Loading der Seiten (Skeletons), normalisierte Buttons + Spinner,
|
||
Detailseiten- und Einstellungs-Redesign, Schlüsselerzeugung.
|
||
- Vollständige Regel-Auditierung (R1–R14), neue Regel R14 (lokale Fonts).
|
||
|
||
### Behoben
|
||
- Datei-Editor: Klick löste einen Alpine-Fehler aus (`@js()` kompiliert nicht in
|
||
Komponenten-Attributen) — auf Index-Lookups umgestellt.
|
||
- Adversarial Review der SSH-Schicht (Escaping, Parser, Host-Key-Pinning).
|
||
- 500 durch `@disabled` auf `<x-btn>`; Modal über dem Backdrop; poll-getriebene
|
||
Metriken (Chart + Gauges) statt fiktiver Fleet-Daten; 2FA-Setup-QR als `<img>`.
|
||
- fail2ban/apt-Härtung schlug fehl, weil der 12s-SSH-Timeout den Paket-Install
|
||
abbrach — langer Timeout für apt-Aktionen.
|
||
|
||
### Sicherheit
|
||
- Server-Härtung aus dem Dashboard (Root-/Passwort-Login, fail2ban, UFW,
|
||
Auto-Updates) mit Befehls-Vorschau, Bestätigung und Aussperr-Schutz.
|
||
- Verwaltung hinterlegter SSH-Zugänge (Name, Sperren, Löschen); der Vault
|
||
verweigert gesperrte Zugänge. Dienst-Steuerung über hinterlegtes Sudo-Passwort.
|
||
- Panel-Selbst-Härtung: Security-Header + umgebungsbewusste CSP, 2FA-Brute-Force-
|
||
Drossel, Session-Cookie-Härtung.
|
||
- Release-/Versionierungsmodell: echte semantische Versionen + Git-Tags, Changelog
|
||
pro Version, Kanäle `stable`/`beta` (kein nutzer-seitiges `dev`).
|
||
|
||
[Unreleased]: https://git.bave.dev/boban/clusev/compare/v0.2.1...HEAD
|
||
[0.2.1]: https://git.bave.dev/boban/clusev/compare/v0.2.0...v0.2.1
|
||
[0.2.0]: https://git.bave.dev/boban/clusev/compare/v0.1.1...v0.2.0
|
||
[0.1.1]: https://git.bave.dev/boban/clusev/compare/v0.1.0...v0.1.1
|
||
[0.1.0]: https://git.bave.dev/boban/clusev/releases/tag/v0.1.0
|