Go to file
boban dfe141b406 fix(ui): stop iOS zoom-on-focus by forcing 16px inputs on touch devices
iOS Safari zooms the viewport when a focused form control's font-size is < 16px.
The dense design uses text-xs/sm/[11px] on inputs, which triggered the zoom on
every input across the app. Add one unlayered media query forcing 16px on
input/textarea/select for touch-primary devices (@media (pointer: coarse)) —
covering phones AND tablets of any size (incl. 1024/1366px iPad Pro that a
width cap would miss) while keeping the small desktop sizing for mouse pointers.
Unlayered so it wins over Tailwind's utility classes.

Verified via touch emulation: inputs render 16px on phone/iPad/iPad-Pro widths
(matchMedia(pointer:coarse)=true) and stay 14px on mouse desktops at any width.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:24:05 +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 chore(release): v0.4.3 2026-06-14 13:07:09 +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 chore(release): v0.4.3 2026-06-14 13:07:09 +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(ui): stop iOS zoom-on-focus by forcing 16px inputs on touch devices 2026-06-14 14:24:05 +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 chore(release): v0.4.3 2026-06-14 13:07:09 +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.