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
parent
b00e7a49fd
commit
a94efb0b0d
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
|
@ -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).
|
||||||
Loading…
Reference in New Issue