Merge claude/2

This commit is contained in:
librelad 2026-07-03 21:35:30 +01:00
commit 9b5a4d276d
2 changed files with 83 additions and 5 deletions

View File

@ -85,6 +85,33 @@ builds. Same tooling, different `<channel>`. To promote an edge build to stable,
rebuild with `make_release.sh stable` at that ref (or copy its artifacts into the
`stable/` path and update `stable/latest.json`).
## Publish an app to the marketplace catalog
The registry/marketplace rides the same signed channel as releases and hotfixes
(`docs/roadmap/updates-and-distribution.md` §8): one `index.json`, one signing
key, one static tree. An app in the catalog is just the drop-in definition
folder, packed and signed:
```bash
scripts/release/make_app.sh <slug> [channel] [spec.json] # e.g. make_app.sh speedtest
```
Produces, under `dist/<channel>/`: `payloads/app-<slug>.tar.gz(.minisig)`, a
pinned catalog icon under `payloads/icons/`, and the upserted + re-signed
`index.json`. Title/category/descriptions come from the app's own `.config`
no second source of truth; the optional `spec.json` overlays envelope fields
(`id`, `version`, `why`, `applies_when.min_lp`, …). Re-publishing an unchanged
app is byte-identical and keeps its version; changed bytes auto-bump it.
Boxes surface catalog apps in the App Center as **"Available — Add"** cards
(`updater check` refreshes `registry_catalog.json`), and `libreportal app add
<slug>` verifies + quarantine-validates the bundle and drops the definition
into the install tree — from there it's a normal app with a normal Install
button. Local definitions always win over the catalog; installed apps are
never touched. Test the whole loop against a local server exactly like a
release (`python3 -m http.server` + `LP_RELEASE_BASE_URL`), starting with
`libreportal artifact index`.
## Test a release locally before publishing
No hosting needed — serve `dist/` and point an install at it:

View File

@ -358,6 +358,36 @@ components is **purely additive**:
This is exactly the §3 "registry, not marketplace" shape, now expressed in the format.
### 8.4b App bundles — the registry payload (spec addendum, built 2026-07-03)
The first non-hotfix type through the pipe. **Envelope**: byte-for-byte §8.1 with
`type:"app"`, `payload.kind:"bundle"` (a `.tar.gz`), the app slug in
`applies_when.app` (fork 3's field doing double duty — for `type:"app"` the
resolver skips the "must be installed" gate; presence is the collision policy's
call), and one **optional additive object** for the browse UI:
```jsonc
"meta": { "category": "media", "description": "…", "long_description": "…",
"icon": "stable/payloads/icons/<slug>.svg", "icon_sha256": "…" }
```
Old boxes ignore `meta` (parsers select known fields). Icons are mirrored locally
by the scan **only when their bytes match the pin** — the browser is never pointed
at a remote host.
**Tarball contract** (enforced fail-closed by the box-side quarantine validator
*before* anything lands in the live-sourced definition tree — placing a definition
is deferred code execution, so trust gates → quarantine → placement, in that
order): gzip; exactly one top-level dir `== slug`; slug `^[a-z0-9][a-z0-9_]{0,31}$`
(shell-identifier-safe; `template`/`libreportal`/`tools`/`scripts`/`resources`
reserved); regular files + dirs only (no links/devices); no `..`/absolute paths;
path charset `[A-Za-z0-9._/@:+-]`; caps 5 MB tarball / 20 MB declared extracted /
400 entries; set-id bits stripped; must contain `<slug>.config` (TITLE+CATEGORY,
parsed line-wise, never sourced) + `docker-compose.yml`; every `*.sh` must pass
`bash -n`. **Collision policy:** an installed app refuses (updating live apps is
the updater's job); a local non-registry definition always wins; a registry-owned
definition updates reversibly (prior tree packed into the undo array).
### 8.5 Fork resolutions (was §6)
1. **Hotfix scope** → **config/compose ops + checksum-pinned file patches; NO code
@ -449,8 +479,29 @@ This is exactly the §3 "registry, not marketplace" shape, now expressed in the
- ✅ **Phase 5 — publisher tooling (BUILT 2026-05-31).** `make_hotfix.sh` turns a spec into
the signed payload + index entry (serial bump, freshness, publishers map), minisign-signs
with `LP_MINISIGN_SECKEY`. Verified end-to-end in unsigned/local mode.
- ⬜ **Deferred (registry; additive, demand-gated — intentionally NOT built).**
`payload.kind:"bundle"` applier (verify tarball → extract into the app tree → scan/regen) +
`type:"app"|"theme"|"component"` + the `app_add` task + community trust-tier **host-script
quarantine** (§3.2) + multi-source "tap" UX + the warrant-canary countersigning
`index_serial`. The hotfix product (Phases 15) is complete; the registry waits for demand.
- ✅ **Phase 6 — the registry slice: app bundles + Browse-&-Add (BUILT 2026-07-03).**
- `make_app.sh <slug>` packs `containers/<slug>/` (the unchanged drop-in contract)
into a deterministic signed tarball + a `type:"app"` / `payload.kind:"bundle"`
envelope (metadata derived from the app's own `.config`; `auto:false` forced —
apps never auto-apply). Shared index logic factored into `lib/release_index.sh`.
- Box side: the two designed seams opened (`_artifactResolve` accepts `app`;
`_artifactApplyBundleFlow` handles `bundle`) — fetch → sha256-pin → minisig →
**quarantine validation** (§8.8) → place in the definition tree → regen →
verify-in-apps.json → applied-record with precise undo → History. Collision
policy: installed-live refused, local definitions win, registry-owned re-add =
reversible definition update. Revert removes/restores the definition.
- `libreportal app add <slug|id>` (the `app_add` task) + `webuiRegistryCatalogScan`
(registry_catalog.json + pin-verified same-origin icons) + App Center
"Available — Add" cards (indigo pill, official badge; local always wins).
The Improvements stream is now explicitly hotfix-only (unknown types skip+log).
- En route, two latent trust-gate bugs fixed: `LP_INDEX_SIGSTATE` (and the whole
resolve-global set) was stranded in command-substitution subshells — on a
signed box every apply would have refused as unsigned. `lpFetchIndexInto` +
direct-call resolve now propagate them.
- Harness-tested: 10/10 (read path), 46/46 (bundle applier), 14/14 (catalog scan);
live task-loop + WebUI verified visually.
- ⬜ **Deferred (additive, demand-gated — intentionally NOT built).** Community
trust-tier **host-script quarantine** (§3.2) + multi-source "tap" UX +
`type:"theme"|"component"` bundle handlers + the warrant-canary countersigning
`index_serial`. The browsable marketplace *website* (shipped as a dev-mode-gated
LibrePortal app) is designed in `marketplace-website.md`.