Replace the page mock data with real reads from the target host via the phpseclib SSH layer. New FleetService parses raw command output into the exact shapes the Livewire pages already consume; all parsing forces LC_ALL=C and reads /proc to stay locale-independent. - app/Services/FleetService.php: metrics (cpu via two /proc/stat samples, mem via /proc/meminfo, disk via df), full snapshot (identity/specs incl. virt+disk_gb, volumes, interfaces from ip+/proc/net/dev, sshd/fail2ban/ufw hardening, authorized keys), systemd units + journal, and an ls-based directory listing. One compound command per read; connect/parse failures bubble up. - clusev:poll-metrics command replaces the mock emitter in the dev supervisor: polls every credentialed server, persists cpu/mem/disk/status, broadcasts MetricsTicked(server) — unreachable boxes flagged offline, loop never dies. - Pages wired with graceful failure (offline state, never a 500): Dashboard (live cached metrics + notable units), Services (real units+journal), Files (real listing + dir navigation via open/go/up), Server-Details (live snapshot persisted onto the row + offline banner). - WithFleetContext prefers a credentialed, non-offline server as the default. - dualChart filters ticks by server name so the chart tracks the active host. Verified live against a real Debian 13 box: metrics/services(91)/journal(25)/ files(navigable)/snapshot(5 ifaces, real hardening, real key) all parse correctly. Credentials are stored encrypted in the vault — never in source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| app | ||
| bootstrap | ||
| config | ||
| database | ||
| docker | ||
| docs | ||
| public | ||
| resources | ||
| routes | ||
| storage | ||
| tests | ||
| .dockerignore | ||
| .editorconfig | ||
| .env.example | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| Dockerfile | ||
| README.md | ||
| artisan | ||
| composer.json | ||
| composer.lock | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| handoff.md | ||
| install.sh | ||
| kickoff-prompt.md | ||
| package-lock.json | ||
| package.json | ||
| phpunit.xml | ||
| rules.md | ||
| vite.config.js | ||
README.md
Clusev
Self-hosted control panel to administer a fleet of Linux servers from one dashboard, agentless over SSH. Clusev is the control-plane (UI + orchestration); it talks to real servers with phpseclib (exec + SFTP) — it never reimplements daemons. Security-first: 2FA, encrypted SSH-credential vault, and a full audit log. Multi-server is free and never paywalled.
Status: v1 — dashboard/live metrics, systemd services, SFTP file manager, server details, auth + 2FA. UI copy is German; meta-docs are English.
Stack: Laravel 13 · Livewire 3 · Tailwind v4 · Reverb (realtime) · Redis · MariaDB · phpseclib3.
Everything runs in Docker. See CLAUDE.md for architecture and rules.md for the hard conventions.
Development
The host needs only Docker (no PHP/Composer/Node). The dev app container runs php-fpm + nginx +
Vite via supervisor; reverb, queue, mariadb, redis are their own services.
cp .env.example .env # then set DB_PASSWORD / DB_ROOT_PASSWORD
docker compose up -d --build # app on :80, Vite HMR on :5173
docker compose run --rm --no-deps -u "${HOST_UID:-1002}:${HOST_GID:-1002}" app php artisan key:generate
docker compose exec app php artisan migrate --seed
Run any tooling inside the container, e.g.
docker compose exec app php artisan make:livewire Servers/Show (class-based — never Volt).
Production install
One host, only Docker + a sudo user. Caddy is the single host-exposed service: auto-TLS when a
domain is set, plain HTTP on the bare IP otherwise. install.sh is idempotent — it generates
secrets once (never regenerates), brings up the prod stack, migrates, and creates the first admin
with a one-time random password printed only on the terminal.
git clone https://git.bave.dev/boban/clusev.git && cd clusev
./install.sh
Prompts (non-interactive: set CLUSEV_DOMAIN / CLUSEV_ADMIN_EMAIL in the environment):
Domain (empty = access by IP over HTTP): clusev.example.com
Admin e-mail (login + Let's Encrypt): admin@example.com
The closing banner shows the URL + the one-time admin password. On first login Clusev forces a password change and 2FA enrolment before the panel unlocks.
One knob: APP_DOMAIN
APP_DOMAIN |
proxy | URL | Reverb |
|---|---|---|---|
| (empty) | Caddy serves plain HTTP on APP_PORT |
http://<ip> |
ws://<ip>/app/* |
clusev.example.com |
Caddy gets a Let's Encrypt cert, forces HTTPS | https://… |
wss://…/app/* |
SITE_ADDRESS, APP_URL, REVERB_* are derived from it by the installer — nothing hardcoded.
Bare-IP mode serves 2FA/audit over cleartext HTTP; the installer warns loudly. Let's Encrypt needs
publicly reachable 80/443 — a private (RFC1918) target needs a DNS-01 Caddy build instead.
Manual deploy (for operators who don't curl | bash)
docker compose -f docker-compose.prod.yml build
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.prod.yml exec -u app app php artisan migrate --force
docker compose -f docker-compose.prod.yml exec -u app app php artisan clusev:install --email=admin@example.com
In-dashboard updates (signed intent file + host-side updater, digest-pinned, cosign-verified, 2FA-gated, with backup + rollback) are designed in
docs/install-update-design.mdand land in v1.x — they are intentionally not shipped yet.
License
AGPL core + commercial Pro modules (open-core). Multi-server fleet management is always free.