Go to file
boban 221e49f24a fix(hardening): detect fail2ban/ufw as installed via binary or active service
The hardening checklist read "installed" from the package manager alone
(dpkg `^ii` / rpm `-q`), so it disagreed with the live fail2ban/firewall
panels, which use `command -v` + `systemctl is-active`. On a host where the
component is genuinely present but not recorded as an apt `^ii` package —
installed from source, or left in dpkg `rc` (config-only) state — the
checklist showed `nicht installiert` while the panel reported it active with
real bans (the reported contradiction).

The installed probe now ORs three conditions: the package-manager test, the
client binary on PATH, and the service already running. Since a component can
never be active without being installed, this only removes FALSE negatives —
it can never produce a false positive. Detection is now consistent with
Fail2banService::status and FirewallTool. Applies to both fail2ban and the
firewall (ufw/firewalld).

Adds HardeningServiceTest covering the Debian/apt source-install fallback:
the emitted probe gains `command -v fail2ban-client` and `dpkg -l ufw`, and an
active component reads as featureOn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:22:28 +02:00
app fix(hardening): detect fail2ban/ufw as installed via binary or active service 2026-06-14 09:22:28 +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: session handoff (state, open fail2ban/UFW detection bug, work recipes) 2026-06-14 08:57:04 +02:00
lang feat(domain): change the panel domain from the dashboard (v0.4.0) 2026-06-14 01:38:16 +02:00
public feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb 2026-06-12 00:31:50 +02:00
resources fix(blade): garbled header on every signed-in page + private metrics channel (v0.4.1) 2026-06-14 08:34:07 +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(hardening): detect fail2ban/ufw as installed via binary or active service 2026-06-14 09:22:28 +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.