clusev/docs/session-handoff.md

158 lines
10 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).
- `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.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 <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).
---
## 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/<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.