# Clusev — Session Handoff (2026-06-14) Self-contained ramp-up for the next session. Read `rules.md` (R1–R17) + `CLAUDE.md` first; this file = current state, open items, and the exact work recipes. `handoff.md` is the original foundational decisions doc (do not overwrite it). --- ## 1. Current state - **Branch:** `feat/v1-foundation` (never commit to `main`). Remote: Gitea `git.bave.dev/boban/clusev.git`. - **Shipped tags** (all pushed): - `v0.3.0` — full bilingual UI (DE default + EN), user-selectable; rule **R16**. - `v0.4.0` — change the panel domain from the dashboard (DB snapshot → applied on restart; Caddy on-demand TLS; request-derived Reverb endpoint; anti-lockout middleware). - `v0.4.1` — fixed a garbled `
` on every signed-in page (Blade raw-block bug → rule **R17**); made broadcast channels **private** (metrics no longer public). - `v0.4.2` — Server-Details UX + hardening polish (see §2b): uniform `danger-soft` buttons, installed-only firewall/fail2ban grid, auto-updates as a neutral preference, SSH-verified server creation + `pending` status, SSH key-only hint, and the fail2ban/UFW detection fix. - `v0.4.3` — responsive audit (R7, all views @ 375/768): hardened mobile overflow from long panel domains (`break-all`/`min-w-0`) and gebannte IPv6 in der fail2ban-Liste. Live pages were already overflow-free; only data-dependent edge cases fixed. - `v0.4.4` — stop iOS zoom-on-focus: one unlayered `@media (pointer: coarse)` rule in `app.css` forces 16px on `input`/`textarea`/`select` on touch devices (phone + tablet any size), desktop sizing unchanged. - `v0.5.0` — **account recovery** (spec/plan in `docs/superpowers/`): 2FA backup codes (encrypted, shown once + downloadable + regenerable, usable at the login challenge), forgot-password via 2FA/backup code (email-link path gated on a configured mailer), and a `clusev:reset-admin` CLI lockout fallback. Resets rotate `remember_token`; codes consumed atomically. **Phase 2 still open: WebAuthn/YubiKey** as a login factor (needs `laragear/webauthn`, JS ceremonies, HTTPS/domain) — own spec. - `v0.4.6` — bilingual input validation: added `lang/{de,en}/validation.php` (full Laravel 13 key set + localized `attributes`); previously all field-validation errors were English-only. - `v0.4.5` — branding: full favicon/PWA set (`public/favicon.*`, `icon-192/512`, `site.webmanifest`) from the orange server brand-mark, wired via `partials/head-icons`; custom bilingual error pages (`resources/views/errors/*` + `lang/errors.php`) replacing Laravel's defaults, prod `APP_DEBUG=false` pinned, no raw error text. NOTE: unmatched-404 + 503 render in the DE default (no session pre-StartSession) — deliberate per R16, custom message still shows. - **Dev stack** runs on this VM via `docker compose` (app container IP **172.18.0.6**, host `10.10.90.136:80`). Managed fleet host = **10.10.90.162** (Debian / apt / ufw; the only live-verified target — dnf/zypper/firewalld command strings are built but not live-tested). --- ## 2. RESOLVED — fail2ban & UFW falsely shown "nicht installiert" (fixed `221e49f`) **Was:** the Hardening checklist showed `fail2ban` and `Firewall (UFW)` as **OFFEN · nicht installiert**, while the fail2ban-Status panel below showed `aktiv · 14 gesperrt` with real SSH-scanner bans. Contradiction. **Root cause — two different detection methods (same host):** - `HardeningService::state` (checklist): installed = `dpkg -l | grep -q '^ii'` / `rpm -q`; active = `systemctl is-active`. → asked "is the **apt/rpm package** recorded?" - `Fail2banService::status` + `FirewallTool` (panels): installed = `command -v `; active = `systemctl is-active`. → asked "is the **binary present + service running**?" - The host is Debian/apt and fail2ban/ufw is genuinely running, but dpkg does not list it as `^ii` (source install, or dpkg `rc` config-only state) → checklist false-negative. **Fix (committed on `feat/v1-foundation`, NOT yet tagged/pushed):** the checklist's installed probe in `app/Services/HardeningService.php::state` now ORs three conditions — package-manager test (`dpkg ^ii` / `rpm -q`) **OR** client binary on PATH (`command -v`) **OR** service active (`systemctl is-active`) — via the local `$anyOf()` helper, for **both** fail2ban and the firewall (ufw/firewalld). Consistent with `Fail2banService::status`. Since a component can never be active without being installed, this only removes FALSE negatives, never adds a false-positive. `PackageManager`/`FirewallTool` were left unchanged (the OR is composed in `HardeningService`, not in their primitives). Verified: new unit test `tests/Feature/HardeningServiceTest.php` (Debian/apt source-install fallback) **passes**; Pint clean; Codex review clean (no actionable regression). **Still pending (deferred this session):** (a) live acceptance check against fleet host 10.10.90.162 — load Server-Details and confirm fail2ban/ufw now read "installiert"; (b) no version bump / CHANGELOG / tag / push — the fix lives on the branch only. The prior session's "Fix fail2ban/UFW false negative" task chip is now **stale** (work is done). --- ## 2b. DONE — Server-Details UX + hardening polish (2026-06-14) Seven reported issues fixed on `feat/v1-foundation`, **released as `v0.4.2`** (tagged + pushed). Spec: `docs/superpowers/specs/2026-06-14-server-details-hardening-polish-design.md`; plan: `docs/superpowers/plans/2026-06-14-server-details-hardening-polish.md`. 1. **Buttons unified** — `x-btn`: retired borderless `ghost`/`ghost-danger`, added `danger-soft` (red-tinted border+bg, always). Löschen/trash now consistent, no hover-only red. All 10 usages migrated (servers/settings/files). 2. **Firewall + fail2ban panels** — render only when the tool is installed **or** the read failed (`readError`); 2-col `lg` grid when both, full width when one, hidden when absent. Removed the redundant "nicht installiert" boxes. 3. **Auto-updates** — reframed as an operator preference: `secure` always true, muted aktiv/inaktiv chip (never SICHER/OFFEN). `HardeningService` `neutral` row flag. 4. **Create-server** — verifies the SSH login inside the create transaction via `FleetService::testConnection` (echoes a marker, requires it in output — proves exec, tolerates missing exit-status). Failure rolls back + shows the SSH reason. New server starts `pending`. 5. **`pending` status** = "Initialisierung" (cyan, never red) across status-pill/dot, server-item, fleet index, and the details hero. Promotes on first contact. 6. **SSH key-only hint** on the SSH-Passwort-Login checklist row while password auth is on. Verified: 16 tests pass (`ButtonComponentTest`, `StatusComponentTest`, `HardeningServiceTest`, `FleetTestConnectionTest`, `CreateServerTest`, `ServerShowPanelsTest`, `ServerShowSshHintTest`); Pint clean; R12 browser DE+EN at 375/768/1280 (200, 0 console, 0 leaked tokens); Codex clean after 3 review rounds. **Pre-existing** stock `tests/Feature/ExampleTest.php` still fails (asserts `/` = 200 but the app redirects to login) — unrelated; a task chip was spawned to delete/fix it. ## 3. Other open items / follow-ups - `resources/js/app.js` `dualChart` is **dead code** — the dashboard chart is a server-rendered SVG, nothing subscribes via Echo. Consider removing it (its `Echo.private('metrics')` is the only metrics subscriber and it never runs). - **Caddy on-demand TLS / real ACME issuance is config-validated only** (no Caddy in dev, no public DNS). App-side logic (`/_caddy/ask`, snapshot, redirects, Reverb tunnel) is browser-verified; the actual cert issuance needs a real prod + DNS test. - "Install-with-domain → clear-to-bare-IP" is the rough edge: re-run `install.sh` for a clean bare-IP realtime (REVERB_* in `.env` otherwise stays domain-shaped). - firewalld is read-only (rule add/delete is ufw-only) — needs a RHEL/Fedora/openSUSE host to finish. --- ## 4. Plugins installed this session (need a SESSION RESTART to load) Installed via the server's `claude` CLI (`~/.claude/remote/ccd-cli/`), scope **user**, into the **local** plugin system (separate from the cloud-synced set): - `caveman@caveman` — ultra-compressed output (token saver); SessionStart hook auto-activates. - `superpowers@superpowers-dev` (v5.1.0) — workflow skills (TDD, systematic-debugging, …). - `ui-ux-pro-max@ui-ux-pro-max-skill` (v2.5.0). Marketplaces persisted in `~/.claude/plugins/known_marketplaces.json`. They are **not** in the current session's skill registry until the session is **restarted** (that's why `/caveman:caveman` still fails this session). After restart: `/caveman:*`, `/superpowers:*`, `ui-ux-pro-max` work. --- ## 5. Work recipes (how to actually verify & ship) - **Everything in containers (R8).** Pint: `docker compose exec -u "${HOST_UID:-1002}:${HOST_GID:-1002}" -T app ./vendor/bin/pint --dirty`. Build: same prefix + `npm run build`. - **R12 — verify in a real browser, BOTH locales:** every touched route → HTTP 200 + 0 console errors AND **inspect the rendered DOM** for leaked `@` / `{{ }}` / `$var` / `group.key` text (R17 — this is how the v0.3.0/v0.4.0 `
` bug slipped: 200 + clean console but garbage markup). Recipe: headless Puppeteer image `ghcr.io/puppeteer/puppeteer:latest` on docker network `clusev_clusev`, target `http://172.18.0.6`, `NODE_PATH=/home/pptruser/node_modules`. Auth via a **TEMP** route `Route::get('/__peek', fn (Request $r) => [AuthFacade::loginUsingId(1), redirect($r->query('to','/'))][1])` — **remove before commit** (route:clear after). Locale is per-user (`user->locale` wins over session), so to test EN the peek must also persist `user->locale`. Dashboard route is `/` (not `/dashboard`). - **R15 — Codex:** `export PATH="$HOME/.npm-global/bin:$PATH"; codex review --uncommitted` until clean (filter node_modules noise). - **Localization (R16):** every visible string in `lang/{de,en}/.php`, keys identical in both. Verify: scan all `__('group.key')` usages resolve via `Lang::has($k,'de'/'en')` and that each `lang/de/*.php` group has the same keys as `lang/en/*.php`. - **Push:** token in `/home/nexxo/.env.gitea` (`GIT_ACCESS_TOKEN`) — read ONLY at push time, never echo/commit; sanitize push output (`sed -E 's#https://[^@]*@#https://@#g; s/[0-9a-f]{40}//g'`). - **Fleet host safety:** SSH password is in the encrypted vault (never in code/logs). Never ban/lock-out the control plane (10.10.90.x). **Done = ** Pint clean → npm build → R12 (200 + 0 console + DOM scan, DE+EN) → Codex clean → version bump + CHANGELOG → commit on `feat/v1-foundation` → tag → push (sanitized) → temp `/__peek` removed.