15 KiB
Clusev — Project Handoff
Purpose of this file. This is the single source of truth for the project's rules and decisions. In the next session, generate two files from it:
rules.md— concise, enforceable coding rules (the "STRICT RULES" section, expanded with examples).CLAUDE.md— project context, stack, commands, conventions, folder map (so any agent ramps up fast).Do not lose any rule below. If something is ambiguous, ask the user before coding.
1. Product
Clusev is a self-hosted control panel to administer a fleet of Linux servers from one dashboard. It connects agentless over SSH. The operator sees and steers the whole fleet in one UI — metrics, services, files, audit — security-first (2FA, full audit log).
- Audience: developers, self-hosters, sysadmins. End-beneficiaries are broad.
- Differentiator: multi-server fleet management with a modern, polished UI (vs Cockpit/aaPanel single-host + dated UX).
- Backend reality: Laravel is the control-plane (UI + API + provisioning). It does NOT reimplement SSH/daemons — it orchestrates real servers over SSH (phpseclib for exec + SFTP).
v1 scope (build this first — do NOT boil the ocean)
Foundation (build once): auth + 2FA, audit log, encrypted SSH-credential vault, SSH layer (exec + SFTP via phpseclib), Reverb realtime channel, queue workers, multi-server switcher. Features: (1) Dashboard/live metrics, (2) systemd services (list + start/stop/restart + logs), (3) SFTP file manager. Plus the Server-Details page (identity, resource gauges, specs, volumes, interfaces, security hardening, SSH keys).
Deferred (later versions — keep out of v1)
- Web terminal (xterm.js Alpine island + small ws↔SSH sidecar) — PHP can't hold an interactive PTY.
- Package management (apt/dnf) — write-risk + distro abstraction.
- Firewall / users / cron, one-click app store, optional push-metrics agent.
Monetization & license (decided)
- Multi-server is FREE (it's the differentiator — never paywall it).
- Paid = Team/Enterprise (SSO/LDAP, granular RBAC, audit retention/export, alerting, auto-backups, support) + optional Managed/Hosted control-plane.
- License: AGPL for the core (protects against SaaS rip-offs, stays OSS) + commercial license for Pro modules. Architect open-core from day 1 (clean core, Pro as separate modules/flags).
2. Name & branding
- Project name:
Clusev(all handles/domains free:clusev.*,get…,use…,.sh). - No
.comrequired. GitHub repo + README + screenshots is the homepage for now. No marketing website yet (comes later, with the Pro/Managed tier). - Design origin: the visual system was prototyped under the codename "BASTION" (see §11). Rename the brandmark to Clusev (the accented letter in the wordmark becomes the brand-orange accent).
- UI copy language: German. Terse, operational, factual (console/status-page register).
No emoji. Status = color/dots/pills, never emoji. Technical identifiers stay native
(
nginx.service,systemd,chmod 600,SSH,2FA).
3. Tech stack (hard requirements)
Update 2026-06-11 (scaffold): Laravel 13 is used instead of 12 — Laravel 13 became the current release after this handoff was written; chosen by user decision. The rest of this table stands (Livewire 3 class-based, Tailwind 4, Reverb, …); PHP pinned to 8.3.
| Layer | Choice |
|---|---|
| Framework | Laravel 12 |
| UI/interactivity | Livewire v3 (class-based — NOT Volt) |
| CSS | Tailwind v4 (CSS-first, @theme in app.css — no tailwind.config.js) |
| Build | Vite |
| Modals | wire-elements/modal (composer require wire-elements/modal) |
| Realtime | Laravel Reverb + Echo (live metrics, broadcasts) |
| Queue/cache | Redis |
| DB | MariaDB (default; Postgres acceptable) |
| SSH | phpseclib (exec + SFTP) |
| Charts | JS lib (ApexCharts / Chart.js / uPlot) as an Alpine island in Blade |
| Icons | Lucide, rendered inline as SVG (Blade component) |
| Fonts | Chakra Petch (display) · Space Grotesk (sans) · JetBrains Mono (numbers/paths). Self-host the .woff2 (don't rely on CDN in prod). |
4. STRICT RULES (non-negotiable)
These are the user's explicit, mandatory rules. Expand each into
rules.mdwith a short rationale and a ✅/❌ code example.
-
R1 — Pages are full-page Livewire components, mapped directly as routes. Every page = a class-based Livewire component referenced in
routes/web.php(Route::get('/x', SomeComponent::class)). No controllers returning Blade views for pages. The view belongs to the Livewire component, not to a controller. -
R2 — Class-based Livewire only. Volt is FORBIDDEN. Each component = a PHP class in
app/Livewire/...plus a separate Blade view inresources/views/livewire/.... Never single-file Volt. Keep class and view as separate files. Usephp artisan make:livewire ...(class). Nevermake:volt. -
R3 — All colors/design tokens live in
resources/css/app.cssunder@theme. Tailwind v4 placeholders like--color-accent,--color-surface, used in markup astext-accent,bg-surface,bg-accent/50(opacity modifier),border-line, etc. No hard-coded hex in markup — only theme utilities. -
R4 — Inline styles are FORBIDDEN. The only allowed inline style is a progress bar's width (to show a dynamic percentage, e.g.
style="width: {{ $pct }}%"). Everything else = Tailwind utility classes. -
R5 — Destructive/confirm actions use a Modal, never an Alpine dialog. No Alpine
confirm()/x-dataconfirm popups for delete/destructive actions. Always a wire-elements/modal component (e.g.ConfirmDelete). Open via the package API. -
R6 — Clean, conventional folder structure (see §5). Organized, predictable, no mess.
-
R7 — Fully responsive: mobile + tablet + desktop. Every screen adapts. Sidebar → drawer/off-canvas on mobile; dense tables → stacked cards or horizontal scroll on small screens; KPI grid reflows; touch targets ≥ 44px. Test all 3 breakpoints.
-
R8 — Docker-first, host stays clean. The target server has only Docker installed + a sudo user — nothing else (no PHP/Node/Composer on the host). Everything runs in containers. Dev and prod both via Docker, deployed as a plain Docker Compose stack — no Portainer, no external orchestrator (see §8).
-
R9 — German UI copy, no emoji (see §2).
-
R10 — Reuse the existing design system (tokens + components from the BASTION mockup, orange accent already softened). See §6 and §11.
5. Folder structure
app/
Livewire/ # class-based components (full-page = routes, + nested)
Dashboard.php
Servers/
Index.php Show.php # Show = the Server-Details page
Services/Index.php
Files/Index.php
Audit/Index.php
Auth/{Login.php, TwoFactor.php}
Modals/ # wire-elements/modal components (ConfirmDelete, ServerForm, ...)
Concerns/ # shared traits (e.g. WithFleetContext)
Models/ # Server, AuditEvent, User, ...
Support/
Ssh/ # phpseclib wrappers: SshClient, Sftp, CredentialVault
Services/ # domain services: FleetService, MetricsPoller, Provisioner
Events/ # broadcast events (MetricsTicked, ...)
Jobs/ # queued ops (long installs/upgrades later)
resources/
views/
livewire/ # mirrors app/Livewire (kebab-case)
dashboard.blade.php
servers/{index,show}.blade.php
services/index.blade.php
files/index.blade.php
audit/index.blade.php
modals/...
components/ # Blade UI components: panel, kpi, status-pill, status-dot,
... # badge, sidebar, topbar, server-item, nav-item, icon, ring
layouts/app.blade.php
css/app.css # @import "tailwindcss"; + @theme { tokens } + base layer
js/app.js # Echo/Reverb bootstrap, Alpine islands (charts, xterm later)
routes/web.php # full-page Livewire routes
config/livewire.php # class_namespace=App\Livewire, view_path=views/livewire
docker/ # Dockerfile, php-fpm/nginx config, entrypoint
docker-compose.yml # DEV (build, bind-mount, Vite)
docker-compose.prod.yml # PROD (image from GHCR, no mount) — deployed via plain docker compose
.dockerignore
handoff.md rules.md CLAUDE.md
6. Design system → Tailwind v4 @theme
Visual direction: "Tactical Terminal" — dark graphite, signal-orange brand (softened on selection/active tints), cyan counter-accent, ops status triad. Mono for every number/IP/path.
Port the mockup tokens (in bastion/bastion.css) into resources/css/app.css:
@import "tailwindcss";
@theme {
/* surfaces */
--color-void: #06080A; --color-base: #0A0D10; --color-surface: #0F1318;
--color-raised: #141A20; --color-inset: #0B0E12;
/* brand (use opacity utilities for tints: bg-accent/10 instead of a separate --accent-dim) */
--color-accent: #FF6B2C; --color-accent-bright: #FF8A4D; --color-accent-deep: #E25617;
--color-accent-text: #FF9259;
--color-cyan: #43C1D8; --color-cyan-bright: #74D7E8;
/* status */
--color-online: #35D07F; --color-warning: #E8B931; --color-offline: #FF5247;
/* text ramp → text-ink / text-ink-2 ... */
--color-ink: #E9EEF3; --color-ink-2: #9DAAB6; --color-ink-3: #69757F; --color-ink-4: #495159;
/* hairline borders → border-line ... */
--color-line: rgba(255,255,255,.10); --color-line-soft: rgba(255,255,255,.055);
--color-line-strong: rgba(255,255,255,.17);
/* fonts (self-host the woff2) */
--font-display: "Chakra Petch", sans-serif;
--font-sans: "Space Grotesk", sans-serif;
--font-mono: "JetBrains Mono", monospace;
/* radii → rounded-* */
--radius-xs: 3px; --radius-sm: 6px; --radius-md: 8px; --radius-lg: 10px;
}
Usage in markup: bg-surface, text-ink, text-accent, bg-accent/10, border-line,
font-mono, rounded-lg, text-online. The old --accent-dim/--accent-line become
bg-accent/10 and border-accent/25.
Components to build as Blade components (from the mockup markup, see §11):
x-panel, x-kpi, x-status-pill, x-status-dot, x-badge, x-icon (Lucide), x-ring,
x-server-item, x-nav-item, plus sidebar + topbar partials. Live metrics via Reverb events
pushed into the Livewire Dashboard component (wire:stream or broadcast → JS chart update).
Selection/active orange already softened: ::selection uses bg-accent/25; active nav/server
tints use bg-accent/10 + border-accent/25. Keep them subtle.
7. Responsive requirements (R7 detail)
- Sidebar: fixed 272px on desktop; off-canvas drawer (hamburger in topbar) on mobile/tablet.
- Topbar: on narrow widths collapse the server-meta badges + uptime (already done in mockup CSS).
- KPI grid: 4-up desktop → 2-up tablet → 1-up mobile.
- Tables (services, volumes, interfaces): horizontal scroll OR stack into cards on mobile.
- Server-Details columns: 2-col desktop → 1-col below ~1080px.
- Touch targets ≥ 44px; test mobile (375), tablet (768), desktop (1280+).
8. Infrastructure: Docker + deploy target (no Portainer)
Target server (deploy + realistic dev): Debian 13 VM, 10.10.90.136, only Docker
installed + a sudo user. Nothing else on the host — everything runs in containers, deployed as a
plain Docker Compose stack. No Portainer, no external orchestrator.
Two compose files:
docker-compose.yml(DEV): servicesapp(php-fpm + nginx or FrankenPHP),vite,reverb,queue(worker),mariadb,redis. Usesbuild:, bind-mounts the source, runs Vite dev server. Configure Vite for remote HMR:server.host = '0.0.0.0',hmr.host = '10.10.90.136'.docker-compose.prod.yml(PROD):image: ghcr.io/<user>/clusev:<tag>, no bind-mount, built assets baked into the image, no Vite. Env via a.envfile on the VM.
Deploy workflow (plain Docker Compose — no Portainer):
- SSH to the VM and run:
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d, then migrate:docker compose exec app php artisan migrate --force. - Wrap it in a small
deploy.sh(or a CI job that SSHes in and runs the above). - Image flow: push → CI builds + pushes to GHCR →
deploy.shpulls the new image onto the VM. (Bootstrap before CI exists: build on the VM withdocker compose -f docker-compose.prod.yml build.) - Reverse proxy + TLS (Caddy or Traefik) sits in front — also just a service in the stack.
- Build multi-arch (
docker buildx) if any dev happens on Apple Silicon (arm64); the prod VM is amd64. - Note: managing Docker/containers may later become a feature of Clusev itself — but Clusev's own deployment never depends on any external panel.
9. Dev workflow (build in the VM)
Decision: develop directly on the Debian 13 VM (prod-parity, no arch mismatch, same host as prod). Connect via VS Code Remote-SSH (or CLI). Everything runs in containers — the host only has Docker. Bootstrap Laravel inside a container (host has no PHP/Composer/Node):
# one-time bootstrap (run in a throwaway php/composer container or the app image)
composer create-project laravel/laravel .
composer require livewire/livewire wire-elements/modal phpseclib/phpseclib laravel/reverb
# Tailwind v4 + Vite plugin
npm install -D tailwindcss @tailwindcss/vite
php artisan livewire:publish --config # ensure class-based, view_path set
Then: scaffold layout + app.css @theme, build the Blade UI components, wire the full-page
Livewire routes, hook Reverb for live metrics.
10. Next session — what to produce
- Read this
handoff.mdend to end. - Generate
rules.md: the §4 STRICT RULES, each expanded with a one-line rationale and a ✅correct / ❌forbidden code snippet (especially R1–R5). Keep it short and enforceable. - Generate
CLAUDE.md: product summary (§1), stack (§3), folder map (§5), key commands (§9), conventions (German UI, tokens, responsive), and a "before you code" checklist that referencesrules.md. - Then scaffold per §8/§9 (Docker-first, in the VM) and start v1 (§1 scope).
11. Reference assets (the mockup)
The working visual prototype lives in ../bastion/:
bastion.css— all design tokens + component CSS (source for the@thememapping).bastion-ui.js— Lucide icon paths, mock data (servers/services/files/audit), metric helpers, sidebar/topbar markup. Reuse the icon set + mock data shapes.index.html— Dashboard screen (fleet, KPIs, live chart, systemd table, files, audit).details.html— Server-Details screen (hero, resource rings, specs, volumes, interfaces, security, keys).
These are the pixel reference. Recreate them as Blade components + Livewire pages (don't copy the prototype's internal JS structure where Livewire/Reverb fits better). Live data is mock for now; later it comes from the SSH layer.
12. Open questions / TODO for the user
- Final confirm name Clusev + grab GitHub org +
clusev.sh(or chosen TLD). - DB choice confirm (MariaDB assumed).
- GHCR namespace / registry for prod images.
- Self-host the 3 webfonts (download woff2 into the repo).