From 3e5a1ff81c30ebff716c26dfe64986363c49c5fc Mon Sep 17 00:00:00 2001 From: boban Date: Thu, 11 Jun 2026 23:12:58 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20project=20bootstrap=20=E2=80=94=20rule?= =?UTF-8?q?s.md,=20CLAUDE.md,=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate the two governing docs from handoff.md: - rules.md: STRICT RULES R1-R10, each with rationale + correct/forbidden example - CLAUDE.md: product, stack, folder map, commands, conventions, before-you-code checklist Add a $HOME-rooted .gitignore that blocks all secrets and home-dir dotfiles (explicit-add-only workflow). Track handoff.md + kickoff-prompt.md as context. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 64 ++++++++++ CLAUDE.md | 236 +++++++++++++++++++++++++++++++++++ handoff.md | 302 +++++++++++++++++++++++++++++++++++++++++++++ kickoff-prompt.md | 45 +++++++ rules.md | 306 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 953 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 handoff.md create mode 100644 kickoff-prompt.md create mode 100644 rules.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b21ddfb --- /dev/null +++ b/.gitignore @@ -0,0 +1,64 @@ +# ───────────────────────────────────────────────────────────────────────────── +# Clusev .gitignore +# This repo is ROOTED IN THE DEV USER'S $HOME. We therefore ignore all home-dir +# dotfiles/tooling and ALL secrets by default. +# HARD RULE: never `git add -A` / `git add .` here — stage project paths +# explicitly. See rules.md → "Secret hygiene". +# ───────────────────────────────────────────────────────────────────────────── + +# ── Secrets (NEVER commit) ─────────────────────────────────────────────────── +.env +.env.* +!.env.example +.env.gitea +*.pem +*.key +id_rsa* +id_ed25519* +.git-credentials +.netrc +auth.json + +# ── Home-dir / tooling noise (repo is rooted in $HOME) ─────────────────────── +.bash_logout +.bash_history +.bashrc +.profile +.wget-hsts +.cache/ +.npm/ +.dotnet/ +.claude/ +.claude.json +.vscode-server/ +.config/ +.local/ +.ssh/ +.gnupg/ +.docker/ +.composer/ + +# ── Laravel ────────────────────────────────────────────────────────────────── +/vendor +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/bootstrap/ssr +/.phpunit.cache +.phpunit.result.cache +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log + +# ── Editors / OS ───────────────────────────────────────────────────────────── +/.idea +/.vscode +/.fleet +/.nova +/.zed +.DS_Store +Thumbs.db diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1f2cb49 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,236 @@ +# CLAUDE.md — Clusev + +Project context for any agent/developer. **Read `rules.md` first — those rules are +non-negotiable.** This file is the fast ramp-up: what we're building, the stack, where things +live, how to run them, and the conventions. Source of truth for decisions: `handoff.md`. + +--- + +## 1. Product + +**Clusev** — a self-hosted control panel to administer a **fleet of Linux servers from one +dashboard**, connecting **agentless over SSH**. The operator sees and steers the whole fleet +(metrics, services, files, audit) in one security-first UI (2FA, full audit log). + +- **Backend reality:** Laravel is the **control-plane** (UI + API + provisioning). It does **not** + reimplement SSH/daemons — it orchestrates real servers over SSH via **phpseclib** (exec + SFTP). +- **Audience:** developers, self-hosters, sysadmins. +- **Differentiator:** multi-server fleet management with a modern, polished UI. **Multi-server is + free and never paywalled.** Paid = Team/Enterprise (SSO/LDAP, RBAC, audit export, alerting, + backups) + optional managed control-plane. **License:** AGPL core + commercial Pro modules — + architect open-core from day one (clean core, Pro as separate modules/flags). + +### v1 scope (build this first — do NOT boil the ocean) +- **Foundation (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 (keep OUT of v1) +- **Web terminal** (xterm.js + ws↔SSH sidecar — PHP can't hold an interactive PTY). +- **Package management** (apt/dnf), firewall / users / cron, app store, push-metrics agent. + +--- + +## 2. Tech stack (hard requirements) + +| 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** | +| Realtime | **Laravel Reverb** + Echo (live metrics, broadcasts) | +| Queue / cache | **Redis** | +| DB | **MariaDB** *(confirmed)* | +| SSH | **phpseclib** (exec + SFTP) | +| Charts | JS lib (ApexCharts / Chart.js / uPlot) as an **Alpine island** in Blade | +| Icons | **Lucide**, inline SVG via a Blade `x-icon` component | +| Fonts | **Chakra Petch** (display) · **Space Grotesk** (sans) · **JetBrains Mono** (numbers/paths) — self-host `.woff2`, no CDN in prod | + +**Composer packages:** `livewire/livewire wire-elements/modal phpseclib/phpseclib laravel/reverb`. +**NPM (Tailwind v4):** `tailwindcss @tailwindcss/vite` (+ Echo + a charts lib). + +--- + +## 3. Architecture notes + +- **Pages = full-page class-based Livewire components** mapped directly in `routes/web.php` + (R1/R2). No page controllers. +- **SSH layer** (`app/Support/Ssh/`): `SshClient` (exec), `Sftp`, `CredentialVault` + (encrypted credentials). Domain services in `app/Services/` (`FleetService`, `MetricsPoller`, + `Provisioner`) orchestrate them; queued long ops go in `app/Jobs/`. +- **Realtime:** `MetricsPoller` → broadcast events (`app/Events/`, e.g. `MetricsTicked`) over + **Reverb**; the Dashboard Livewire component receives them (Echo / `wire:stream`) and updates + the chart island. **Mock data first, real SSH after.** +- **Open-core:** keep the core clean; Pro features behind separate modules/flags from day one. + +--- + +## 4. Folder map (§5 — follow exactly, R6) + +``` +app/ + Livewire/ # class-based components (full-page = routes, + nested) + Dashboard.php + Servers/{Index.php, Show.php} # Show = 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/ # FleetService, MetricsPoller, Provisioner + Events/ # broadcast events (MetricsTicked, …) + Jobs/ # queued ops +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: 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 bits, php-fpm/nginx config, entrypoint +docker-compose.yml # DEV (build, bind-mount, Vite) +docker-compose.prod.yml # PROD (image from GHCR, no mount) +.dockerignore +handoff.md rules.md CLAUDE.md +``` + +--- + +## 5. Design system → Tailwind v4 `@theme` + +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**. All tokens live in `resources/css/app.css` (R3). Token groups: + +- **Surfaces:** `void, base, surface, raised, inset` → `bg-surface`, `bg-raised`, … +- **Brand:** `accent, accent-bright, accent-deep, accent-text` → `text-accent`, `bg-accent/10` + (use **opacity utilities** for tints, e.g. `bg-accent/10`, `border-accent/25` — no `--accent-dim`). +- **Counter-accent:** `cyan, cyan-bright`. +- **Status:** `online (#35D07F)`, `warning (#E8B931)`, `offline (#FF5247)` → `text-online`, … +- **Text ramp:** `ink, ink-2, ink-3, ink-4` → `text-ink`, `text-ink-2`, … +- **Borders (hairline):** `line, line-soft, line-strong` → `border-line`, … +- **Fonts:** `--font-display` (Chakra Petch), `--font-sans` (Space Grotesk), `--font-mono` + (JetBrains Mono) → `font-display`, `font-mono`. +- **Radii:** `--radius-xs/sm/md/lg` → `rounded-sm`, `rounded-lg`. + +> Full token values are in `handoff.md` §6 — port them verbatim into `app.css`. Selection +> (`::selection`) and active nav/server tints use `bg-accent/25` and `bg-accent/10` + `border-accent/25` +> (keep subtle). **`../bastion` mockup is NOT present on this machine** → rebuild from §6 tokens + +> the §11 component list; pull Lucide SVG paths directly from Lucide. + +**Blade components to build:** `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. + +--- + +## 6. Conventions + +- **UI copy: German**, terse/operational, **no emoji** (status = color/dots/pills). Native + technical tokens stay as-is (`nginx.service`, `chmod 600`, `SSH`, `2FA`). (R9) +- **Colors:** only `@theme` token utilities in markup — never raw hex/rgb. (R3) +- **Inline styles:** forbidden except a progress bar's `width`. (R4) +- **Naming:** Livewire class `App\Livewire\Servers\Show` ↔ view `livewire.servers.show` + (kebab, mirrored path). Components in `app/Livewire/…`, views in `resources/views/livewire/…`. +- **Pages:** full-page Livewire components as routes — no page controllers. (R1/R2) +- **Destructive actions:** wire-elements/modal only — no `confirm()`/Alpine popups. (R5) +- **Responsive:** every screen at **375 / 768 / 1280+**; sidebar → drawer on small; KPI grid + 4→2→1; tables scroll/stack; touch targets ≥ 44px. (R7) +- **Docs language:** these meta-docs are in English; **UI strings are German**. + +--- + +## 7. Commands (everything in containers — R8) + +> Dev stack services: `app` (php-fpm + nginx/FrankenPHP), `vite`, `reverb`, `queue`, +> `mariadb`, `redis`. Vite HMR is configured for remote access (`host=0.0.0.0`, +> `hmr.host=10.10.90.136`). + +```bash +# ── Stack lifecycle (DEV) ──────────────────────────────────────────────── +docker compose up -d # start the dev stack +docker compose logs -f app # tail app logs +docker compose down # stop + +# ── Run tooling INSIDE the container ───────────────────────────────────── +docker compose exec app php artisan migrate +docker compose exec app php artisan make:livewire Servers/Show # class + view (never make:volt) +docker compose exec app composer require some/package +docker compose exec app php artisan tinker + +# ── One-time bootstrap (host has no PHP/Composer/Node) ─────────────────── +# run in a throwaway container or the app image: +# composer create-project laravel/laravel . +# composer require livewire/livewire wire-elements/modal phpseclib/phpseclib laravel/reverb +# npm install -D tailwindcss @tailwindcss/vite +# php artisan livewire:publish --config # class-based, view_path set + +# ── Realtime / queue (run as their own services; manual run if needed) ─── +docker compose exec app php artisan reverb:start +docker compose exec app php artisan queue:work + +# ── Prod deploy (plain Docker Compose — no Portainer) ──────────────────── +docker compose -f docker-compose.prod.yml pull +docker compose -f docker-compose.prod.yml up -d +docker compose -f docker-compose.prod.yml exec app php artisan migrate --force +``` + +--- + +## 8. Infrastructure & deploy (§8) + +- **Target VM:** Debian 13, **`10.10.90.136`**, **only Docker** + a sudo user. Everything in + containers. **No Portainer, no external orchestrator.** Dev happens on this same VM (prod-parity). +- **`docker-compose.yml` (DEV):** `build:`, **bind-mounts** source, runs Vite dev server with + remote HMR (`server.host=0.0.0.0`, `hmr.host=10.10.90.136`). +- **`docker-compose.prod.yml` (PROD):** `image: ghcr.io/OWNER/clusev:` *(GHCR namespace is a + **placeholder `OWNER`** — set via `.env` before the first prod push)*, **no bind-mount**, assets + baked into the image, no Vite. Reverse proxy + TLS (Caddy/Traefik) is just another service. +- **Deploy:** wrap pull+up+migrate in `deploy.sh` (or a CI job that SSHes in). Image flow: + push → CI builds + pushes to **GHCR** → `deploy.sh` pulls onto the VM. Bootstrap before CI: + build on the VM. Use `docker buildx` (multi-arch) only if any dev happens on arm64; prod is amd64. + +--- + +## 9. Repo & workflow + +- **Project root = `/home/nexxo`** (the dev user's `$HOME`; matches §5 placement of docs). +- **Remote:** Gitea `git.bave.dev/boban/clusev.git`. Credentials live in **`.env.gitea`** + (untracked) — the `GIT_ACCESS_TOKEN` must later move into the app `.env`. +- **Work on a feature branch, not `main`.** Commit in sensible steps. +- **Secret hygiene (critical — repo is in `$HOME`):** never `git add -A`/`.`; stage explicit + project paths; `.env*`, keys, and home-dir dotfiles are gitignored. See `rules.md` → Appendix. +- **Verify before "done":** container builds, page renders, no console errors. Test the 3 breakpoints. + +--- + +## 10. Before you code — checklist + +1. **Read `rules.md`.** R1–R10 are non-negotiable; on any conflict, **stop and ask**. +2. New page? → full-page **class-based** Livewire component + route in `web.php` (R1/R2). Never Volt. +3. Need a color/spacing? → an **`@theme` token utility** (R3). No raw hex, no inline style except + progress `width` (R4). +4. Destructive/confirm? → **wire-elements/modal** (R5). +5. Place files per the **§4 folder map** (R6). Class ↔ mirrored kebab view. +6. Build **responsive**: 375 / 768 / 1280; ≥44px touch targets (R7). +7. Run all tooling **inside the container** (R8). +8. UI copy **German, no emoji**; status via color/dots/pills (R9). +9. **Reuse** the token set + Blade component kit (R10). +10. **Verify** (build, render, no console errors, 3 breakpoints), then `git status` for stray + secrets, commit on the feature branch. diff --git a/handoff.md b/handoff.md new file mode 100644 index 0000000..a398307 --- /dev/null +++ b/handoff.md @@ -0,0 +1,302 @@ +# 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) + +| 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). diff --git a/kickoff-prompt.md b/kickoff-prompt.md new file mode 100644 index 0000000..fc21428 --- /dev/null +++ b/kickoff-prompt.md @@ -0,0 +1,45 @@ +Lies zuerst `handoff.md` in diesem Verzeichnis KOMPLETT (oben bis unten). Das ist die +verbindliche Quelle für Produkt, Stack, Regeln, Ordnerstruktur und Deployment des Projekts +**Clusev** (self-hosted Multi-Server-Control-Panel, agentless über SSH). + +Kontext dieser Maschine: Debian 13, `10.10.90.136`, **nur Docker + ein sudo-User** installiert — +sonst nichts (kein PHP/Node/Composer auf dem Host). Alles läuft in Containern. Deployment = plain +Docker Compose, kein Portainer. + +Arbeite in dieser Reihenfolge und stoppe an den markierten Freigabe-Punkten: + +1. **`handoff.md` lesen.** Bestätige mir in 5–8 Stichpunkten, dass du Produkt, Stack und die + STRICT RULES (R1–R10, §4) verstanden hast. Wenn die Mockup-Referenz `../bastion` existiert, + sieh sie dir an (Pixel-Vorlage + Tokens). + +2. **Offene Punkte (§12) abfragen, BEVOR du baust:** finaler Name (Clusev?), DB (MariaDB?), + GHCR-Namespace, ob `../bastion` vorhanden ist. Bei Unklarheit fragen — nicht raten. + +3. **`rules.md` erzeugen** — die STRICT RULES R1–R10 aus §4, jede mit kurzer Begründung + + ✅korrekt / ❌verboten Code-Beispiel (v.a. R1–R5: full-page Livewire-Routen, class statt Volt, + Tokens in `@theme`, keine Inline-Styles außer Progressbar-width, Löschen nur per Modal). + +4. **`CLAUDE.md` erzeugen** — Produkt (§1), Stack (§3), Ordnermap (§5), Commands (§9), + Konventionen (UI Deutsch/kein Emoji, Tokens, responsive Mobile+Tablet+Desktop) + eine + "Before-you-code"-Checkliste, die auf `rules.md` verweist. + + → **FREIGABE-PUNKT:** Zeig mir `rules.md` + `CLAUDE.md`. Erst nach meinem OK weiter. + +5. **Scaffold (Docker-first, §8/§9).** Host hat nur Docker → alles im Container bootstrappen: + - `Dockerfile` (php8.3-fpm + composer + node), `docker-compose.yml` (dev: app, vite, reverb, + queue, mariadb, redis — `build:`, bind-mount, Vite remote-HMR `host=0.0.0.0`, + `hmr.host=10.10.90.136`), `docker-compose.prod.yml`, `.dockerignore`. + - Laravel 12 + Livewire 3 (**class, kein Volt**) + Tailwind 4 + Vite. + - `composer require livewire/livewire wire-elements/modal phpseclib/phpseclib laravel/reverb`. + - `resources/css/app.css` mit `@theme`-Tokens aus §6 (aus `bastion.css`). + - Saubere Ordnerstruktur exakt nach §5. + + → **FREIGABE-PUNKT:** App startet im Container, leere Seite rendert, Vite-HMR läuft. Zeig mir das. + +6. **v1 starten (§1 Scope).** Layout + Blade-Components (Sidebar, Topbar, Panel, Kpi, StatusPill, + StatusDot, Badge, Icon[Lucide], Ring), Dashboard als full-page Livewire-Route mit Live-Metriken + über Reverb. Mock-Daten zuerst, SSH-Layer (phpseclib) danach. Alles responsive. + +Halte dich strikt an `rules.md`. Konflikt zwischen meiner Anweisung und den Regeln → nachfragen. +Verifiziere jeden Schritt (Container baut, Seite rendert, keine Console-Errors) bevor du +"fertig" sagst. Arbeite auf einem Branch, nicht auf main; committe in sinnvollen Schritten. diff --git a/rules.md b/rules.md new file mode 100644 index 0000000..15dad8d --- /dev/null +++ b/rules.md @@ -0,0 +1,306 @@ +# Clusev — STRICT RULES + +> **Non-negotiable.** Generated from `handoff.md` §4. These are the user's explicit, +> mandatory rules. If any instruction (mine, yours, or a future agent's) conflicts with a +> rule here, **STOP and ask** — do not "work around" it. Keep this file open while coding. + +--- + +## Quick gate — run through this before every commit + +- [ ] **R1/R2** — Page is a **full-page, class-based Livewire component** mapped directly in `routes/web.php`. +- [ ] **R2** — Livewire **class** in `app/Livewire/…` **and** a matching **separate Blade view** in `resources/views/livewire/…`. **No Volt.** +- [ ] **R3** — No hard-coded hex/rgb in markup — only `@theme` token utilities (`bg-surface`, `text-accent`, …). +- [ ] **R4** — No inline `style="…"` **except** a progress bar's `width`. +- [ ] **R5** — Destructive/confirm action runs through a **wire-elements/modal**, never Alpine `confirm()`. +- [ ] **R6** — Files placed exactly per the §5 folder map. +- [ ] **R7** — Verified at **375 / 768 / 1280 px**; touch targets ≥ 44px. +- [ ] **R8** — Every PHP/Composer/Node/artisan command ran **inside the container**. +- [ ] **R9** — UI copy is **German**, **no emoji**; status via color/dots/pills. +- [ ] **R10** — Reused existing `@theme` tokens + Blade components (no ad-hoc colors/widgets). +- [ ] **Secrets** — `git status` shows **no** `.env*`, token, key, or home-dir dotfile staged. + +--- + +## R1 — Pages are full-page Livewire components, mapped directly as routes + +**Why.** One page = one component = one route. No controller indirection, no orphan Blade views. +Keeps routing, state, and view co-located and predictable. + +✅ **Correct** +```php +// routes/web.php +use App\Livewire\Dashboard; +use App\Livewire\Servers\Show; + +Route::get('/', Dashboard::class)->name('dashboard'); +Route::get('/servers/{server}', Show::class)->name('servers.show'); +``` + +❌ **Forbidden** +```php +// Controller returning a Blade view for a PAGE +Route::get('/', [DashboardController::class, 'index']); + +class DashboardController { + public function index() { + return view('dashboard'); // ❌ pages do not belong to controllers + } +} +``` + +--- + +## R2 — Class-based Livewire only. Volt is FORBIDDEN. + +**Why.** Class + separate view is explicit, testable, and conventional. Volt single-file +components hide logic in the view and are banned for this project. + +✅ **Correct** — `php artisan make:livewire Servers/Show` (creates class **and** view) +```php +// app/Livewire/Servers/Show.php +namespace App\Livewire\Servers; + +use App\Models\Server; +use Livewire\Component; + +class Show extends Component +{ + public Server $server; + + public function render() + { + return view('livewire.servers.show'); // ← separate Blade file + } +} +``` +```blade +{{-- resources/views/livewire/servers/show.blade.php --}} +
{{ $server->hostname }}
+``` + +❌ **Forbidden** +```blade +{{-- Volt single-file component — NEVER. Do not run `php artisan make:volt`. --}} + 0]); +?> +
{{ $count }}
+``` + +--- + +## R3 — All colors / design tokens live in `resources/css/app.css` under `@theme` + +**Why.** Single source of truth for the visual system (Tailwind v4 CSS-first, no +`tailwind.config.js`). Markup references **tokens**, never raw colors — so a token change +restyles the whole app. + +✅ **Correct** +```css +/* resources/css/app.css */ +@import "tailwindcss"; +@theme { + --color-surface: #0F1318; + --color-accent: #FF6B2C; + --color-online: #35D07F; + --color-line: rgba(255,255,255,.10); +} +``` +```blade +
+ CPU + online +
soft accent tint
{{-- opacity modifier, not a separate token --}} +
+``` + +❌ **Forbidden** +```blade +
{{-- ❌ raw hex in markup --}} +``` + +--- + +## R4 — Inline styles are FORBIDDEN (one exception) + +**Why.** Inline styles bypass the token system and Tailwind. The **only** legitimate dynamic +value that can't be a utility class is a progress bar's percentage width. + +✅ **Correct** — the ONE allowed inline style +```blade +
+
+
+``` + +❌ **Forbidden** +```blade +
{{-- ❌ use utilities: mt-3 flex text-accent --}} +``` + +--- + +## R5 — Destructive / confirm actions use a Modal, never an Alpine dialog + +**Why.** Destructive actions (delete server, remove SSH key, stop a service) must be +deliberate, styled, and auditable. Use **wire-elements/modal** components — never native +`confirm()` or an `x-data` popup. + +✅ **Correct** +```blade +{{-- trigger: open the modal component (exact payload per installed wire-elements/modal version) --}} + +``` +```php +// app/Livewire/Modals/ConfirmDelete.php (a real Livewire modal class) +namespace App\Livewire\Modals; + +use App\Models\Server; +use LivewireUI\Modal\ModalComponent; // wire-elements/modal base class + +class ConfirmDelete extends ModalComponent +{ + public Server $server; + + public function delete(): void + { + $this->server->delete(); // audit-logged in the domain layer + $this->closeModalWithEvents([/* refresh listeners */]); + } + + public function render() { return view('livewire.modals.confirm-delete'); } +} +``` + +❌ **Forbidden** +```blade + +{{-- ❌ no native confirm(), no Alpine confirm popups for destructive actions --}} +``` + +--- + +## R6 — Clean, conventional folder structure + +**Why.** Predictable structure = fast ramp-up, no duplication, no "where does this go?". +Follow the §5 map in `CLAUDE.md` exactly. + +✅ **Correct** — component, view, model, SSH wrapper each in their canonical place +``` +app/Livewire/Servers/Show.php +resources/views/livewire/servers/show.blade.php +app/Models/Server.php +app/Support/Ssh/SshClient.php +``` + +❌ **Forbidden** +``` +app/Http/Controllers/ServerController.php {{-- ❌ pages aren't controllers (R1) --}} +app/Livewire/show.blade.php {{-- ❌ view in the class namespace --}} +app/ServerSshHelperStuff.php {{-- ❌ unstructured dumping ground --}} +``` + +--- + +## R7 — Fully responsive: mobile + tablet + desktop + +**Why.** Operators use phones and tablets in the field. Every screen must adapt — not just shrink. + +✅ **Correct** +```blade +{{-- KPI grid reflows 1 → 2 → 4; sidebar is drawer on small, fixed on large --}} +
+ + {{-- desktop sidebar --}} + {{-- ≥44px touch --}} +``` + +❌ **Forbidden** +```blade +
{{-- ❌ desktop-only; breaks on mobile/tablet --}} +