Go to file
boban 6d9dcb9529 feat(versions): show the available release's changelog before updating (v0.9.49)
The Versions page only rendered the INSTALLED CHANGELOG.md, so the operator
couldn't see what an available update contained until after applying it. Now,
when behind a published release, a "What's new in vX.Y.Z" panel shows the
changelog of the available version(s) — fetched from the remote CHANGELOG.md at
the latest tag (Gitea raw API, anonymous/read-only, briefly cached), parsed with
the shared changelog parser and filtered to versions newer than installed.

Degrades gracefully: a failed fetch yields an empty preview and never blocks the
update button. Refactored releases() to a reusable parseChangelog().

Tests: preview lists only newer versions + renders their entries; no preview
when current. 362 pass, Pint clean, /versions loads 200 with no console errors,
versions lang parity 51/51.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:48:37 +02:00
app feat(versions): show the available release's changelog before updating (v0.9.49) 2026-06-21 23:48:37 +02:00
bootstrap feat(auth): guests-only BlockBannedIp middleware + 403 page 2026-06-20 17:45:16 +02:00
config feat(versions): show the available release's changelog before updating (v0.9.49) 2026-06-21 23:48:37 +02:00
database feat(wg): wg_traffic_samples table + model 2026-06-20 23:59:11 +02:00
docker feat(wg): configurable DNS + Server/Peers tabs + faster offline (v0.9.45) 2026-06-21 22:48:46 +02:00
docs docs: enterprise README for Gitea — branded hero + architecture diagram 2026-06-21 16:52:00 +02:00
lang feat(versions): show the available release's changelog before updating (v0.9.49) 2026-06-21 23:48:37 +02:00
public feat(branding): favicons + PWA manifest, and custom branded error pages 2026-06-14 15:27:49 +02:00
resources feat(versions): show the available release's changelog before updating (v0.9.49) 2026-06-21 23:48:37 +02:00
routes feat(wg): clusev:wg-sample command (sample peers + prune) + schedule 2026-06-21 00:00:06 +02:00
storage feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
tests feat(versions): show the available release's changelog before updating (v0.9.49) 2026-06-21 23:48:37 +02:00
.dockerignore feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
.editorconfig feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
.env.example feat(versions): update completion feedback, instant availability, local time 2026-06-19 19:08:54 +02:00
.gitattributes feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
.gitignore feat(domain): change the panel domain from the dashboard (v0.4.0) 2026-06-14 01:38:16 +02:00
CHANGELOG.md feat(versions): show the available release's changelog before updating (v0.9.49) 2026-06-21 23:48:37 +02:00
CLAUDE.md fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +02:00
Dockerfile feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
README.md docs: enterprise README for Gitea — branded hero + architecture diagram 2026-06-21 16:52:00 +02:00
artisan feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
composer.json feat(webauthn): install web-auth/webauthn-lib + credential storage 2026-06-14 18:18:05 +02:00
composer.lock feat(webauthn): install web-auth/webauthn-lib + credential storage 2026-06-14 18:18:05 +02:00
docker-compose.prod.yml feat(ops): add a scheduler service (schedule:work) — runs wg-sample + prune-audit 2026-06-21 00:05:14 +02:00
docker-compose.yml feat(ops): add a scheduler service (schedule:work) — runs wg-sample + prune-audit 2026-06-21 00:05:14 +02:00
handoff.md feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
install.sh feat(wg): clusev-wg-request watcher (path+service) + install wiring 2026-06-21 00:30:12 +02:00
kickoff-prompt.md chore: project bootstrap — rules.md, CLAUDE.md, .gitignore 2026-06-11 23:12:58 +02:00
package-lock.json feat: live metrics over Reverb (mock emitter) 2026-06-12 01:23:10 +02:00
package.json feat: live metrics over Reverb (mock emitter) 2026-06-12 01:23:10 +02:00
phpunit.xml feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
rules.md fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +02:00
update.sh feat(update): real phase progress feed + deep-linkable changelog series/page 2026-06-20 22:08:41 +02:00
vite.config.js feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00

README.md

Clusev — self-hosted control plane for a fleet of Linux servers

One dashboard for your whole fleet. Agentless over SSH. Self-hosted, security-first, open core.

License Version Laravel Livewire Docker Self-hosted

Features · Architecture · Quick start · Domain & TLS · Security · License


What is Clusev

Clusev is the control plane for a fleet of Linux servers — a modern, security-first web UI that administers your machines by talking to them directly over SSH (exec + SFTP via phpseclib). It installs nothing on the target servers and never reimplements their daemons; it orchestrates the real ones.

Multi-server management is free and never paywalled. Optional Pro modules (SSO/LDAP, RBAC, audit export, alerting) are separate — the core stays open.

Agentless Security-first Self-hosted
Pure SSH + SFTP. No daemon, no package, no push-agent on your servers. 2FA, encrypted credential vault, tamper-evident audit log, per-device sessions. One Docker stack on one VM. Your servers, your data, your keys.

Features

Capability What you get
Dashboard & live metrics CPU, memory, load and disk per server, streamed in real time over WebSockets.
systemd services List, start / stop / restart, and tail the live journal — per service, per server.
SFTP file manager Browse the remote filesystem, edit text files in place, preview images.
Server details & hardening Resource gauges, volumes, interfaces and SSH keys; UFW / firewalld rules and fail2ban status with one-click controls; a guided "generate an SSH key and disable password login, safely" flow.
Security Pluggable 2FA (TOTP and/or hardware keys, or off), one-time backup codes, an encrypted SSH-credential vault, and a complete tamper-evident audit log.
Multiple administrators Add further admin accounts — every action is attributed in the audit log; view and revoke active sessions per device, per user, or globally.
Domain, TLS & e-mail Run on a bare IP over HTTP, set a domain for automatic Let's Encrypt HTTPS, or sit behind your own reverse proxy. In-panel SMTP for password-reset mail.
WireGuard (built-in) Stand up a WireGuard tunnel from the dashboard, manage peers and live status, and optionally gate the panel — and even SSH — to the tunnel only.

The interface is German by default (English available). Status is shown the operational way — colour, dots and pills — never emoji.


Architecture

Operator → Clusev control plane → your fleet

Laravel is the control plane, not a re-implementation of your servers. The browser talks to it over HTTPS and a Reverb WebSocket for live telemetry; it reaches each server over SSH exec + SFTP using credentials sealed in an encrypted vault that never leaves the control plane. Everything runs as one Docker stack — app (php-fpm + nginx + Vite), reverb, queue, scheduler, mariadb, redis — on a single VM.

Layer Technology
Framework Laravel 13
UI / interactivity Livewire 3 (class-based)
Styling Tailwind v4 (CSS-first, @theme)
Realtime Laravel Reverb + Echo
Queue / cache Redis
Database MariaDB
SSH transport phpseclib (exec + SFTP)
Reverse proxy / TLS Caddy (automatic Let's Encrypt)
Packaging Docker + Docker Compose

Quick start

sudo apt-get update && sudo apt-get install -y git   # minimal images often lack git
git clone https://git.bave.dev/boban/clusev.git
cd clusev
sudo ./install.sh

The installer is idempotent (safe to re-run) and, in one pass:

  1. Installs Docker (Debian/Ubuntu, from Docker's official repository) if it isn't already present.
  2. Creates a dedicated clusev system user — in the docker group, owning and running the stack — with a random password.
  3. Asks for a domain (leave empty for IP access) and an admin e-mail (default admin@clusev.local), or reads CLUSEV_DOMAIN / CLUSEV_ADMIN_EMAIL from the environment for an unattended install. With a domain it checks whether DNS already points here: if so a certificate is obtained automatically; if not, it warns you and lets you take the domain anyway (HTTP until DNS is correct) or continue on the IP.
  4. Generates all secrets (once — never regenerated on re-run), builds the image, starts the stack, runs the migrations, and creates the first administrator with the default password clusev (you are forced to change it on first login).
  5. Installs a themed host login banner (MOTD) showing the dashboard address and live stack status.

When it finishes, the terminal prints a single summary: the dashboard URL, the admin login (your e-mail — admin@clusev.local if you left it empty — plus the default password clusev), and the clusev host user + its random password (shown only once — note it down).

First login

Log in with your admin e-mail and the password clusev — the panel then forces you to set a new password immediately. Do this right away: clusev is a known default, so the account is only safe once changed. You can change the login e-mail later under Settings → Profile. 2FA is optional but recommended — enable TOTP and/or a security key from Settings → Security whenever you like.


Requirements

  • A Debian or Ubuntu server (a small VM is enough) with root access.
  • A public IP. Optionally a domain whose DNS points at the server (for automatic HTTPS).
  • Only git to fetch the repo — the installer sets up Docker and everything else.

Access, domain & TLS

  • Bare IP (no domain): served over plain HTTP at http://<server-ip>. This address always stays reachable as a recovery path, even after a domain is configured.
  • With a domain: set it during install or later under System → Domain & TLS. The panel obtains and renews a Let's Encrypt certificate automatically and serves HTTPS — just point DNS at the server. (Let's Encrypt needs publicly reachable ports 80/443.)
  • Behind your own reverse proxy (one that already terminates TLS): switch TLS-Terminierung to Externer Reverse-Proxy in System → Domain & TLS. The panel then serves HTTP only and trusts the proxy's forwarded scheme — set TRUSTED_PROXY_CIDR to the proxy's address and firewall the HTTP port so only the proxy can reach it.

Domain/TLS changes apply on a stack restart — use the "Jetzt neu starten" button in System (no terminal needed; a small, scoped host service performs the restart).


Updating

cd clusev
sudo ./update.sh         # pulls the latest code, then rebuilds, restarts and migrates

update.sh fast-forwards the repo (it never discards local changes), re-runs the idempotent installer non-interactively, and updates itself if the script changed. Secrets and the configured domain / e-mail are preserved. (The older two-step git pull && sudo ./install.sh still works.)


Security & recovery

The forgot-password screen offers self-service recovery: an e-mail reset link (valid 15 minutes) when SMTP is configured, or an inline 2FA-proof reset (e-mail + TOTP or a backup code + new password) as a fallback.

Completely locked out (lost password and 2FA, no SMTP)? Recover from the host:

cd clusev
docker compose -f docker-compose.prod.yml exec app php artisan clusev:reset-admin

This clears the second factor so you can set a new password on the next login. The bare-IP http://<server-ip> address is also always available if a domain becomes unreachable. (This command is documented in-panel under Settings → Security and is deliberately not shown on the public forgot-password screen.)


License

Open core, AGPL-3.0 — multi-server fleet management is always free; optional Pro modules (SSO/LDAP, RBAC, audit export, alerting) are separate.

Project home — git.bave.dev/boban/clusev