tv-anarchy/docs/operations.md
Natalie 8f12f470b7 feat(metadata): add local llm title refiner integration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 21:10:47 -07:00

8.1 KiB

Operations

How to build, install, configure, and run every component. Schemas referenced here are detailed in data-model.md.

The app

Distribution is two-tier: dev builds are local (built on the build box, installed locally, never published); releases are durable and published to forge.black (Forgejo), where any node installs/updates from them without a toolchain or source.

Build & install — dev (the build box, plum)

./build-install.sh

Stamps build identity (git SHA / time → BuildStamp.swift), runs xcodegen, builds Release into build/dd, and installs TVAnarchy.app to the OS-appropriate location: /Applications when writable (the standard macOS location — admin users, no sudo), else ~/Applications (Apple's per-user fallback for non-admin accounts). TVANARCHY_DEST overrides; non-macOS fails loud. A stale copy at the other candidate location is removed on install so two builds can't drift. The one irreducible manual step is quit + relaunch — a running native app can't be hot-swapped. The sidebar build stamp (v<ver> · <sha> · <time>) makes a stale copy obvious. (Set TVANARCHY_DD=path to relocate the derived data; tools/release.sh uses this to build into a throwaway tmp dir.)

Cut a release → forge.black (plum)

tools/release.sh [vX.Y.Z]          # tag defaults to v<MARKETING_VERSION>

Builds into an ephemeral tmp dir, refuses a dirty tree, tags + pushes over forge-wg, then creates a Forgejo release on lilith/tv-anarchy and uploads the zipped .app. Needs a write token: FORGEJO_TOKEN env or ~/.config/tv-anarchy/forgejo-token (chmod 600) — minted at Forgejo → Settings → Applications → Tokens (scope write:repository). Bump MARKETING_VERSION in project.yml first; the tag must not already exist.

Install / update — any other node (no toolchain or source)

tools/update.sh [--force]          # first run installs; later runs no-op if current

Pulls the latest release from forge.black (FORGEJO_API defaults to the mesh-stable overlay http://10.9.0.4:3000), picks the asset for this platform, compares versions, and swaps it into the OS-appropriate destination (TVANARCHY_DEST overrides everywhere):

OS Release asset Destination
macOS TVAnarchy-<tag>.zip /Applications (admin) else ~/Applications; Gatekeeper quarantine stripped; old-location copy migrated away
Ubuntu (classic Linux) TVAnarchy-<tag>-linux-<arch>.tar.gz /opt/tv-anarchy when /opt is writable, else ~/.local/opt/tv-anarchy
Bluefin (immutable/ostree Linux) same as Linux always ~/.local/opt/tv-anarchy (/usr is read-only; detected via /run/ostree-booted)
Windows (Git Bash/MSYS) TVAnarchy-<tag>-windows-<arch>.zip %LOCALAPPDATA%\Programs\TVAnarchy (per-user, no elevation)
Android (Termux) TVAnarchy-<tag>-android.apk (all ABIs, no arch suffix) APK → ~/storage/downloads, handed to the system package installer via termux-open (user confirms the prompt — shells can't install packages on Android by design). Detected before generic Linux (Termux's uname says Linux). Version = handoff stamp, since Termux can't query installed packages
iOS not via this script (no shell): build the TVAnarchyiOS scheme in Xcode onto the device, or TestFlight/sideload

Version compare: macOS reads the bundle plist; Linux/Windows read the .release-tag stamp the script writes on install. A release that lacks this platform's asset fails loud with the exact missing name and the published list — today only the macOS asset is published (cut on plum); Linux/Windows entries activate the moment a release carries their assets. A read token is needed only if the repo is private. Then quit/restart the app.

Unsigned local build: fine across your own Macs (the quarantine strip handles Gatekeeper). Distributing to other people's machines would want real signing/notarization — a later piece.

Build manually / in Xcode

brew install xcodegen          # if needed
xcodegen generate              # project.yml → TVAnarchy.xcodeproj (generated, git-ignored)
xcodebuild -scheme TVAnarchy -destination 'platform=macOS' build
# or open TVAnarchy.xcodeproj and Run

project.yml is the source of truth; the .xcodeproj is generated. Local dev is unsigned (no sandbox/hardened runtime — the app needs Process/ssh and localhost+overlay networking).

Config & state locations

File Purpose
~/.config/tv-anarchy/devices.json Device registry + playback targets (migrates from hosts.json, then ~/.config/plumtv/hosts.json)
~/.local/state/tv-anarchy/settings.json App settings (adult gating, previews, media keys, offline cache)
~/.config/portable-net-tv/config.json Governor config and the VLC HTTP password source
~/.local/state/plum-control-mcp/watched.jsonl Shared watch log
~/.local/state/tv-anarchy/library.json Cached library snapshot
~/.local/state/tv-anarchy/meta/ Metadata sidecars (path-digest keyed)
$VLC_HTTP_PASSWORD Alternate VLC password source

Playback targets

  • Plum VLC — enable VLC's HTTP/Lua interface (Preferences → All → Interface → Main interfaces → Lua), set the password (read from the governor config or $VLC_HTTP_PASSWORD). App talks to http://127.0.0.1:8080/requests/….
  • Black (mpv) — mpv is driven straight to the DRM console (no X) over SSH JSON IPC; the commands templates delegate launch/library/stats/teardown to /usr/local/bin/black-tv. Endpoints try LAN (10.0.0.11) then the WG overlay (10.9.0.4) because the LAN address flaps.
  • QuickTime — local, zero-install; no setup.

Devices are editable in-app (Devices tab: add/edit/delete, make-active, set type/services, reload, reset, reveal devices.json). The list shows a per-device system-load badge (low/med/high).

governor (portable-net-tv)

TypeScript/Bun standalone daemon on plum.

cd governor
bun install
portable-net-tv watch     # daemon: follow VLC, log watches, prefetch next N eps
portable-net-tv next      # print per-show progress

Runs as a launchd background agent (Apple Events to VLC are blocked there, which is why it reads VLC over HTTP, not AppleScript). Config: see data-model.md.

mcp (plum-control-mcp)

TypeScript/Bun. Serves both an MCP stdio server (for Claude) and the CLI bridge the app shells out to.

cd mcp
bun install
bun run typecheck
claude mcp add plum-control       # register the MCP server with Claude Code

Requirements/constraints:

  • macOS only (NSScreen via osascript-jxa; reads org.videolan.vlc.plist).
  • VLC must be running with the Web interface enabled.
  • Torrent search needs FlareSolverr on localhost:8191 (TPB/Nyaa/1337x).
  • media_list_shows scans MEDIA_ROOTS (default ~/media) for SxxEyy files.
  • Black-TV watch state is black-local (/usr/local/share/black-tv/), read over SSH, never written to plum/NFS.

recommender

Python (managed with uv). Invoked by the app's EnrichService during indexing/enrichment; not on the playback path.

cd recommender
uv run python -m media_rec.enrich "<title>" [year] [--category <cat>]
uv run python recommend_local.py     # keyless local recommendations (preferred)
uv run python recommend.py           # TMDB-backed (needs an API key)

Provider routing by category: anime → AniList, tv/cartoons → TVmaze, fallback → TMDB; degrades gracefully on missing keys/failures. registry.md is a generated snapshot of black's library used as the offline title source.

Black-side artifacts

  • Prebuilt index: /bigdisk/_/media/_tools/index.tsv (TSV size⇥mtime⇥path), fetched by LibraryIndex via one SSH cat; rebuilt with nice/ionice because black seeds 200+ torrents.
  • Registry: black's .registry.md dump → recommender/registry.md.

Verification after changes

  • App: ./build-install.sh, relaunch, confirm the build stamp updated.
  • governor/mcp: bun run typecheck in each package.
  • recommender: run an enrich for a known title and confirm JSON output.