clusev/handoff.md

307 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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/<user>/clusev:<tag>`,
**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 R1R5). 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).