clusev/docs/session-handoff.md

10 KiB
Raw Blame History

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 unifiedx-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.