Ctrl K
ComunidadGuías…

proposals/2026-07-18-host-platform-expansion.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Host platform expansion (Start9, Linux, macOS, Windows)

## Goal

Broaden **where the PearTune host can run** beyond the Umbrel/Docker path, so the
pitch — *"your music lives on a machine you own (an Umbrel, a NAS, an old
desktop)"* — is true for more than Umbrel owners. This is the agreed prerequisite
to the next big feature (multi-source / multi-host): broaden *where the host runs*
before adding complexity to *what it serves*, because packaging is lower
architectural risk and does not entangle with the source-scoped-`trackId` dedup
problem multi-source needs.

Scope decided with Tim (2026-07-18): **HOST platforms only.** These targets are
places the always-on daemon runs; the phone stays the client. A desktop *client*
(playing music on the laptop itself) is explicitly out — it roughly doubles the
work and overlaps the deferred iOS/client-parity effort.

Distribution philosophy decided with Tim: **technical-first.** Ship the Docker /
CLI / systemd paths so technical self-hosters can run it now; the polished signed
desktop tray app is a follow-on, not a blocker.

## Tier

**T2.** This is packaging + a new Start9 surface + a desktop distribution surface.
It adds **no wire/protocol change** and **no change to the two load-bearing rules**
(the grant store stays host-local; revoke still kills live connections) — packaging
runs the same `host/` code. The one thing that would have made it T3 — a first-class
auth model for a non-Umbrel install — is **already done** (proposal
2026-07-14-dashboard-auth): `createAuth` is a real password login (session cookie,
failure lockout, timing-safe compare) and `requireSafeBind` fail-closed refuses to
bind anything but loopback unless a password is set. So the security posture already
holds off-Umbrel; this proposal does not touch it.

## Background: what already exists, and what we reuse

Two findings make this much smaller than it looks:

1. **The host is a plain Node CLI, already designed for this.** `host/index.js` is
   a `#!/usr/bin/env node` CLI with a `bin` entry, and its own header already names
   the roadmap: *"On an Umbrel this is the container entrypoint; on a desktop it is
   what the tray app wraps."* Config comes from flags or env
   (`PEARTUNE_MUSIC`, `PEARTUNE_PASSWORD`, host/port). Nothing about the runtime is
   Umbrel-specific.

2. **The suite already has the templates.** The PearTune host is modeled on
   PearCircle's `seeder-launcher`, and:
   - `pearcircle/seeder-launcher/start9/manifest.yaml` — the seeder is **already
     packaged for StartOS**. Direct template for a PearTune s9pk.
   - `pearcal-native/electron` + `pearcal-native/desktop` + `pearguard/desktop` —
     the suite already has the Electron/tray desktop pattern the host header
     anticipates.
   - Wiki: `cross-platform-parity.md`, `linux-cross-build-quirk.md` for build gotchas.

## Shared prerequisite: a fresh multi-arch image

Start9 and every Docker-based path (Linux, and the technical macOS/Windows path)
wrap the **same** multi-arch image, which is currently **stale** — it predates the
source picker, and Tim's box has only ever received `docker cp`'d files, never a
rebuilt image (see TODO Milestone 4 + the many "RELEASE-GATE NOTE" entries in
DONE/DECISIONS). So this effort pulls the **M4 fresh-image publish** forward as its
first step, rather than treating it as separate:

- Rebuild the multi-arch (amd64 + arm64) image to GHCR, **baking in everything since
  PR #6** — including this session's host-side changes: the analog-amber dashboard
  recolour (`host/ui/dashboard.html`, `host/ui/login.js`), the new brand icon
  (`umbrel/icon.svg`), the green connected-dot + logo dashboard tweaks, plus the
  earlier session's host files (presence.js, state/media/server, protocol framing,
  logprune, sort adapters, etc.).
- Re-pin the compose digest.
This image is the artifact all the container-based platforms below consume.

## Shared technical risk: holepunch connectivity per platform

The whole pitch is "no port forwarding" — the host reaches HyperDHT via **outbound
UDP holepunching**. On Umbrel this forced `network_mode: host` (bridge NAT killed
holepunching — measured twice; see dashboard-auth). The per-platform question is
always the same: *can the host make the outbound UDP the DHT needs?*
- Native desktop (Linux/macOS/Windows): yes — a normal process behind a home router
  makes outbound UDP fine. This is the pitch working as designed.
- Containerized (Start9): must replicate the host-networking / DHT reachability the
  Umbrel package needs. The seeder's Start9 package already solved this — reuse its
  net config and verify.
Each platform's acceptance test below includes "pair a phone and stream from off the
LAN" precisely to prove holepunch works there.

## Scope, per platform

### 1. Linux (lowest effort — mostly done)
- **Docker:** the existing image + a compose file already run anywhere Docker does.
  Deliverable is documentation + the re-pinned image, not code.
- **Native:** the host already has a `bin`, so `npx`/a global install works for
  technical users. Add a sample **systemd unit** for always-on, and document the
  `PEARTUNE_MUSIC` / `PEARTUNE_PASSWORD` env. `requireSafeBind` already forces a
  password for any non-loopback bind; on first run with a non-loopback bind and no
  password set, the host **generates-and-prints** one (persisted to the data dir) so
  a bare install works without a platform to mint `${APP_PASSWORD}` (see Resolved).
- **Bundled binary: out for v1** (Docker/CLI is enough — see Resolved).

### 2. Start9 (medium effort, high reuse)
- Adapt `pearcircle/seeder-launcher/start9/manifest.yaml` → a PearTune `start9/`
  s9pk that wraps the fresh multi-arch image: manifest (id/title/version/marketing),
  config spec (music path, password → StartOS `${APP_PASSWORD}` equivalent), health
  check, volume for `/data` (grant store — host-local, never replicated) + the music
  mount, and the network/holepunch config from the seeder.
- **Auth:** like Umbrel, StartOS's proxy cannot front a host-networked service, so
  use the built-in password login (already present). Wire StartOS's generated
  password in.
- **Test on hardware:** Tim's Start9 box (returned-feline.local, i5-7500T/16GB — see
  `[[peartune-selfhost-hardware]]`). **Distribution: sideload the s9pk for v1** (not
  a community-registry publish — see Resolved). Acceptance: sideload the s9pk, set
  the music path + password, pair the TCL, stream on-LAN and off-LAN, revoke mid-song.
- **Reach note (from the 2026-07-14 platform survey):** Start9's registry has only
  Jellyfin + Nextcloud Music, so on Start9 the Jellyfin + Subsonic adapters are what
  make PearTune useful at all — good fit, existing adapters cover it.

### 3. macOS / Windows (technical-first now; tray app follow-on)
- **Now (technical):** the Docker Desktop path + the Node CLI, documented. This gets
  it into the hands of technical self-hosters on those OSes without new code.
- **Follow-on (polished):** a menubar/tray app that starts the host, opens the
  dashboard, and runs at login — adapting `pearcal-native/electron`. The real cost
  lives here and is why it's deferred: bundling the Node runtime, **code signing +
  notarization** (macOS needs an Apple Developer account, $99/yr; Windows needs a
  cert), and auto-start. Tracked as its own follow-on with its own proposal when we
  get there.

## Compat / migration

- No data or wire change. A host is a host on every platform; the grant store
  (`/data`) and `source.json` shapes are unchanged, so a library can move between
  platforms by moving its data dir.
- The device-facing protocol is identical, so existing paired phones keep working
  regardless of where the host is moved.

## Verify (per platform, on real hardware)

The canonical gate (`npm run verify`) is unchanged — this is packaging. Acceptance
is per-platform hardware smoke, each proving the same invariants:
1. Install via the platform's mechanism; set music path + password.
2. Pair the TCL by QR from the dashboard.
3. Browse, play, seek — **on-LAN and off-LAN** (proves holepunch on that platform).
4. Revoke mid-song from the dashboard: within a second, reconnect + next track + art
   all denied (the CLAUDE.md revoke gate), current buffer may finish.
- Linux: also verify the systemd unit restarts the host on boot.
- Start9: also verify the s9pk config (music path, password) round-trips.

## Rollback

Each platform package is additive and independent. Nothing here changes the running
Umbrel deployment except the shared fresh image (which is a strict improvement over
the stale one). Reverting a platform = not publishing that package.

## Sequencing

1. **Fresh multi-arch image → GHCR + re-pin digest** (shared unblocker; overdue).
2. **Linux** (document Docker/compose + native CLI + systemd) and **Start9** (adapt
   the seeder s9pk, test on returned-feline.local) — both wrap the image, high reuse.
3. **macOS/Windows technical path** (document Docker Desktop + CLI).
4. **Desktop tray app** — separate follow-on (its own proposal; signing/notarization).
Then: multi-source / multi-host.

## Resolved (Tim, 2026-07-18)

- **Start9 distribution: SIDELOAD for now.** Ship the s9pk as a sideload, not a
  community-registry publish, for v1. "Done" for Start9 = a working sideloadable
  s9pk verified on returned-feline.local; registry publish is a later step.
- **Linux binary: DOCKER for now.** No bundled single-file binary in v1 — the
  Docker/compose path (+ the `bin` CLI for technical users) is enough. Revisit only
  if demand shows.
- **Password UX off-Umbrel: GENERATE-AND-PRINT on first run.** If a non-loopback
  bind is requested with no `PEARTUNE_PASSWORD` set, the host generates a password on
  first run and prints it (persisting it to the data dir), rather than aborting. A
  loopback-only bind stays password-free; an explicitly-set password always wins.
  This keeps `requireSafeBind`'s fail-closed guarantee (a LAN-exposed dashboard is
  never unauthenticated) while making a bare NAS/Linux install work without an
  Umbrel/Start9 platform to mint `${APP_PASSWORD}`.
- **Registry/versioning: confirmed.** Standard tag/version scheme for the multi-arch
  GHCR image; the existing `umbrel/` listing publishes against the fresh digest.