Surface the existing pin (keep-from-cull) and per-file delete actions as visible inline buttons on each offline cache row instead of context-menu-only: a star toggles protection from auto-cull (and restore-if-missing), a trash culls that file early. Aligns wording/icons to the star metaphor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
19 KiB
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:
storageorseed(if it provides a media root, transmission, or custody) orlaptop(if used for local playback). - For control: configure with
mpv-ipcorvlcHostKind. 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+CastTargetimplementingPlayerTarget+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
HostKindcase + a type that conforms toPlayerTarget(andMediaLaunchableif it can launch library files). The Devices tab editor andPlayerControllerwill 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:
HostKindandDeviceTypeinDeviceConfig.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
cellphoneDeviceType — 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.shwith newtva_oscases or build targets (roku,rpi-arm64,web) andtva_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 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 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 |
| 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 |