diff --git a/docs/assets/clusev-architecture.svg b/art/clusev-architecture.svg similarity index 100% rename from docs/assets/clusev-architecture.svg rename to art/clusev-architecture.svg diff --git a/docs/assets/clusev-banner.svg b/art/clusev-banner.svg similarity index 100% rename from docs/assets/clusev-banner.svg rename to art/clusev-banner.svg diff --git a/docs/superpowers/handoffs/2026-06-20-clusev-cli-release-handoff.md b/docs/superpowers/handoffs/2026-06-20-clusev-cli-release-handoff.md new file mode 100644 index 0000000..ba1174b --- /dev/null +++ b/docs/superpowers/handoffs/2026-06-20-clusev-cli-release-handoff.md @@ -0,0 +1,131 @@ +# Session Handoff — Clusev v0.9.25 (clusev host CLI + short commands + help tab-URL) + +**Date:** 2026-06-20 +**Branch:** `feat/v1-foundation` +**Status:** Code released (tagged + pushed). **Production deploy is PENDING — user-side.** + +--- + +## TL;DR for the next session + +A feature shipping a host CLI (`clusev`), short operator commands, a help "Commands / CLI" +topic, and help-tab URL syncing is **fully implemented, tested, Codex-reviewed, locally +browser-verified, tagged `v0.9.25`, and pushed** to Gitea. The **only remaining step** is the +production deploy (which I cannot do from this host — no SSH to the prod VM) and the post-deploy +domain R12. Pick up there. + +--- + +## What shipped (v0.9.25) + +Five user requests, all delivered: + +1. **Help tabs in the URL** — `#[Url]` on `App\Livewire\Help\Index::$topic`. Clicking a topic + syncs `?topic=`; reload/bookmark/share keep the active tab. Default `overview` is omitted. + (Settings tabs already had `#[Url]`.) +2. **Short host commands** — new `clusev` wrapper installed by `install.sh` to `/usr/local/bin`. + Subcommands: `update`, `reset-admin`, `restart`, `logs`, `ps`/`status`, `migrate`, `artisan`, + `version`, `help`. Wraps the long `docker compose -f …` invocations. Host command + uses a space (`clusev reset-admin`); the underlying artisan name stays `clusev:reset-admin`. +3. **All operator-facing surfaces switched to short commands + help explains the real command** — + new bilingual help topic **"Befehle / CLI"** (`commands`) documents every command with purpose, + when-to-use, and the real underlying command (shown WITHOUT the long `-f ` filename). + Switched: versions update panel, help recovery/updates/domain-tls, settings/system reset hints, + sentinel-write-failed lang strings, MOTD. +4. **Long compose filename hidden, not renamed** — user chose "wrapper hides it" (renaming collides + with the existing dev `docker-compose.yml` and risks orphaning a live stack). A regression test + (`tests/Feature/CommandShortcutsTest.php`) forbids `docker-compose.prod.yml` on any display + surface; the new CHANGELOG entry was even worded to avoid the literal (the `/versions` page + renders CHANGELOG.md — see gotcha below). +5. **Detailed help descriptions** — done in the commands topic + reworded recovery/updates/versions. + +## Spec / plan / commits + +- Spec: `docs/superpowers/specs/2026-06-19-clusev-cli-and-command-shortcuts-design.md` +- Plan: `docs/superpowers/plans/2026-06-19-clusev-cli-and-command-shortcuts.md` +- Commits on `feat/v1-foundation` (newest first): + - `b619b32` chore: release 0.9.25 (config version bump + CHANGELOG) ← **tag `v0.9.25`** + - `5967b7b` fix: shell-escape the install dir when generating the clusev CLI (R15 Codex) + - `8d2b708` feat: short clusev commands in versions panel, lang strings, MOTD (+ test-string fixups) + - `437a225` feat: help tab-URL + Commands/CLI topic + short command copy + - `580c72a` feat: clusev host CLI wrapper for short stack commands +- Branch + tag **pushed** to `git.bave.dev/boban/clusev.git`. Working tree clean. + +## Key files + +- New: `docker/clusev/clusev` (wrapper template, `__CLUSEV_DIR__` placeholder, **unquoted** + assignment — install.sh injects a `printf %q` shell-escaped path; do NOT re-quote it). +- New: `tests/scripts/test-clusev-cli.sh` (host bash test, 8 checks incl. injection-resistance). +- New: `resources/views/livewire/help/content/{de,en}/commands.blade.php`. +- New: `tests/Feature/CommandShortcutsTest.php` (leak guard + short-command assertions). +- Changed: `install.sh` (phase 9 renders+installs the CLI), `app/Livewire/Help/Index.php`, + `lang/{de,en}/{help,versions,settings,system}.php`, help partials (recovery, updates, domain-tls), + `resources/views/livewire/versions/index.blade.php`, `docker/motd/00-clusev`, + `tests/Feature/{HelpPageTest,ForgotPasswordSmtpAwareTest}.php`, `config/clusev.php`, `CHANGELOG.md`. + +## Verification done + +- `php artisan test` (whole suite): **236 passed**. +- `bash tests/scripts/test-clusev-cli.sh`: green (incl. a real injection-resistance check — + confirmed the old quoted-rendering was exploitable, the new one is not). +- `npm run build`: OK. +- **Codex R15: PASS.** It found ONE security issue — unescaped `$(pwd)` rendered into the generated + CLI, a root-code-injection vector via a hostile checkout path. Fixed (`printf %q` + unquoted + assignment) and re-reviewed PASS, including the embedded-newline edge case. +- **Local R12** (headless Chrome / puppeteer Docker against the dev stack on `http://localhost`): + `/help` 200, URL syncs to `?topic=commands` on click, deep-link reload keeps the topic, commands + content renders, **0 console/page/network errors**; `/versions` 200, shows `sudo clusev update`, + no `docker-compose.prod.yml`. (Used gkonrad id=4, 2FA-off; temp password set + **restored** to the + original hash afterwards.) + +--- + +## NEXT STEP (start here) + +### 1. Deploy v0.9.25 to production (user runs this on the prod VM) + +From this host I have **no SSH access** to the prod VM (`10.10.90.165` — publickey/password denied) +and there is **no local prod stack**, so I cannot deploy. The operator runs, on the prod VM: + +```bash +cd clusev && sudo ./update.sh +``` + +(This is the release that first ships the `clusev` wrapper, so use `./update.sh` this once; the +update runs `install.sh` which installs `/usr/local/bin/clusev`, so from the NEXT release +`sudo clusev update` works. Alternatively click "Jetzt aktualisieren" in the panel.) + +After it finishes, `which clusev` on the VM should resolve. + +### 2. Domain R12 (I CAN do this over HTTP after the deploy) + +The public domain `panel.test.bave.dev` → external proxy `10.10.20.20` (Zoraxy) → prod VM. Only +HTTP is needed (no SSH), via the puppeteer Docker image. **Only meaningful AFTER the deploy** (the +domain serves the old image until then). Verify on the domain: +- `/help` 200, click "Befehle / CLI" → URL `?topic=commands`, reload keeps it, DE/EN switch, no + console errors. +- `/versions` shows `sudo clusev update`. +- On the VM shell (read-only): `clusev help`, `clusev ps`, `clusev version`. Do NOT run + `reset-admin`/`update` against the real admin. + +--- + +## Constraints & gotchas (carry forward) + +- **Respond in German** in chat (code/commits/docs stay English). Caveman-terse mode was active. +- **Gitea token**: read ONLY at push time from `/home/nexxo/.env.gitea` (`GIT_ACCESS_TOKEN`), never + echo/commit; sanitize push output `sed -E 's#https://[^@]*@#https://@#g; s/[0-9a-f]{40,}//g'`. +- **De-Claude history**: commits intentionally carry NO `Co-Authored-By: Claude` trailer (the + project wants Claude-free history; history rewrite + force-push only at beta with explicit OK). +- **Dev DB**: never `Server::create` to inspect; use RefreshDatabase tests / `Blade::render`. +- **View-cache flake**: run `php artisan view:clear` before browser-verifying a `.blade.php` change; + for `artisan test` flakiness isolate with `VIEW_COMPILED_PATH=/tmp/vc-xxx`. +- **`/versions` renders CHANGELOG.md** — whole-page leak scans flag historical changelog prose + (e.g. the v0.4.1 entry literally contains `@php`); scope leak checks to UI chrome, not changelog. +- Memory index updated: see `clusev-prod-deploy-user-side`, `clusev-versions-page-renders-changelog`. + +## Not done / possible follow-ups + +- Production deploy + domain R12 (above) — the only open item for THIS feature. +- (Backlog) de-Claude history rewrite, deferred to beta. +- (Optional) bash completion for `clusev`; was intentionally left out (YAGNI).