Captures the brainstorm on hotfixes, the updater reframe to 'Updates & Improvements', and registry-not-marketplace distribution: one signed/declarative/reversible primitive behind hotfixes, app installs, and themes. Vision/TODO doc with open forks, not a spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
192 lines
10 KiB
Markdown
192 lines
10 KiB
Markdown
# LibrePortal — Updates, Improvements & Distribution (Roadmap / Vision)
|
|
|
|
**Status:** Discussion / vision — *not committed decisions yet* · **Audience:** us, future-self · **Scope:** the updater feature, "hotfixes", and how third-party themes/apps/components get distributed · **Origin:** brainstorm 2026-05-30/31
|
|
|
|
> This is a thinking doc, not a spec. It captures where a design conversation
|
|
> landed so we don't lose it. Actionable items are `TODO` checkboxes; the open
|
|
> forks at the bottom are genuinely undecided. Nothing here is built.
|
|
|
|
---
|
|
|
|
## 0. The one idea everything hangs off
|
|
|
|
The cohesion worry that started this: the updater feels like a **bolt-on**. The fix
|
|
isn't to hide it — it's to notice that hotfixes, app updates, themes, and components
|
|
are all the *same verb*:
|
|
|
|
> **LibrePortal pulls a signed, declarative thing from a source, verifies it, and
|
|
> applies it reversibly (snapshot → apply → rollback).**
|
|
|
|
Build that **one distribution primitive** once, and hotfixes / app-installs / themes /
|
|
components become three *payloads* through one pipe — not three separate features.
|
|
That single primitive is the spine of this whole doc.
|
|
|
|
It rides machinery that already exists:
|
|
- **Mutations via tasks** — every apply is a `libreportal …` task, never a new mutating API.
|
|
- **Scan-and-manifest** — a thing is "installed" by dropping a folder; the scan discovers it.
|
|
- **Recovery** — the updater already snapshots-before-update and can roll back. Everything inherits that safety net for free. *This is what makes bold defaults defensible.*
|
|
- **minisign** — release signing infra already exists; reuse it as the trust anchor.
|
|
- **The existing update-check pipe** — already pings out for "is there a new version"; extend that *one signed manifest*, don't add a second phone-home.
|
|
|
|
---
|
|
|
|
## 1. Hotfixes
|
|
|
|
**What it is:** a small, signed, individually-reversible, **declarative** change the
|
|
LibrePortal team ships *out-of-band* (between releases), each with a plain-English
|
|
what + why, each independently toggleable.
|
|
|
|
**The killer use case — upstream breakage.** Self-hosters get burned independently when
|
|
an upstream image changes something (Vaultwarden renames an env var, Jellyfin moves a
|
|
data dir, an app's `latest` tag breaks on a Tuesday). A hotfix channel turns the team's
|
|
collective firefighting into a shipped product: *we notice, push a one-line reversible
|
|
fix, it lands on every install within hours.* No single self-hoster can replicate that.
|
|
|
|
**Content flavors:**
|
|
- **Upstream-breakage fixes** (the killer one)
|
|
- **Security hardening** (tighten a default header, disable a risky default)
|
|
- **Compatibility shims** (ARM, rootless, specific kernels)
|
|
- **Quality-of-life tweaks** ("cool tweaks we found useful")
|
|
|
|
**The supply-chain contract (non-negotiable for this project):** an on-by-default,
|
|
auto-fetched, auto-applied feed *is* a remote-code channel into every box. So:
|
|
- **Signed** — minisign, our key.
|
|
- **Declarative, not arbitrary scripts** — "set config key K", "add compose label L",
|
|
"patch file F *only if its checksum matches*". Bounded + auditable, not `run this .sh`.
|
|
- **Public + identical for everyone** — same transparency model as the warrant canary.
|
|
A publicly-logged feed makes a *targeted* hotfix to one victim impossible to send silently.
|
|
- **Rides the existing update-check pipe** — no new phone-home, no new metadata leak.
|
|
- **Nothing silent** — every applied hotfix lands in **History** with what / why / revert.
|
|
|
|
**On "enabled by default" (UNDECIDED — see open forks):** leaning toward splitting by
|
|
severity — *security/breakage* auto-applies (rollback has your back); *tweaks/QoL* are
|
|
surfaced with one-click apply, or auto only if the user opted into "auto-improve."
|
|
|
|
**Why on-by-default is even defensible:** because Recovery already exists — every hotfix
|
|
is reversible through the same task → snapshot → apply path. The safety net unlocks the
|
|
bold default.
|
|
|
|
`TODO` (when prioritized):
|
|
- [ ] Define the declarative hotfix schema (the allowed operations + checksum preconditions).
|
|
- [ ] Decide auto-apply policy (uniform vs severity-split).
|
|
- [ ] Surface applied/available hotfixes as a stream in the updater + History audit trail.
|
|
- [ ] Sign + publish the hotfix manifest on the same channel as the version check.
|
|
|
|
---
|
|
|
|
## 2. Reframe the updater → "Updates & Improvements"
|
|
|
|
The updater's identity is currently fuzzy ("a list of app versions" — which honestly
|
|
*could* just be a tab on the app page, which is why it reads as bolted-on). Hotfixes give
|
|
it a reason to be its own thing. Rename the concept from **"App Updater"** to
|
|
**"Updates & Improvements"** — the single front door for *everything that changes your
|
|
install from the outside*:
|
|
|
|
- **App updates** (version bumps)
|
|
- **Security** (CVEs — the urgent stuff)
|
|
- **Hotfixes** (curated small improvements — §1)
|
|
- **Recovery** (the safety net that makes all of it safe to apply)
|
|
- **History** (audit trail of everything applied)
|
|
|
|
That earns the standalone link and answers the earlier "should this fold into Admin / be
|
|
a tab on apps?" question: it stays its own section *because* it's now the curated-improvement
|
|
channel, not just a version list. (Existing tabs already are Overview / Updates / Security /
|
|
Recovery / History — this is mostly a framing + the hotfix stream, not a rebuild.)
|
|
|
|
`TODO`:
|
|
- [ ] Decide on the rename / framing in the UI.
|
|
- [ ] Add the Hotfixes stream as a tab or a section within Overview.
|
|
|
|
---
|
|
|
|
## 3. Distribution: a **registry**, not a **marketplace**
|
|
|
|
For getting third-party **apps / components / themes** onto a box: do **not** build an
|
|
upload platform (the Google-Play / Nextcloud-store / npm shape = hosting + accounts +
|
|
moderation + liability for code running near-root on people's boxes). That's the
|
|
worst-fitting shape for a privacy/no-managed-hosting/blind-relay project.
|
|
|
|
**Want Nextcloud's *UX* (in-app browse + one-click install) on F-Droid's *backend*** (a
|
|
signed, git-published index of recipes pointing at authors' own repos; contribution = a PR
|
|
to the index repo; you host a static signed JSON, not an upload server). Power users can
|
|
add a **custom source URL** (a "tap"), so the ecosystem is open without you being the host
|
|
or gatekeeper.
|
|
|
|
### 3.1 Why our apps aren't Nextcloud's apps (the key insight)
|
|
|
|
A **Nextcloud app** is a PHP plugin running *inside* the Nextcloud process — it can do
|
|
anything, which is why Nextcloud needs a code-signing CA + review. A **LibrePortal app**
|
|
is a *whole separate container we orchestrate* (upstream's image, from upstream's
|
|
registry). What a user "adds" is a **definition** (image, ports, config keys, routing) —
|
|
*wiring*, not in-process code. That's a much smaller, more declarative trust surface.
|
|
Lean into it.
|
|
|
|
### 3.2 The one real danger to design around
|
|
|
|
A LibrePortal app definition can ship host-side `tools/*.sh` hooks that run via the task
|
|
system. The compose/config is declarative + safe-ish; **the hook scripts are the
|
|
arbitrary-code part** (our equivalent of Nextcloud's in-process PHP). So tier trust around
|
|
*that*:
|
|
|
|
| Tier | Signed by | Host scripts | UI |
|
|
|---|---|---|---|
|
|
| **Official** | LibrePortal team key | allowed (reviewed) | green check |
|
|
| **Community** | author key | disallowed / sandboxed / **shown for review before install** | yellow "community — review the source", extra confirm |
|
|
| **Custom source** | author key / unsigned | advanced | "you're on your own" framing |
|
|
|
|
### 3.3 Install flow (all existing machinery)
|
|
|
|
Browse catalog → click **Add** → WebUI dispatches a task (`libreportal app add <signed-source>`)
|
|
→ fetch definition, verify signature/checksum, drop into `containers/<app>/`, run scan/regen,
|
|
app appears. Snapshot-before + reversible uninstall via Recovery. No new mutating API.
|
|
|
|
`TODO`:
|
|
- [ ] Build the signed-fetch + reversible-install primitive (§0) — hotfixes need it too.
|
|
- [ ] Surface first-party app definitions as a browsable "Browse & Add" catalog in the App Center.
|
|
- [ ] Define the trust tiers + how host scripts are gated for community sources.
|
|
- [ ] (later) The signed git index format + "add custom source" UX.
|
|
- [ ] (later) Theme gallery on the same index (lowest risk, but still signed — CSS can exfil via `background-image`).
|
|
|
|
---
|
|
|
|
## 4. Sequencing — don't build the storefront before there are goods
|
|
|
|
You have one theme set, a handful of first-party apps, and zero community contributions
|
|
today. A registry with nothing in it is pure overhead. So:
|
|
|
|
1. **First-party catalog UX now** — surface our own app definitions as browse-and-add.
|
|
Useful day one with no third parties; first-party apps *are* the seed catalog.
|
|
2. **The signed-fetch + reversible-install primitive** underneath (hotfixes need it anyway).
|
|
3. **Open to a community index** only once there's real demand. The index is a one-file
|
|
signed artifact you add the day the first good community app/theme exists — not a platform.
|
|
|
|
Same staging applies to hotfixes (first-party only, always) and themes.
|
|
|
|
---
|
|
|
|
## 5. Money / Connect note
|
|
|
|
A *paid* marketplace contradicts the decided Connect direction (blind relay, no managed
|
|
hosting; value = privacy relay + support stack). If money ever enters, "curated/supported
|
|
components *as part of Connect*" fits the model; "host a store and take a cut" does not.
|
|
Flag only — not on the table.
|
|
|
|
---
|
|
|
|
## 6. Open forks (genuinely undecided — decide before any of this becomes a plan)
|
|
|
|
1. **Hotfix scope** — config/compose tweaks only, or can a hotfix patch app files / our own WebUI code too? (Sets the entire risk profile.)
|
|
2. **Auto-apply policy** — uniformly on-by-default, or split by severity (security auto, tweaks surface-and-suggest)?
|
|
3. **Hotfix locality** — per-app (also shows on the app's page) vs system-wide vs both?
|
|
4. **Third-party contribution — yet?** Or first-party-curated for the foreseeable future? If the latter, skip the index entirely and just build the signed-fetch primitive; "registry" is a v2 concern.
|
|
5. **App catalog entry point** — curated Browse-&-Add list, or bring-your-own-compose (add an arbitrary container) as the primary entry, or both?
|
|
|
|
---
|
|
|
|
## 7. Stuff we discussed but didn't capture here
|
|
|
|
*(Placeholder — there were more conclusions from the brainstorm that didn't make it in.
|
|
Add them as they resurface.)*
|
|
|
|
- [ ] _…_
|