244 lines
12 KiB
Markdown
244 lines
12 KiB
Markdown
# Operations
|
|
|
|
How to build, install, configure, and run every component. Schemas referenced here
|
|
are detailed in [data-model.md](./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)
|
|
|
|
```sh
|
|
./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)
|
|
|
|
```sh
|
|
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)
|
|
|
|
```sh
|
|
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 |
|
|
|
|
All per-OS logic (OS detection, destination, asset name) lives in ONE place —
|
|
`tools/platform.sh` — sourced by `build-install.sh`, `release.sh`, and
|
|
`update.sh`; run standalone (curl-piped, no checkout), `update.sh` fetches that
|
|
same file from the forge's raw endpoint, so the logic is never duplicated.
|
|
|
|
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
|
|
|
|
```sh
|
|
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.
|
|
- **Roku (ECP)** — transport control of the stick's own playback (play/pause,
|
|
jump-back, exit, now-playing) over its unauthenticated LAN REST on port 8060;
|
|
discoverable via SSDP (`ST: roku:ecp`). Deliberately NOT a library playback
|
|
destination — a Roku can't open NFS paths; that's the planned dev channel
|
|
(see roadmap).
|
|
|
|
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). Devices with a configured `restart`
|
|
command template (black by default) get a **Restart service** menu action that
|
|
hard-restarts the host-side player (`black-tv restart`: relaunch the mpv unit,
|
|
resuming the live playlist/position; clean teardown when idle/hung).
|
|
|
|
A **"not up to date"** badge flags a stale helper deploy: the device's `stats`
|
|
report carries the sha256 of the helper script it runs (`helper_sha`), and the
|
|
app compares it against the repo's vendored copy
|
|
(`mcp/src/blacktv/black-tv.sh`) — a mismatch (or a pre-stamp helper that
|
|
reports nothing) means the app may be speaking verbs the device doesn't know.
|
|
No badge appears when freshness can't be judged (device unreachable, unknown
|
|
helper, or no repo checkout).
|
|
|
|
**Update & restart service** (menu + expanded summary) is the in-app fix: the
|
|
app base64-pushes the vendored script over the device's own SSH channel,
|
|
installs it atop the deployed bin (`sudo install` — atomic), verifies the
|
|
landed sha256, then restarts the player service through the fresh script. The
|
|
manual `mcp/README.md` deploy step remains for hosts the app can't reach.
|
|
|
|
Each row expands (chevron) into a **summary section**: backend + endpoints,
|
|
role/fleet class + services, live connection/playback line, host load
|
|
(1/5/15-min + mpv decode CPU), and the helper-deployment line (deployed vs
|
|
repo hash, judged) — with the Restart / Update & restart buttons inline, so
|
|
diagnosing and fixing a wedged device happens in one place.
|
|
|
|
## governor (`portable-net-tv`)
|
|
|
|
TypeScript/Bun standalone daemon on plum.
|
|
|
|
```sh
|
|
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](./data-model.md#configjson--governor-portable-net-tv).
|
|
|
|
### Fleet engine (`portable-net-tv fleet …`)
|
|
|
|
```sh
|
|
portable-net-tv fleet status # registry + duties + probed capacity + warnings
|
|
portable-net-tv fleet duties # assign duties; Δ-log changes since last run
|
|
portable-net-tv fleet custody # floor-check every title; print re-pin plans
|
|
portable-net-tv fleet repin [--apply] # execute the re-pin plans (rsync holder→target
|
|
# on the target via ssh; dry-run by default;
|
|
# targets need ssh + mediaRoot in fleet.json)
|
|
portable-net-tv fleet reaper [--apply] # classify healthy|stalled|dead; --apply = safe
|
|
# nudges only (reannounce/verify)
|
|
portable-net-tv fleet peers <q> # peers_for(infohash|title), provenance-tagged
|
|
portable-net-tv fleet probe # ssh df + reachability → rolling capacity state
|
|
# (feeds custody disk-eligibility + status)
|
|
portable-net-tv fleet daemon [--apply-nudges] [--interval-min=N]
|
|
# periodic tick (default 10 min): duties Δ →
|
|
# floor check → reaper; for launchd
|
|
portable-net-tv fleet serve [--port=N] [--token=T]
|
|
# HTTP service (default :9094, $FLEET_TOKEN):
|
|
# /health /registry /custody /reaper
|
|
# /peers_for/<infohash>[?title=]
|
|
```
|
|
|
|
All subcommands take `--json`. Reads the fleet registry from
|
|
`~/.config/tv-anarchy/fleet.json` (array form; `devices.json` fallback) — see
|
|
[data-model.md](./data-model.md#fleetjson--the-fleet-registry-app-side--governor-policy).
|
|
Read-only by default: mutation only behind `--apply`/`--apply-nudges`
|
|
(`repin --apply` rsyncs + records the new holding; reaper nudges are idempotent
|
|
transmission ops; mesh recoveries and re-searches remain printed plans).
|
|
Engine state under `~/.local/state/tv-anarchy/`: `fleet-state.json` (CLI duty
|
|
diffs), `fleet-daemon-state.json` (daemon duty diffs), `fleet-probe-state.json`
|
|
(rolling capacity), `fleet-holdings.json` (recorded re-pins).
|
|
Tests: `bun test` in `governor/`.
|
|
|
|
## mcp (`plum-control-mcp`)
|
|
|
|
TypeScript/Bun. Serves both an MCP stdio server (for Claude) and the CLI bridge
|
|
the app shells out to.
|
|
|
|
```sh
|
|
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.
|
|
|
|
```sh
|
|
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.
|