clusev/handoff.md

15 KiB
Raw Blame History

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:

@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 GHCRdeploy.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).