Persistent Stop & Source Blacklist
Persistent Stop & Source Blacklist
Two operator affordances make the cockpit deterministic: a Stop that stays stopped, and a blacklist that keeps a bad stream gone.
Persistent Stop (master kill switch)
Hitting Stop in the cockpit (POST /api/stream/stop) does three things:
- Sets
stream.operator_stopped: truein the config YAML (persisted). - Kills the managed ffmpeg encode.
- Causes both background scrapers to idle on their next tick.
While operator_stopped is true:
- The watchdog will not auto-restart the exited process
(
should_watchdog_restart_exited_processreturnsfalse, andwatchdog_loopshort-circuits). - The private-IPTV loop skips its refresh, so it cannot re-select sources and auto-start ffmpeg.
- The public auto-scraper skips discovery (existing red tiles are retained).
- On a full service/host restart, boot honours the persisted flag and stays down.
The stop is cleared only by an explicit Start (/api/stream/start) or
Restart (/api/stream/restart). No process-lifecycle side effect resets it —
notably, start_managed_process no longer touches the desired state, so a
watchdog- or scraper-initiated start can never silently re-arm a stopped stream.
The cockpit header shows a “STOPPED (manual)” banner and relabels Start as
Resume whenever the flag is set. GET /api/status exposes it at
runtime.operator_stopped and config.stream.operator_stopped.
operator_stoppedis managed solely by the start/stop/restart endpoints. Do not hand-edit it in the YAML expecting the scrapers to react mid-event; use the endpoints so the in-memory desired state stays consistent.
Source blacklist
source_blacklist is a persisted list that permanently blocks a stream so it can
never be re-selected by a scraper or shown to viewers. Each entry:
source_blacklist:
- url: https://example.com/known-bad/live.m3u8
id: private-iptv-slate-feed
label: Always-on slate feed
channel: UFC 24/7
reason: dead/slate feed
added_at: 0
Matching
A source matches a blacklist entry by any of:
- Normalized URL (scheme+host lowercased, trailing slash dropped).
- URL without its query string — so a block survives CDN token rotation on the same path.
- id (exact).
- channel or label (case-insensitive).
Where it is enforced (defense in depth)
The blacklist is checked at every point a source can enter or be shown:
| Stage | Function | Effect |
|---|---|---|
| Private-IPTV selection | select_private_iptv_candidates |
Blocked entries dropped before the expensive ffprobe stage |
| Ingest write-barrier | merge_private_iptv_sources |
Blocked feeds never written into stream.sources |
| Public inventory | public_stream_inventory |
Blocked manual/auto tiles hidden from viewers |
| Public auto-scrape | _run_auto_scrape |
Blocked scraped URLs never stored |
| Manual add | add_public_stream |
Rejects a blacklisted URL with 409 |
| Managed status | source_statuses |
Blocked ingest sources filtered from the cockpit list |
Managing it
- Block: the Block button on any managed source or public tile, or
POST /api/blacklistwith any of{url, id, label, channel, reason}. Blocking also immediately strips matching entries frompublic_sourcesandstream.sources. - Unblock: the Blacklist panel’s Unblock button, or
POST /api/blacklist/removewith{url}or{id}. - List:
GET /api/blacklist, or readconfig.source_blacklistfrom/api/status.
Because the private-IPTV merge rebuilds the auto list from scratch each cycle, the blacklist — not a one-off remove — is what makes a block stick.