Merge claude/2
This commit is contained in:
commit
b0a194dd48
132
docs/roadmap/marketplace-website.md
Normal file
132
docs/roadmap/marketplace-website.md
Normal file
@ -0,0 +1,132 @@
|
||||
# LibrePortal Marketplace — the marketplace app (Roadmap / Design)
|
||||
|
||||
**Status:** direction decided 2026-07-03 (container MVP planned; submissions phase 2) ·
|
||||
**Audience:** us, future-self · **Scope:** the browsable marketplace *system* — the
|
||||
apps.nextcloud.com analogue — shipped as a LibrePortal app, and how community
|
||||
submissions eventually flow through it · Builds on `updates-and-distribution.md` §3/§8.
|
||||
|
||||
---
|
||||
|
||||
## 1. The decided shape: the marketplace is a LibrePortal app
|
||||
|
||||
The marketplace server ships **in this repo as `containers/libreportal-marketplace/`**
|
||||
— a normal app definition installed through the normal pipeline, with one twist:
|
||||
it is **dev-mode-only** (see §4). The official `marketplace.libreportal.org` is
|
||||
simply *our* instance of this app running on our own box — full dogfooding — and
|
||||
anyone who wants their own marketplace enables Developer Mode and installs the
|
||||
same app. "Open source and self-hostable" is therefore not a promise, it's the
|
||||
literal distribution mechanism.
|
||||
|
||||
The marketplace has two user-facing surfaces over **one source of truth**:
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ THE SIGNED CATALOG (the source of truth) │
|
||||
│ <base>/<channel>/index.json (+ .minisig) │
|
||||
│ <base>/<channel>/payloads/<id>.tar.gz │
|
||||
└───────────┬───────────────────┬────────────┘
|
||||
│ │
|
||||
boxes fetch + verify it the marketplace app
|
||||
│ serves + renders it
|
||||
┌──────────────▼─────────┐ ┌──────▼──────────────────┐
|
||||
│ IN-APP (every box) │ │ libreportal-marketplace │
|
||||
│ App Center grid shows │ │ (dev-mode app): hosts │
|
||||
│ "Available — Add" │ │ the catalog tree + a │
|
||||
│ cards; Add = task → │ │ browsable website: │
|
||||
│ verify → drop-in │ │ browse, search, per-app │
|
||||
└────────────────────────┘ │ pages, submit info │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
Boxes never talk to the website and never trust it — they only trust the
|
||||
minisign signature on the catalog. A marketplace instance can be compromised,
|
||||
defaced, or down and no install is ever at risk. That property is load-bearing;
|
||||
every choice below preserves it.
|
||||
|
||||
## 2. What the container serves
|
||||
|
||||
One app, one docroot, two things in it:
|
||||
|
||||
- **The catalog tree itself** — `/<channel>/index.json(.minisig)` +
|
||||
`/<channel>/payloads/…`, exactly the layout `make_app.sh` / `make_hotfix.sh`
|
||||
emit into `dist/`. A box pointed at this instance via `CFG_RELEASE_BASE_URL`
|
||||
consumes it directly.
|
||||
- **The browse UI** — static pages (no third-party assets, same rule as the
|
||||
WebUI) rendered **client-side from the same `index.json`**: front page by
|
||||
category, search, one page per app (title, description, icon, publisher +
|
||||
trust badge, version), a copyable `libreportal app add <slug>` snippet, and a
|
||||
"run your own marketplace" page documenting exactly this app + the
|
||||
`CFG_RELEASE_BASE_URL` knob. Client-side rendering means the UI needs no build
|
||||
step and can never disagree with the catalog it serves.
|
||||
|
||||
**No backend.** No accounts, no database, no API server in the MVP. Publishing
|
||||
*to* a marketplace = running the publisher tools and syncing their `dist/`
|
||||
output into the app's docroot (its bind-mounted data dir).
|
||||
|
||||
**Server:** `nginx:alpine` static serving (the pattern the retired
|
||||
`getlibreportal` container established: JSON no-cache, tarballs/sigs immutable).
|
||||
Traefik routing, ports, backup labels — all standard app-definition machinery.
|
||||
|
||||
## 3. Community submissions (phase 2 — the F-Droid flow, not the Play flow)
|
||||
|
||||
When community apps open up (demand-gated, per `updates-and-distribution.md`
|
||||
§8.5 fork 4), submission is a **pull request, not an upload**:
|
||||
|
||||
1. Author PRs their app folder (`containers/<app>/` shape — the drop-in contract
|
||||
in `docs/contributing/development.md`) to a public catalog repo.
|
||||
2. CI validates mechanically: the bundle tarball contract, compose lint, no
|
||||
host-script hooks unless flagged for review, icon/meta present.
|
||||
3. A maintainer reviews — *especially* any `tools/*.sh` (the one genuinely
|
||||
dangerous surface, §3.2) — then signs and publishes with the same tools used
|
||||
for first-party apps. The entry lands in the index with `publisher:<author>`,
|
||||
`trust:"community"`.
|
||||
4. The marketplace site renders the submission queue from the PR list (links,
|
||||
not accounts) and the published result from the index.
|
||||
|
||||
Everything ends in "reviewed → signed → one public catalog" because the boxes
|
||||
only trust the signature. An account-based submission portal could be added to
|
||||
the marketplace app later, but it would only ever be a nicer way to open the PR
|
||||
— it can never become an unreviewed upload channel.
|
||||
|
||||
## 4. Dev-mode gating (the important part)
|
||||
|
||||
The marketplace app must not confuse regular users browsing their App Center —
|
||||
running a marketplace is an operator/developer act. So it ships **hidden unless
|
||||
Developer Mode is on**, riding the existing `CFG_DEV_MODE` machinery (10-click
|
||||
logo easter egg; auto-enabled on git/local installs):
|
||||
|
||||
- New app-level convention: `CFG_<APP>_DEV_ONLY=true` in the app's `.config`.
|
||||
- `webuiGenerateLibrePortalConfig` emits it as `dev_only: true` in `apps.json`.
|
||||
- The App Center filters `dev_only` apps out of the grid/search unless
|
||||
`window.systemConfigs.CFG_DEV_MODE === 'true'` (the same flag the `**DEV**`
|
||||
config-field filter uses — see `field-factory.js` `_filterDevKeys`).
|
||||
- The CLI stays honest: `libreportal app install libreportal-marketplace` works
|
||||
regardless (dev-only is decluttering, not a security boundary — the security
|
||||
boundary is the signing key, which the app never contains).
|
||||
|
||||
The convention is generic — any future operator-grade app (build servers, relay
|
||||
infrastructure) can reuse it.
|
||||
|
||||
## 5. Explicitly not doing
|
||||
|
||||
- **No dynamic marketplace backend** (accounts / uploads / ratings) in the MVP —
|
||||
re-affirming §3's decision; nothing in the in-app UX needs it, and phase 2
|
||||
submissions stay review-and-sign whatever front door exists.
|
||||
- **No telemetry** on the marketplace site (same rule as the product); download
|
||||
counts, if ever wanted, come from static server logs on *our* instance only.
|
||||
- **No website-only metadata.** If the site needs a field (e.g. screenshots
|
||||
later), it enters the catalog format as an optional envelope/`meta` addition
|
||||
so in-app and website stay one source of truth.
|
||||
|
||||
## 6. Sequencing
|
||||
|
||||
1. **Now:** the in-app registry slice (`updates-and-distribution.md` §8.7) —
|
||||
catalog format gains `meta` (category/description/icon), `make_app.sh`
|
||||
publishes app bundles. The catalog becomes real.
|
||||
2. **Now (after the publisher tool exists):** `containers/libreportal-marketplace/`
|
||||
MVP — nginx app serving the catalog tree + client-rendered browse UI, plus
|
||||
the `CFG_<APP>_DEV_ONLY` gating convention. End-to-end tests then run against
|
||||
a real marketplace instance instead of `python3 -m http.server`.
|
||||
3. **Demand-gated:** the community submission flow (public catalog repo + CI
|
||||
validation + review/sign tooling), yellow-tier rendering, and the in-app
|
||||
community trust-tier UX (host-script quarantine — §8.7 deferred).
|
||||
Loading…
x
Reference in New Issue
Block a user