# Devices

**Job:** Register every machine in your TVAnarchy install — what it plays, what
it stores, how it connects — without saying “fleet” in the product UI.

One-liner: *Which boxes exist, what duty they run, how storage pools compose.*

**Replaces (product language):** “fleet” → **Devices** + **install identity**.
Implementation may keep `fleet.json`, `MeshJoin`, governor `fleet/*` until
rename migration.

---

## Scope

| In | Out |
|----|-----|
| Device registry (`devices.json`) | Library catalog semantics (Library pillar) |
| Playback targets (VLC, mpv, Roku, …) + device-optimized builds/packages | Torrent search UI (Download) |
| Device type presets (laptop, storage, …) | Net edition merge logic |
| Service flags (`stream`, `custody`, `offlineCache`, …) | Raw WireGuard config (operator) |
| Mesh join / install pairing UX | `peers_for` implementation (Download) |
| **Storage pools** — spare capacity on a laptop extends a storage device | Full cloud SAN vendor integrations |
| Shared media volume placement | Metadata enrichment (Library) |

---

## User surfaces

| Surface | Role |
|---------|------|
| Devices tab | List, edit, add devices |
| Device edit | Kind, hostname, services, **storage contribution** |
| This Mac | Local device view — offline policy, cache dir |
| Settings → Devices | Mesh join, VPN, install identity |
| Settings → Storage pools | Compose shared media drive (planned) |

iOS: Settings bridge URL, Join install (today: JoinFleetView → **Join install**).

---

## Device types (unchanged enum, renamed context)

| Type | Typical role |
|------|----------------|
| `laptop` | Play locally; contribute extension storage; offline warmup |
| `storage` | Canonical media root (the always-on host) |
| `cellphone` | Stream + small local cache |
| `seed` | Public swarm face + custody |
| `broadcast` | Mesh anchor, registry (one per install) |

**Install** = one operator identity (today: one `fleet.json` row set). Devices
are members of an install, not “the fleet” in UI copy.

---

## Device Services

`DeviceServices` are the granular capability flags on each device (in `devices.json`). `DeviceType` supplies a sensible default preset; every flag is independently overridable in the Devices tab editor.

| Flag (`DeviceServices`) | Description | Actuated today? | Maps to governor duty |
|-------------------------|-------------|-----------------|-----------------------|
| `stream` | Device is eligible as a playback target (has a player backend). | Yes (`PlayerController` routing, targets) | — |
| `offlineCache` | Device pulls recent episodes (next-Y of most-recent-Z shows) to local disk for offline use. | Yes (local players on laptop/cellphone) | — |
| `ttlSeed` | Device seeds titles with a short TTL while actively playing them (contributes to availability while watching). | Planned | — |
| `custody` | Device holds copies toward the N-replica custody floor for wanted titles. | Planned | `custody_floor` |
| `publicSwarmFace` | Device acts as the public swarm face (contacts DHT/public trackers on behalf of the install; keeps home IPs dark). | Planned | `public_swarm_face` |
| `f2fRelay` | Device relays friend-to-friend (F2F) requests and media bytes across the mesh. | Planned | `f2f_relay` |
| `meshAnchor` | The single install-wide anchor (broadcast duty): holds the aggregated registry, anchors F2F rendezvous, runs Discord bridge, etc. | Planned (exactly one per install) | `broadcast` |

`stream` and `custody` are independent (a storage node can both hold the canonical copies *and* serve local playback).

---

## Example Devices (DeviceType presets + services)

The five `DeviceType` values are convenience presets. Real installs mix them and override services per device.

| Type | Fleet class (governor) | Default Services | Example hardware / role (generic) |
|------|------------------------|------------------|-----------------------------------|
| `cellphone` | consumer | `stream`, `offlineCache` | Phone/tablet: primarily streams from central host; keeps small local cache for offline. |
| `laptop` | roamer | `stream`, `offlineCache`, `ttlSeed` | Roaming client machine: full local playback + cache + TTL-seeds what it watches. |
| `storage` | server | `stream`, `custody` | Always-on central storage host (canonical media root). Often also streams to local players. |
| `seed` | seedbox | `custody`, `publicSwarmFace` | Off-home or dedicated public node: provides custody floor + public swarm face (hides the rest of the install). |
| `broadcast` | broadcast | `publicSwarmFace`, `f2fRelay`, `meshAnchor` | The single always-on mesh anchor per install. Registry, F2F rendezvous, public face duties, Discord bridge, etc. |

In the UI: pick a Type for the preset, then toggle individual service checkboxes. Governor duties (`fleet/*` engine, `assignDuties`) compute actual duties from the resulting flags + reachability / alwaysOn / public_ip.

---

## Supported Playback Backends and Extensibility

The combination of `HostKind` + concrete implementations of `PlayerTarget` (and `MediaLaunchable` / `Enqueueable` where applicable) determines how a device can be *controlled* for playback. These are orthogonal to the `DeviceType` roles (governor class mapping) above.

Current backends:

- mpv over SSH (MpvTarget — full MediaLaunchable + Enqueueable)
- VLC over HTTP (VLCTarget — full MediaLaunchable + Enqueueable)
- Local QuickTime on macOS (QuickTimeTarget)
- Roku via ECP (RokuTarget — transport control + now-playing only; *not* MediaLaunchable because a Roku cannot open our files directly today)

Raspberry Pi (or any headless Linux box):

- Fits existing types: `storage` or `seed` (if it provides a media root, transmission, or custody) or `laptop` (if used for local playback).
- For control: configure with `mpv-ipc` or `vlc` HostKind. The corresponding target works if mpv/VLC is running and reachable over SSH or HTTP.
- No dedicated "rpi" preset or Kodi integration — use the generic backends.

Roku (full library playback):

- Partial today: ECP gives transport control over whatever the Roku is playing (its own Netflix etc. channels).
- Full "send TVAnarchy library content to the Roku" requires the planned control-plane HTTP daemon on the always-on host (HTTP media streams) + a future Roku dev channel. See the universal client north star.

Chromecast / Google Cast:

- Not supported. No Cast protocol implementation (no equivalent of RokuTarget or CastTarget).
- Current workaround: Play on a supported host (central mpv/VLC) that itself can cast, or use the future HTTP media plane from the always-on host (Chromecasts are excellent at receiving plain HTTP streams or HLS).
- Would be straightforward to add later as a new `HostKind` + `CastTarget` implementing `PlayerTarget` + `MediaLaunchable` (using mDNS discovery + the Cast protocol).

Other smart TVs (LG webOS, Samsung Tizen, Sony, etc.), Apple TV (AirPlay or tvOS APIs), Kodi/LibreELEC boxes, generic DLNA/UPnP renderers:

- Not explicitly supported today.
- The system is designed for extension: add a new `HostKind` case + a type that conforms to `PlayerTarget` (and `MediaLaunchable` if it can launch library files). The Devices tab editor and `PlayerController` will pick it up.
- With the planned always-on HTTP daemon + media plane, many of these become "dumb" receivers that just need a URL — reducing the need for per-device native protocol implementations.

The Devices pillar's primary goals are **fleet role modeling and duty assignment** (the 5 DeviceTypes + 7 services that drive custody, swarm faces, mesh, etc.). Playback backends are a related but secondary concern and are intentionally extensible via protocols in `PlayerTarget.swift` and `MediaLaunchable.swift`.

## Additional Architectural Improvements from Prior Art (WatchTV-UWU)

The other TV project (WatchTV-UWU, deep in the always-on host's last Linux backup at applications/src/@uwuapps/watchtv/ – a sophisticated monorepo TS/Node network media progress tracker + progressive streamer) offers more improvements to mine for v2 completeness:

- **Monorepo packages for TS components** (governor/mcp/host daemon): Extract core/database (SQLite + schema for state/progress), media-utils (scanner/organization/titles/quality), config, types, api-client, service-discovery, utils. Aligns with BTD faces and pillar separation while DRY. See their packages/ (core, database, media-utils, service-discovery) and apps/ (orchestrator, codec-service, media-manager).

- **Progressive streaming/transcoding architecture** (directly for the host daemon /media): On-demand HLS segments (10s), just-in-time FFmpeg (only as watched), adaptive quality, buffering, cleanup, options for codec/bitrate. Efficient for central host serving to RPi/Roku/Chrome cast/etc. thin receivers. Mine their STREAMING_ARCHITECTURE.md, codec-service, StreamManager.

- **Layered + event-driven design**: UI/Controllers/Services/Data Access separation, event bus for real-time (e.g., duties/net updates, progress sync via Net pillar). Shared types across interfaces. Their UI_ARCHITECTURE.md and controller patterns.

- **Advanced organization + monitoring**: Quality-aware (fits torrent variants + Net quality), duplicates, safe transactional moves/rollback (for Devices re-pin), caching, activity logging/alerts/analytics. See ORGANIZATION_SYSTEM_SUMMARY.md and related services.

- **SQLite for queryable state** (complements our jsonl): For titles, progress/watch history, merged views. Enhanced schema with indexes, multi-user support.

- **TUI/CLI + web with shared backend**: For tools (fleet/net/devices status). Their TUI (blessed), CLI (commander), optional web.

These enhance pillar DRY (shared media core across Library/Net/Devices/Watch via the host daemon), host daemon efficiency (key for "thin Watch" and optimized pkgs for roku/rpi/etc.), Net consumers (e.g., quality/ progress facts), and builds (streaming makes RPi/Roku pkgs powerful).

Cross-ref: v2/plan.md Appendix D (full mining details + how to incorporate). Adopt in phases: streaming in the host daemon (7), org in Library (1b/parallel), packages in TS (ongoing for Net/Devices).

Update pillar checklist for new modules to consider these patterns.

See also:
- `HostKind` and `DeviceType` in `DeviceConfig.swift`
- Concrete targets: `VLCTarget.swift`, `MpvTarget.swift`, `RokuTarget.swift`, `QuickTimeTarget.swift`
- Future: control-plane HTTP daemon on the always-on host (thins Watch, enables more thin receivers)

## Builds and Optimized Packages

Current release artifacts (defined in `tools/platform.sh`, produced/cut via `tools/release.sh` on the mac build box, fetched by `tools/update.sh` on all nodes):

| Target | Asset | Architectures | Notes |
|--------|-------|---------------|-------|
| macOS | `TVAnarchy-$TAG.zip` | universal (x86_64 + arm64) | Full .app bundle |
| Linux | `TVAnarchy-$TAG-linux-$ARCH.tar.gz` | x86_64, aarch64 (and others via uname -m) | Installs to /opt or ~/.local/opt |
| Windows | `TVAnarchy-$TAG-windows-$ARCH.zip` | x86_64, aarch64 | Per-user install |
| Android | `TVAnarchy-$TAG-android.apk` | universal (all ABIs) | Handed to package installer (Termux bridge for storage) |

iOS companion: separate Xcode build (TVAnarchyiOS scheme using VLCKit), distributed via TestFlight/direct/sideload (no auto-update asset in the same Forgejo channel). 

**Why iOS is handled separately today (unlike Android or the planned Roku channel):**
- It's a full native companion app (Sources/TVAnarchyiOS/), not the same Swift client as macOS. Uses VLCKit for in-app playback + the mcp bridge (HTTP :8787 on an intermediate host) for library/downloads/remote control.
- No shell environment on iOS for the `update.sh` / platform.sh flow that works for mac/linux/windows/android. Install is manual or TestFlight.
- Apple distribution friction: code signing, provisioning profiles (free ones expire every 7 days — see `tools/deploy-phone.sh`), device registration/UDIDs, no generic binary drop.
- Role: Fits `cellphone` DeviceType — primarily a remote/stream client ("registry" entry + bridge consumer), not a primary "always-on" or heavy storage node.
- Contrast with Android: APK fits the asset model and can be "installed" via Termux or Files app.
- Future unification (north star): Once the control-plane HTTP daemon (host daemon) is live, iOS becomes another wrapped target via Capacitor (same TS/HTML thin client as Android/web). The native Swift iOS app gets retired at parity, like the plan for the macOS UI. See `docs/roadmap.md` "Client decision".

This is why the platform logic explicitly calls out "iOS via Xcode/TestFlight" as the exception (see `tools/update.sh:57` and `tools/platform.sh`). The *device role* itself (`cellphone`) is not special — it's the client software packaging and distribution that is, for the reasons above.

**Decision: produce optimized, small-footprint packages for device-specific targets (Roku, RPi, etc.).**

These pair with the Devices pillar roles and the future control-plane HTTP daemon:

- **Roku channel package**: `TVAnarchy-$TAG-roku.zip` (or .tar of the channel dir). Self-contained BrightScript + SceneGraph for direct sideload via the Roku dev channel mechanism. Provides native-feeling living-room UI (browse, search, queue, control) that talks *only* to the always-on host's HTTP endpoints (library, media streams, player commands). Reuses any future thin TS client logic if we extract a shared web core. This is the path to full Roku library playback (beyond today's ECP transport control only).

- **RPi / embedded ARM**: Optimized Linux packages `TVAnarchy-$TAG-linux-arm64.tar.gz` (primary) and armv7l variant. 
  - Static linking / vendored minimal media stack (ffmpeg, etc.) for reliable low-power operation.
  - Suitable for `storage` (media root + transmission), `seed` (public face + custody), or client playback nodes.
  - Easy one-line install for Raspberry Pi OS, LibreELEC, DietPi, or as a headless host daemon consumer.
  - Smaller footprint than full desktop Linux builds; pre-tuned for 24/7 media duties or as extension tier.

- **etc / future device-optimized**:
  - Thin web/PWA bundle (static JS/TS/HTML) for the universal client vision. Consumable directly in any browser, or wrapped (Tauri for additional desktops, Capacitor for more mobiles). This becomes the default for "any device with a screen + network + video tag" once the control-plane daemon provides the media + API plane. Enables Roku channel (webview or native bridge), smart TVs with browsers, additional embedded boxes, etc.
  - Other embedded: e.g. specific smart TV sideload packages, Kodi addon, or generic HTTP media client for boxes that just need a stream URL from the central host.

**Implementation notes**:
- Extend `tools/platform.sh` with new `tva_os` cases or build targets (`roku`, `rpi-arm64`, `web`) and `tva_asset_name`.
- `tools/release.sh` (or additional CI/build scripts) will need target-specific builders: BrightScript packager for Roku, cross-compile or QEMU for ARM, Vite/TS bundler for web.
- The single-source platform logic ensures `update.sh` (and future per-device updaters) can pull the right optimized asset.
- Aligns with the host daemon / Phase 7: once the always-on host speaks clean HTTP for everything, the "client" packages can be much thinner and more device-specific.
- Update `v2/manifest.json`, `correlation/components.md`, and the release tooling docs when these ship.
- Keeps Apple platforms on the native Swift client (best-in-class VLCKit offline) while the rest of the fleet gets purpose-built, low-overhead packages.

This gives first-class support for the living-room (Roku) and embedded/low-power (RPi etc.) devices that the Devices pillar is designed to model, without bloating the main client.

---

## BitTorrentDrive (internal face)

## BitTorrentDrive (internal face)

Devices owns **within-install** byte placement. The engine is
[BitTorrentDrive](../packages/bittorrentdrive.md) **internal face** — same
package Net uses externally for editions, different ACL and payloads.

| Devices product | Drive operation |
|-----------------|-----------------|
| Extension warmup | `pinInternal` → extension tier |
| Shared media volume | `Manifest` + tier map |
| Re-pin / custody move | `pinInternal` + `unpin` (policy from Download) |
| Path resolve for Player | `resolveNearest` |

Net pillar uses **external face** (`publishEdition` / `subscribeEdition`).

## Storage pools & shared media drive (planned)

See [devices-storage.md](./devices-storage.md) for the full design. Summary:

- Multiple **storage devices** join one **logical media volume**.
- A laptop’s spare disk is an **extension tier** — not a second offline cache
  with duplicates.
- **Extended warmup** places files on the extension tier while you watch; the
  library path **aliases** to the nearest copy (hardlink, bind mount, or
  content-addressed slot).
- Retrieval optimizer prefers: **local extension → canonical storage → stream**.

```
┌─────────────────┐     extension pool      ┌──────────────────┐
│  central host   │◄──── (spare GB on client) ──│  client machine  │
│  (storage)      │     shared namespace      │  (laptop)        │
│  canonical tier │                           │  watching Show X │
└─────────────────┘                           └──────────────────┘
         │                                              │
         └────────── Library catalog (one path) ────────┘
```

---

## Core modules (today)

| Module | Path |
|--------|------|
| `DeviceConfig`, `DevicesConfig` | `DeviceConfig.swift` |
| `DeviceType`, `DeviceServices`, `HostKind` | same |
| `OfflineCachePolicy` | per-device; moves under Devices pillar |
| `MeshJoin`, `FleetState` | `Mesh/` — rename UX to “install” |
| `DisplayService` | playback display pick (cross Watch + Devices) |

Governor: `governor/src/fleet/*` — custody, re-pin, probe disk. Devices pillar
**consumes** custody plans; Download pillar **actuates** re-pin.

---

## State

| Artifact | Path |
|----------|------|
| Device registry | `~/.config/tv-anarchy/devices.json` |
| Install registry | `~/.config/tv-anarchy/fleet.json` *(rename → `install.json` planned)* |
| Engine state | `~/.local/state/tv-anarchy/fleet-state.json` |
| Storage pool map | `~/.local/state/tv-anarchy/devices/storage-pools.json` *(planned)* |
| Placement index | `~/.local/state/tv-anarchy/media-placement.json` *(planned)* |

---

## Settings (Devices pillar)

| Setting | Where |
|---------|-------|
| Per-device offline policy | `devices.json` → `offlinePolicy` |
| Mesh join token / QR | Settings → Devices |
| VPN profiles | Keychain + app support |
| Extension pool enable + quota | Settings → Devices → Storage *(planned)* |
| Custody / seed service toggles | Devices tab edit |

---

## v2 phases

| Phase | Deliverable |
|-------|-------------|
| Doc + UI copy | “Install” / “Devices” not “fleet” in product strings |
| Settings section | Devices block in SetupView |
| Storage pools spec | [devices-storage.md](./devices-storage.md) |
| Placement daemon | Governor or `DevicesStorageController` |
| Extended warmup alias | Replace duplicate rsync offline dir |

---

## Net / Download touchpoints

| Pillar | Interaction |
|--------|-------------|
| Download | `custody`, `re-pin`, `mediaRoot` on storage devices |
| Watch | `PlayerController` picks target device |
| Library | Index reads **logical** paths from shared volume |
| Net | Observations tagged with `installId` + `deviceId` |