fix(release): leak-safety hardening from adversarial review

- promote.sh: add a leak guard that refuses to publish a tree still carrying a
  private identifier (git.bave.dev / boban/clusev / clusev-staging) or an internal
  doc (CLAUDE.md, rules.md, handoff.md, kickoff-prompt.md, docs/). This makes
  promotion safe even for a tag cut BEFORE the export-ignore fixes — such a tree is
  refused instead of leaking the dev/staging repos. Negative test added.
- .gitattributes: export-ignore kickoff-prompt.md (internal bootstrap doc; leaked
  the VM IP + infra topology).
- README: move the banner/architecture SVGs out of the export-ignored docs/ into
  art/ so the public README images are actually published.
- .env.example: comment out CLUSEV_REPOSITORY — a present-but-empty value defeats
  the config default (env() returns ''), which would silently disable the public
  update check. Left unset, the public-repo default applies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/v1-foundation
boban 2026-06-22 22:15:36 +02:00
parent b00e7a49fd
commit a94efb0b0d
3 changed files with 131 additions and 0 deletions

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -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=<key>`; 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 <prod compose> …` 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 <compose>` 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://<redacted>@#g; s/[0-9a-f]{40,}/<redacted>/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).