Sign inSign up

purestream711/nostalgiatv-server

By purestream711

Updated 5 days ago

NostalgiaTV server - 30-day schedules, web UI, HDHR tuner, Plex integration

Image
Content management system
0

10K+

purestream711/nostalgiatv-server repository overview

NostalgiaTV Docker Server

A self-hosted companion server for the NostalgiaTV Android app. Generates 30-day schedules, indexes your Plex, Jellyfin, or Emby content, manages channels and commercials, and exposes a browser-based Watch and Configure UI — plus an HDHR/IPTV tuner so other apps (Plex DVR, Channels DVR, Jellyfin, TiviMate, etc.) can tune in too.

Built for amd64 and arm64 — PCs, servers, Raspberry Pi 4/5, Synology/QNAP NAS, Apple Silicon Macs.

What you get

Web UI — open http://<server>:19850 in any browser
  • /watch — Live TV guide with an inline player, plus On Demand, Movies, and TV Shows browsing. Sign in with Plex, Jellyfin, Emby, or a local account.
  • /configure — admin tabs: Channels, Commercials, Groups, Schedule, Design, Profile Configuration, Servers, Admin (External Sources, Automatic Backup, Logs).
Media servers
  • Plex, Jellyfin, and Emby — connect any mix of them, as many as you like, and pick which libraries feed the guide per server (Servers tab).
  • Cross-server duplicate hiding — when the same movie or episode lives on more than one server (e.g. mid-migration), only one copy shows up. Matched by provider ID (TMDB/IMDb/TVDB) for movies and by show + season + episode for episodes; each server carries a preference rank.
  • Content sync — movies, shows, episodes, music, collections, and labels, indexed incrementally and self-healing on deletion.
  • Local folders — the Android app can scan local files on the device. In Docker mode, mirror folders (offline copies of your server library) match against the Docker server's library; local-only libraries are Android-only.
Channels
  • Your libraries — default channels plus custom channels built from libraries, collections, playlists, genres, decades, studios, labels, or hand-picked items, with per-channel weights, time slots, restricted hours, and exclusions.
  • Plex Live TV and Jellyfin / Emby Live TV — your DVR/tuner channels pulled into the lineup with their EPG, playable as live streams.
  • External Sources — import third-party M3U / XMLTV / HDHR lineups (other tuners, IPTV providers), editable and refreshable from the browser.
  • Live Stream channels — point a channel at a stream URL or resolver; finite videos are broadcast-clock aligned so every device lands at the same position.
  • Music "Tune Choice" channels — genre radio stations with a themed visualizer in the Watch UI.
  • Weather — the built-in WeatherStar 4000 channel and the native Storm Channel, which falls back to a deterministic offline weather simulation (season-correct temps, real sunrise/sunset, accurate moon phases) when there's no internet.
  • Trailers ("What's On") — a channel of trailers for what's airing.
  • Content Groups — reusable named sets of content you can drop into channels.
Schedule
  • 30-day generation — algorithms: RANDOM, CYCLIC_SHUFFLE, BLOCK_SHUFFLE, BLOCK_CYCLIC.
  • Marathon insertion, premiere assignment for recently-added items, commercial padding, mature/kid filtering, recently-scheduled tracking.
  • Per-channel refresh — regenerate one channel's schedule without touching the rest.
Commercials
  • Commercial groups, break sequences (bumper → commercial → …), padding, and mid-program breaks.
  • Sources: a whole library, a collection, a playlist, specific items, a music genre, or trailers — and any source can come from any backend, so a Plex bumper and a Jellyfin commercial can share one break.
  • Mid-break modes: HALFWAY, CHAPTERS (from the item's own server), and SILENCE — real ffmpeg silence detection, which runs on the Docker server only (too CPU-heavy for a TV stick).
Everything else
  • Multi-profile support — separate channel configs, schedules, themes, and settings per profile (kids, adult, guest, etc.), switchable from both UIs.
  • Themes + Design tab — 8 built-in themes (Retro, Vintage, Premium, Rad-Lad, Classic, Modern, Cable Box, Midnight Blue) plus custom themes, fonts, and CRT/bezel effects, all editable from the browser.
  • Watch UI playback — commercials and break screens, trailer pre-rolls, subtitle and audio-track selection, scrubbing, resume / Continue Watching, and CRT effects with bezels.
  • Config import/export — full settings transfer, per-profile or whole-server, and multi-backend aware (server identities travel; tokens never do).
  • Automatic Backup — weekly snapshots of the full config, rotated, restorable in one click from the Admin tab.
  • Authentication — Plex / Jellyfin / Emby / local sign-in for the Watch UI, a local username+password for Configure, and API-key auth for the Android app.
HDHR / IPTV tuner emulator (enabled by default)

Exposes your whole channel lineup to anything that speaks HDHR:

  • GET /discover.json, /lineup.json, /device.xml, /lineup_status.json
  • GET /channels.m3u (M3U playlist) and GET /xmltv.xml (EPG)
  • GET /stream/{channelId} for tuner playback

Quick Start

Create a folder, save this as docker-compose.yml inside it:

services:
  nostalgiatv:
    image: purestream711/nostalgiatv-server:latest
    container_name: nostalgiatv-server
    ports:
      - "19850:19850"
    volumes:
      - ./data:/app/data
      - ./config:/app/config
      - ./logos:/app/logos
    environment:
      - TZ=America/New_York
      # Run as your host user so the bind-mounted folders above stay writable
      # (find yours with `id -u` / `id -g`). See "Volumes & permissions" below.
      - PUID=1000
      - PGID=1000
    restart: unless-stopped

Then start it:

docker compose up -d

Or pull and run directly, no compose file:

docker pull purestream711/nostalgiatv-server:latest
docker run -d \
  --name nostalgiatv-server \
  -p 19850:19850 \
  -v "$(pwd)/data:/app/data" \
  -v "$(pwd)/config:/app/config" \
  -v "$(pwd)/logos:/app/logos" \
  -e TZ=America/New_York \
  -e PUID=1000 \
  -e PGID=1000 \
  --restart unless-stopped \
  purestream711/nostalgiatv-server:latest

Then open http://localhost:19850 (or http://<your-server-ip>:19850 from another device) and sign in with Plex, Jellyfin, or Emby.

Setup

  1. Start the container.
  2. Open http://<server>:19850 — you'll land on the Watch UI's sign-in screen.
  3. Pick your provider — Plex (account sign-in), Jellyfin, or Emby (server URL + username + password). You can connect more than one before continuing, so a mixed Plex + Jellyfin setup onboards in a single pass.
  4. Choose which libraries feed the guide, let the content index build, and create your first profile.
  5. Click the mode switcher in the top bar to jump to /configure and adjust channels, commercials, profiles, themes, etc.
From the Android app
  1. Start the container.
  2. Open NostalgiaTV on your Android device.
  3. Go to Settings → Admin → Docker Mode (Pro feature).
  4. Enter your server URL (e.g., http://192.168.1.100:19850).
  5. The app registers with the server and offloads schedule generation and content indexing to Docker. All connected devices share the same schedule.
As an HDHR tuner (Plex DVR, Channels DVR, Jellyfin, etc.)

The tuner is enabled by default. In your DVR app, point it at:

  • Tuner URL: http://<server>:19850
  • M3U playlist: http://<server>:19850/channels.m3u
  • EPG (XMLTV): http://<server>:19850/xmltv.xml

To disable the tuner, set HDHR_ENABLED=false.

Environment Variables

VariableDefaultDescription
TZAmerica/New_YorkTimezone (affects schedule generation and EPG display)
PUID1000Host user ID the server runs as, so files it writes to the bind mounts are owned by you. id -u to find yours
PGID1000Host group ID the server runs as. id -g to find yours
SCHEDULE_HOURS720Hours of schedule to generate ahead (720 = 30 days). Supported range 24–2160 (90 days)
SCHEDULE_REFRESH_INTERVAL21600Seconds between scheduler ticks (21600 = 6 hours)
SCHEDULE_REGEN_THRESHOLD_HOURS168Regenerate a channel when its remaining schedule falls below this many hours (168 = 7 days)
PLEX_CONTENT_POLL_INTERVAL_SECONDS300How often to probe your servers for content adds/removals (5 minutes)
HDHR_ENABLEDtrueWhether the HDHR tuner emulator is on
WEATHER_API_KEY(bundled key)WeatherAPI.com key for the native Storm Channel. Defaults to the same key the Android app ships with — override to use your own
LOG_LEVELINFOLogging level — DEBUG, INFO, WARNING, ERROR

SCHEDULE_HOURS, SCHEDULE_REFRESH_INTERVAL, and HDHR_ENABLED can also be changed at runtime via POST /api/settings, which persists them to config/server_settings.json (they then override the env vars on restart).

Volumes

PathPurpose
/app/dataSQLite database, content index, registered devices, automatic backups, persistent state
/app/configProfiles, channel overrides, custom channels, themes, server settings
/app/logosCustom channel logos uploaded via the UI

Back up data/ and config/ together — they're a matched pair.

Volumes & permissions

The container starts as root, chowns data/, config/, and logos/ to PUID:PGID, then drops to that unprivileged user before running — so a first run on a native-Linux host (Synology, QNAP, Portainer, Dockhand, Raspberry Pi) works even though the Docker daemon creates those bind-mount folders as root. Set PUID/PGID to your host user (id -u / id -g) if it isn't the default 1000:1000, and the database and config will be created and owned correctly with no manual chown. Docker Desktop on Windows/macOS handles bind-mount ownership through its own file-sharing layer and generally works regardless of PUID/PGID.

API

The full API is documented at http://<server>:19850/docs (FastAPI auto-generated, ~230 endpoints across status, channels, schedule, profiles, servers, design, commercials, groups, content, HDHR, Live TV, trailers, weather, logs, backups, and config import/export).

Key endpoints for orientation:

  • GET /api/status — health check and server state
  • POST /api/register — Android device registration (returns API key)
  • POST /api/register/jellyfin — first-run bootstrap for a Jellyfin/Emby-only install
  • GET /api/schedule — bulk schedule download for app playback
  • GET/POST /api/servers/{id}/libraries — backend-neutral per-server library selection
  • GET /api/config/export and POST /api/config/import — full config transfer
  • GET /discover.json, /lineup.json, /channels.m3u, /xmltv.xml, /stream/{id} — HDHR/IPTV

All /api/* endpoints (except /api/status, /api/register*, and /api/auth/*) require either an X-API-Key header or an authenticated browser session. HDHR endpoints are intentionally unauthenticated so legacy tuner apps can reach them.

Updating

docker compose pull
docker compose up -d

Or with plain Docker:

docker pull purestream711/nostalgiatv-server:latest
docker stop nostalgiatv-server && docker rm nostalgiatv-server
# re-run your `docker run` command

Your data/, config/, and logos/ volumes are preserved.

Development

Run from source instead of the published image:

docker compose -f docker-compose.dev.yml up -d --build

Or without Docker:

cd docker
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 19850 --reload

Image Publishing

The latest image is published manually with a multi-arch buildx push — this is the only publish path. It reuses a persistent buildx instance named ntv-builder to avoid accumulating stray builders in Docker Desktop:

docker login -u purestream711
docker buildx use ntv-builder 2>/dev/null || \
  docker buildx create --name ntv-builder --driver docker-container --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t purestream711/nostalgiatv-server:latest \
  --push .

The Docker Hub overview page is not linked to a source repo, so it does not auto-sync from this README — update the repository description manually (or via the Hub API) after changing this file.

Tag summary

Content type

Image

Digest

sha256:2578bfe71

Size

232.2 MB

Last updated

5 days ago

docker pull purestream711/nostalgiatv-server