This repo is a monorepo for the broader Chiba signage system plus Chiba Cable: a “TV guide + channels” experience designed to run as a kiosk on Raspberry Pis.
If you only care about Cable, start here: cable/ and scripts/pis/.
- Guide UI (
cable/apps/guide): a React/Vite app that renders the grid, plays programs, and supports kiosk-style “gallery” autoplay. - Server (
cable/apps/server): an Express server that builds the index from TOML channel manifests, serves media (/media,/cache,/stash), hosts special low-data routes like/weatherstar, and serves the built Guide + Ops UI. - Channel configs (
cable/config/channels/*.toml): define what each channel is and what it plays. - Launch profiles (
cable/config/profiles/*.tomlorscripts/pis/modes/*.toml): define kiosk URL params per-Pi (gallery mode, pinned channel, QR on/off, etc). - Pi tooling (
scripts/pis/*): bootstrap, update, prefetch stash media, apply launch profiles.
cable/config/chiba.toml: Cable server config (library roots, channel manifest dir, scheduling params).cable/config/channels/*.toml: Channel manifests (id/number/name/programs/embed/audio).cable/config/profiles/*.toml: “What to show” launch profiles for the fleet (composable with inventory).scripts/pis/registry.toml: Fleet inventory (hosts, node names, orientation/rotation).scripts/pis/modes/*.toml: Mode/profile files in the same shape ascable/config/profiles/*.toml(often install-specific templates).
Responsibilities:
- Build and serve the guide index at
GET /api/index. - Provide a WebSocket endpoint (
/ws) used by the guide for live updates and remote control plumbing. - Serve local media via
GET /media/...(restricted to configured library roots). - Serve cached remote media via
GET /cache/:id?url=...(downloads and caches, size-limited). - Serve stash-cached NAS media via
GET /stash/:id?path=...("fast 404 if not cached", optional background warming). - Prefetch stash media via
POST /api/stash/prefetch(used before gallery playlist installs). - Serve embed helpers via
GET /embed/:id(iframe/proxy pages driven by channel[embed]config). - Serve “special channels” like
/weatherstar(plus/weatherstar.jpg),/village(plus/village.jpgand/village/live), and/mars. - Host built UIs from
cable/apps/guide/distandcable/apps/ops/dist(Ops UI is served under/ops).
Config location:
- The server reads
CHIBA_CONFIGif set. - Otherwise it prefers
cable/config/chiba.tomland falls back toconfig/chiba.tomlat repo root.
Useful env knobs (not exhaustive):
PORT(default8787)CHIBA_MEDIA_CACHE_DIR(default./media-cacheat repo root)CHIBA_MEDIA_CACHE_MAX_BYTES(default1GiB)CHIBA_STASH_AUTOFETCH(controls background warming behavior for stash cache misses)
Responsibilities:
- Renders the TV guide grid from
GET /api/index. - Plays a program URL as
image,video,audio, oriframe(everything else, including server routes like/weatherstaror/embed/...). - Supports gallery mode (kiosk/ambient behavior):
- In gallery mode,
gallery=1enables autoplay into a pinned channel. - In gallery mode,
playlist=1turns a channel’s programs into a looping playlist (unique URLs). - Gallery playlist mode has special handling for
/stash/...items so cold caches don’t cause rapid “skip loops”.
Ops UI intended for fleet visibility and control. The server also has a set of ops endpoints and helpers (cable/apps/server/src/ops-*) and serves the built Ops UI under /ops.
A small Vite app for generative visuals. Typically used as a source URL for a channel program (embedded as an iframe).
Key sections:
[server]sets host/port (server defaults to8787).[library] roots = [...]are allowed media roots for/mediaand/stash.[channels] manifest_dir = "channels"points atcable/config/channels/.
Each channel TOML is parsed by the server and becomes a row in the guide index.
Common fields:
id,number,name,call_sign,accent,description- Optional audio bed uses
audio_source = { type = "path" | "url", value = "...", cache = true|false }plusaudio_volumeand optional offset fields. - Programs are repeated
[[program]]entries (the loader also accepts[[programs]]). - Program media uses
source = { type = "path" | "url", value = "...", cache = true|false }. - If a program
sourceistype="path", it becomes/media/...or/stash/...(whencache=true). - If a program
sourceistype="url", it can be used directly or wrapped through/cache/...(whencache=true). - Optional
[embed]can render a proxy/iframe page at/embed/<id>(dismiss selectors, overlays, etc).
These define how each Pi should launch the Cable Guide (they map to query params on the guide URL).
Schema:
[defaults.cable]applies to all Pis.[pis.<pi-id>.cable]overrides for a single Pi.
The canonical key list is derived from code:
- Guide params:
cable/apps/guide/src/constants/params.ts - Profile keys and mapping:
cable/apps/server/src/ops-apply-mode.ts - Generated reference:
node scripts/pis/print-launch-options.mjs
Important keys:
mode = "gallery"emitsgallery=1channel = "weatherstar"(or"137") emitschannel=...playlist = trueemitsplaylist=1nosplash = trueemitsnosplash=1lock = true|falseemitslock=1or (in gallery mode)lock=0qr = falseemitsqr=0theme,scale,text_scale,hours
Extra key used by Pi scripts (not a guide query param):
prefetch_channels = ["earl", ...]is consumed byscripts/pis/prefetch-stash.sh.
Inventory is the source of truth for:
host,ip,node_nameorientation,display_rotateguide_port(defaults to5173if not specified)
Profiles are composable with inventory: inventory describes hardware/addressing, profiles describe “what to show”.
Use the ops CLI in the server package:
pnpm -C cable/apps/server ops:apply-mode -- \
--inventory scripts/pis/registry.toml \
--mode cable/config/profiles/default.toml \
--allWhat it does:
- Builds a kiosk URL like
http://localhost:5173/?screenId=<node_name>&gallery=1&channel=... - POSTs it to each Pi’s Node API on port
8080(POST /kiosk-url) - Uses per-node API keys via env (see
scripts/pis/pis-secrets.env.example)
PI_PASSWORD=interact ./scripts/pis/apply-cable-launch.sh \
--inventory scripts/pis/registry.toml \
--mode cable/config/profiles/default.toml \
--allThis merges inventory + profile on your machine, then SSHes into each Pi to update the kiosk launch URL (and best-effort handle portrait rotation when configured in inventory).
- The guide has a “remote” view (separate UI) and uses the server WebSocket endpoint (
/ws) to move selection, open programs, and drive app-specific controls. - Protocol notes live in
cable/REMOTE_PROTOCOL.md.
Core scripts:
scripts/pis/bootstrap.shrsyncs the repo to a Pi, sets up services, and sets an initial kiosk URL.scripts/pis/fast-cable-update.shsyncs config/guide/server changes quickly without a full bootstrap.scripts/pis/prefetch-stash.shwarms NAS-backed (/stash) media on a Pi viaPOST /api/stash/prefetch.scripts/pis/print-launch-options.mjsprints a generated reference for guide query params and profile keys.
Run the Cable stack locally:
pnpm dev:cableOr individually:
pnpm dev:cable:server
pnpm dev:cable:guide
pnpm dev:cable:ops
pnpm dev:cable:genartBuild:
pnpm build
pnpm build:cable:guide
pnpm build:cable:ops