Skip to content

Repository files navigation

TownLight Station

TownLight Station is a locally installed Windows broadcast appliance for LPM and PEG operations. It is being built as one traceable product: station control, deterministic media execution, operator workflows, installation, recovery, distribution, and proof all ship from this repository.

The current foundation provides a versioned station-profile API, strict commissioning validation, SQLite persistence in WAL mode, and a native Windows service. It is the first vertical slice of the station control plane, not a feature-complete release.

The repository also contains a supervised isolated channel worker, a length-framed typed command/event protocol, a durable per-channel event journal, and the first machine-proven persistent media graph. The station runtime launches and handshakes the real worker over a private named pipe, validates every identity and sequence, enforces response deadlines, and guarantees process cleanup. The GStreamer graph keeps its encoder, MPEG-TS mux, and UDP output alive while switching raw fallback and program sources. Unknown major versions, stale commands, corrupted journals, malformed frames, missed deadlines, and MPEG-TS continuity errors fail visibly.

Architecture

  • Rust owns the station control plane and isolated media workers.
  • A single station service owns authoritative SQLite writes.
  • One persistent GStreamer worker per enabled channel owns media timing and output.
  • React and TypeScript will provide operator and resident web interfaces.
  • The appliance remains locally operable when optional cloud services are unavailable.

See the architecture overview for boundaries and non-negotiable design rules.

Requirements

  • Windows 11 or Windows Server 2022 or newer
  • Rust 1.97.1 for development
  • GStreamer 1.28.6 MSVC x64, development install, for media-engine development and tests

Install GStreamer from the official Windows download and put its bin directory first in PATH before building. The runtime currently uses the SQLite library included with Windows and does not require a separately installed database server. The product installer will carry the selected GStreamer runtime rather than asking station operators to configure a development SDK.

Quick start

  1. Install Rust and the official GStreamer 1.28.6 MSVC x64 development runtime.
  2. Clone this repository.
  3. Run cargo test --offline --workspace.
  4. Run cargo run -p stationd -- station.db 127.0.0.1:4070.
  5. Open http://127.0.0.1:4070/health.

stationd service --database <absolute-path> --address 127.0.0.1:<port> is the Service Control Manager entry point used by installation infrastructure. Service mode refuses relative database paths and non-loopback listeners, reports startup and shutdown state to Windows, and handles both requested stops and operating-system shutdown cooperatively. It is not intended to be launched directly from an operator terminal.

Installer

installer\build-installer.ps1 builds one elevated x64 setup executable in dist. It accepts only the pinned official GStreamer 1.28.6 runtime hash, embeds stationd, channel-worker, the private media runtime, notices, and an immutable source commit, and refuses a dirty release worktree. -AllowDirty exists only for local packaging tests.

Installation is health-gated before Inno Setup commits the product registration: the installer stages immutable binaries, installs the private runtime, registers a delayed-auto LocalSystem service with restart recovery, starts it, receives the local readiness response, and writes a candidate receipt. Any failure before formal installation removes the staged service, runtime, and binaries and returns nonzero. Uninstall removes application/runtime files and the service but deliberately preserves station data beneath %ProgramData%\TownLight Station.

Run installer\verify-installed.ps1 after installation to compare installed hashes and receipts with the candidate manifest and to prove the service, health endpoint, and required media factories. Development candidates are not code-signed; a trusted Windows signing identity and timestamp are mandatory release gates before external distribution.

API

GET /health reports whether the local database is ready.

PUT /api/v1/station validates and stores the singleton station profile:

{
  "station_id": "3f5f721f-96c7-48b1-b061-1bf1ad1e62c2",
  "display_name": "KTLT Community Television",
  "timezone": "America/Denver",
  "expected_revision": 0
}

The response includes the new revision. Send that value as expected_revision on the next update; a stale update receives 409 revision_conflict instead of overwriting another operator's work.

GET /api/v1/station returns the commissioned profile and revision, or a typed not_commissioned error.

The loopback control plane also exposes PUT/GET /api/v1/channels, PUT /api/v1/assets, PUT /api/v1/schedule/items, POST /api/v1/schedule/prepare, POST /api/v1/schedule/commit, channel schedule listing, and commit-report lookup. Preparation actively reports overlaps, missing/unready/short media, and the nearest gap. Approval reruns that gate under a SQLite write lock, then atomically stores the operator approval and changes the item from draft to committed. The daemon launches one supervised worker per enabled channel and advances a report through pending, queued, acknowledged, and completed only after durable worker acknowledgments for load, take, and fallback return. A restarted worker reconciles queued and on-air database state by reloading and, when still within the scheduled window, retaking the asset.

Channel worker

channel-worker <worker-id> <channel-id> <journal-path> <pipe-name> <udp-destination> connects to the station service through its private duplex Windows named pipe and owns the channel media graph. It reports Ready only after fallback output starts, and reports ShutdownComplete only after the graph stops. The pipe is restricted to the creating user, Administrators, and SYSTEM, rejects remote clients, and permits only one server instance. LoadAsset verifies the asset's content identity and decodability before adding it to the running graph; TakeAsset and ReturnToSchedule perform bounded source transitions. Every accepted load and on-air change is synchronized to the worker journal before acknowledgement. Live transitions remain explicitly rejected.

Media engine

station-media-assets probes actual streams and finite duration, rejects non-media or incomplete A/V, copies accepted bytes atomically into a SHA-256-addressed library, revalidates the stored object, and makes duplicate imports idempotent. station-media-engine can then prepare and transactionally replace the decoded program leg while fallback remains live. File pads are aligned to the running clock; both selectors feed one continuous segment; and sample-rate adjusters enforce canonical video/audio timelines before the persistent OpenH264/AAC encoders and MPEG-TS output. The integration proof generates and ingests two distinct real A/V files, airs both in sequence with fallback between them, and rejects transport errors, discontinuities, counter jumps, backward PTS, excessive A/V divergence, or unsafe decoder teardown. A second process-level proof drives an approved timed item through the real worker and twice restarts that worker to prove state reconciliation. Live inputs, production profiles, and sink fanout are not claimed yet.

Development

Run formatting, linting, and tests before committing:

cargo fmt --check
cargo clippy --offline --all-targets --all-features -- -D warnings
cargo test --offline --workspace

See CONTRIBUTING.md for the proof-first workflow and LICENSE for terms.

About

TownLight Station — a locally installed Windows broadcast appliance for LPM and PEG operations.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages