307 lines
15 KiB
Markdown
307 lines
15 KiB
Markdown
# 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 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).
|