LibrePortal/docs/roadmap/updates-and-distribution.md
librelad 30612a0d87 docs: organize docs/ into purpose folders with consistent naming
Sort docs/ into guide/ contributing/ architecture/ roadmap/ and rename
to consistent kebab-case (USER->guide/install-and-use, FOOTPRINT->
architecture/system-footprint, frontend-modularization->architecture/
webui-architecture, etc.). Add a docs/README.md index and a docs/
CONTRIBUTING.md pointer so the forge still surfaces the contributing
guide. Fix every reference (README, init.sh comments, frontend code
comments, and the USER<->DEVELOPMENT cross-links). History preserved
via git mv. Root stays README.md + CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:48:38 +01:00

10 KiB

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.)