# 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: > 1. **`rules.md`** — concise, enforceable coding rules (the "STRICT RULES" section, expanded with examples). > 2. **`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 `.com` required.** 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.md` with 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 in `resources/views/livewire/...`. Never single-file Volt. Keep class and view as separate files. Use `php artisan make:livewire ...` (class). Never `make:volt`. - **R3 — All colors/design tokens live in `resources/css/app.css` under `@theme`.** Tailwind v4 placeholders like `--color-accent`, `--color-surface`, used in markup as `text-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-data` confirm 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`: ```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): services `app` (php-fpm + nginx or FrankenPHP), `vite`, `reverb`, `queue` (worker), `mariadb`, `redis`. Uses `build:`, **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//clusev:`, **no bind-mount**, built assets baked into the image, no Vite. Env via a `.env` file 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.sh` pulls the new image onto the VM. (Bootstrap before CI exists: build on the VM with `docker 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 1. Read this `handoff.md` end to end. 2. 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. 3. 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 references `rules.md`. 4. 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 `@theme` mapping). - `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).