clusev/docs/session-handoff.md

115 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Clusev — Session Handoff (2026-06-14)
Self-contained ramp-up for the next session. Read `rules.md` (R1R17) + `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 `<header>` 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. 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 <pkg> | grep -q '^ii'` /
`rpm -q`; active = `systemctl is-active`. → asked "is the **apt/rpm package** recorded?"
- `Fail2banService::status` + `FirewallTool` (panels): installed = `command -v <client>`;
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).
---
## 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/<ver>`), 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 `<header>` 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}/<group>.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://<redacted>@#g; s/[0-9a-f]{40}/<redacted>/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.