# 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).
