Go to file
boban 37efa64b9d fix(ssh): require an exec marker in the connection probe
Auth success alone does not prove the server can run commands (a server that
authenticates but rejects exec channels would be accepted, yet every fleet
operation runs commands). The probe now echoes a unique marker and requires it
in the output — proving exec works, while still tolerating servers that omit
the optional exit-status packet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:46:59 +02:00
app fix(ssh): require an exec marker in the connection probe 2026-06-14 10:46:59 +02:00
bootstrap fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +02:00
config fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +02:00
database feat(i18n): bilingual foundation (DE/EN) + localize auth/dashboard/servers/fleet/account 2026-06-13 22:36:16 +02:00
docker feat(domain): change the panel domain from the dashboard (v0.4.0) 2026-06-14 01:38:16 +02:00
docs docs(plan): server-details UX + hardening polish implementation plan 2026-06-14 10:17:28 +02:00
lang fix(ssh): require an exec marker in the connection probe 2026-06-14 10:46:59 +02:00
public feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
resources fix(servers): keep read-error panels visible + pending header support 2026-06-14 10:41:04 +02:00
routes fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +02:00
storage feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
tests fix(servers): keep read-error panels visible + pending header support 2026-06-14 10:41:04 +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 fix(tls/ui): fully automatic TLS (status-only) + uniform hardening buttons 2026-06-13 21:17:26 +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 fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +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 feat(deploy): install.sh + Caddy auto-TLS + prod hardening 2026-06-12 15:17:32 +02:00
artisan feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
composer.json feat: auth + 2FA (login wall + TOTP + forced onboarding) 2026-06-12 13:57:02 +02:00
composer.lock feat: auth + 2FA (login wall + TOTP + forced onboarding) 2026-06-12 13:57:02 +02:00
docker-compose.prod.yml feat(domain): change the panel domain from the dashboard (v0.4.0) 2026-06-14 01:38:16 +02:00
docker-compose.yml feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +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(security): dashboard hardening, credential mgmt, system domain/TLS + channel, self-hardening 2026-06-13 02:25:23 +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
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 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.md and 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.