diff --git a/docs/session-handoff.md b/docs/session-handoff.md new file mode 100644 index 0000000..0f898b9 --- /dev/null +++ b/docs/session-handoff.md @@ -0,0 +1,106 @@ +# 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). +- **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. OPEN BUG (priority) — fail2ban & UFW falsely shown "nicht installiert" + +**Symptom:** the Hardening checklist shows `fail2ban` and `Firewall (UFW)` as **OFFEN · +nicht installiert**, while the fail2ban-Status panel below shows `aktiv · 14 gesperrt` with +real SSH-scanner bans. Contradiction. + +**Root cause — two different detection methods (same host):** +- `HardeningService` (checklist): installed = `dpkg -l | grep -q '^ii'` + (`PackageManager::isInstalledTest`, apt) / `FirewallTool::installedTest()` for ufw; active = + `systemctl is-active`. → asks "is the **apt package** recorded?" +- `Fail2banService::status` (panel): installed = `command -v fail2ban-client`; active = + `systemctl is-active`; jails via `fail2ban-client status`. → asks "is the **binary present + + service running**?" +- The host is Debian/apt and fail2ban is genuinely running (14 real bans), but dpkg does not + list it as `^ii` → checklist false-negative. Same for ufw. + +**Fix direction:** make the checklist's "installed" = (apt `ii`) OR (`command -v` binary +present) OR (service active) — align with `Fail2banService`. Touch: +`app/Services/HardeningService.php` (~lines 50–66, the `$cmd` probe), +`app/Support/Os/PackageManager.php::isInstalledTest`, +`app/Support/Os/FirewallTool.php::installedTest`. Verify on 10.10.90.162 how fail2ban/ufw are +actually installed (apt vs source vs `rc` config-only). A task chip was spawned for this. + +--- + +## 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.