docs: update README files to include new environment variables and local files feature

This commit is contained in:
2026-03-16 02:29:42 +01:00
parent e805028d46
commit abcf872d2d
3 changed files with 130 additions and 28 deletions

View File

@@ -70,4 +70,5 @@ docker compose -f compose.yml -f compose.traefik.yml up -d
| `NEXT_PUBLIC_API_URL` | frontend build arg | Baked in at build time — must point to the public backend URL | | `NEXT_PUBLIC_API_URL` | frontend build arg | Baked in at build time — must point to the public backend URL |
| `API_URL` | frontend runtime env | Server-side only (Next.js API routes). Set in compose. | | `API_URL` | frontend runtime env | Server-side only (Next.js API routes). Set in compose. |
| `DATABASE_URL` | backend | `sqlite:///app/data/k-tv.db` or postgres DSN | | `DATABASE_URL` | backend | `sqlite:///app/data/k-tv.db` or postgres DSN |
| `SESSION_SECRET` | backend | Change in production | | `JWT_SECRET` | backend | JWT signing key — change in production (min 32 chars) |
| `COOKIE_SECRET` | backend | OIDC state cookie encryption key — change in production (min 64 chars) |

View File

@@ -11,7 +11,7 @@ The backend is a Cargo workspace with three crates following Hexagonal (Ports &
``` ```
k-tv-backend/ k-tv-backend/
├── domain/ # Pure business logic — no I/O, no frameworks ├── domain/ # Pure business logic — no I/O, no frameworks
├── infra/ # Adapters: SQLite/Postgres repositories, Jellyfin HTTP client ├── infra/ # Adapters: SQLite/Postgres repositories, Jellyfin HTTP client, local files
└── api/ # Axum HTTP server — routes, DTOs, startup wiring └── api/ # Axum HTTP server — routes, DTOs, startup wiring
``` ```
@@ -79,11 +79,20 @@ OIDC state (CSRF token, PKCE verifier, nonce) is stored in a short-lived encrypt
If Jellyfin variables are not set, the server starts normally but schedule generation endpoints return an error. Channel CRUD and auth still work. If Jellyfin variables are not set, the server starts normally but schedule generation endpoints return an error. Channel CRUD and auth still work.
### Local Files (optional — requires `local-files` feature)
| Variable | Default | Description |
|----------|---------|-------------|
| `LOCAL_FILES_DIR` | — | Absolute path to local video library root. Enables the local-files provider when set. |
| `TRANSCODE_DIR` | — | Directory for FFmpeg HLS transcode cache. Enables transcoding when set. |
| `TRANSCODE_CLEANUP_TTL_HOURS` | `24` | Hours after last access before a transcode cache entry is deleted. |
### CORS & Production ### CORS & Production
| Variable | Default | Description | | Variable | Default | Description |
|----------|---------|-------------| |----------|---------|-------------|
| `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins | | `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins |
| `BASE_URL` | `http://localhost:3000` | Public base URL used to build stream URLs for local files |
| `PRODUCTION` | `false` | Enforces minimum secret lengths when `true` | | `PRODUCTION` | `false` | Enforces minimum secret lengths when `true` |
## Feature Flags ## Feature Flags
@@ -100,6 +109,7 @@ default = ["sqlite", "auth-jwt", "jellyfin"]
| `auth-jwt` | JWT Bearer token authentication | | `auth-jwt` | JWT Bearer token authentication |
| `auth-oidc` | OpenID Connect integration | | `auth-oidc` | OpenID Connect integration |
| `jellyfin` | Jellyfin media provider adapter | | `jellyfin` | Jellyfin media provider adapter |
| `local-files` | Local filesystem media provider with optional FFmpeg transcoding |
## API Reference ## API Reference
@@ -137,11 +147,49 @@ All endpoints are under `/api/v1/`. Endpoints marked **Bearer** require an `Auth
| `GET` | `/channels/:id/epg?from=&until=` | Bearer | EPG slots overlapping a time window (RFC3339 datetimes) | | `GET` | `/channels/:id/epg?from=&until=` | Bearer | EPG slots overlapping a time window (RFC3339 datetimes) |
| `GET` | `/channels/:id/stream` | Bearer | `307` redirect to the current item's stream URL — `204` if no-signal | | `GET` | `/channels/:id/stream` | Bearer | `307` redirect to the current item's stream URL — `204` if no-signal |
### Other ### Library
All endpoints require Bearer auth and return `501 Not Implemented` if the active provider lacks the relevant capability.
| Method | Path | Auth | Description | | Method | Path | Auth | Description |
|--------|------|------|-------------| |--------|------|------|-------------|
| `GET` | `/config` | — | Server configuration flags | | `GET` | `/library/collections` | Bearer | List media collections/libraries |
| `GET` | `/library/series` | Bearer | List TV series (supports `?collection=`, `?provider=`) |
| `GET` | `/library/genres` | Bearer | List genres (supports `?type=`, `?provider=`) |
| `GET` | `/library/items` | Bearer | Search/filter media items (supports `?q=`, `?type=`, `?series[]=`, `?collection=`, `?limit=`, `?strategy=`, `?provider=`) |
### Files (local-files feature only)
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/files/stream/:id` | — | Range-header video streaming for local files |
| `POST` | `/files/rescan` | Bearer | Trigger library rescan, returns `{ items_found }` |
| `GET` | `/files/transcode/:id/playlist.m3u8` | — | HLS transcode playlist |
| `GET` | `/files/transcode/:id/:segment` | — | HLS transcode segment |
| `GET` | `/files/transcode-settings` | Bearer | Get transcode settings (`cleanup_ttl_hours`) |
| `PUT` | `/files/transcode-settings` | Bearer | Update transcode settings |
| `GET` | `/files/transcode-stats` | Bearer | Cache stats `{ cache_size_bytes, item_count }` |
| `DELETE` | `/files/transcode-cache` | Bearer | Clear the transcode cache |
### IPTV
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/iptv/playlist.m3u` | `?token=` | M3U playlist of all channels |
| `GET` | `/iptv/epg.xml` | `?token=` | XMLTV EPG for all channels |
### Admin
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/admin/logs` | `?token=` | SSE stream of live server log lines (`{ level, target, message, timestamp }`) |
| `GET` | `/admin/activity` | Bearer | Recent 50 in-app activity events |
### Config
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| `GET` | `/config` | — | Server configuration flags and provider capabilities |
## Examples ## Examples
@@ -267,6 +315,21 @@ curl -s -I http://localhost:3000/api/v1/channels/<id>/stream \
### Channel ### Channel
A named broadcast channel owned by a user. Holds a `schedule_config` (the programming template) and a `recycle_policy`. A named broadcast channel owned by a user. Holds a `schedule_config` (the programming template) and a `recycle_policy`.
Channel fields:
| Field | Description |
|-------|-------------|
| `access_mode` | `public` / `password_protected` / `account_required` / `owner_only` |
| `access_password` | Hashed password when `access_mode` is `password_protected` |
| `logo` | URL or inline SVG for the watermark overlay |
| `logo_position` | `top_right` (default) / `top_left` / `bottom_left` / `bottom_right` |
| `logo_opacity` | 0.01.0, default 1.0 |
| `auto_schedule` | When `true`, the server auto-regenerates the schedule when it expires |
| `webhook_url` | HTTP endpoint called on domain events |
| `webhook_poll_interval_secs` | Polling interval for webhook delivery |
| `webhook_body_template` | Handlebars template for the webhook POST body |
| `webhook_headers` | JSON object of extra HTTP headers sent with webhooks |
### ScheduleConfig ### ScheduleConfig
The shareable programming template: an ordered list of `ProgrammingBlock`s. Channels do not need to cover all 24 hours — gaps are valid and produce a no-signal state. The shareable programming template: an ordered list of `ProgrammingBlock`s. Channels do not need to cover all 24 hours — gaps are valid and produce a no-signal state.
@@ -286,6 +349,8 @@ Provider-agnostic filter used by algorithmic blocks:
| `tags` | Provider tag strings | | `tags` | Provider tag strings |
| `min_duration_secs` / `max_duration_secs` | Duration bounds for item selection | | `min_duration_secs` / `max_duration_secs` | Duration bounds for item selection |
| `collections` | Abstract groupings (Jellyfin library IDs, Plex sections, folder paths, etc.) | | `collections` | Abstract groupings (Jellyfin library IDs, Plex sections, folder paths, etc.) |
| `series_names` | List of TV series names (OR-combined) |
| `search_term` | Free-text search term for library browsing |
### FillStrategy ### FillStrategy
How an algorithmic block fills its time budget: How an algorithmic block fills its time budget:
@@ -305,6 +370,22 @@ Controls when previously aired items become eligible again:
| `cooldown_generations` | Don't replay within this many schedule generations | | `cooldown_generations` | Don't replay within this many schedule generations |
| `min_available_ratio` | Always keep at least this fraction (0.01.0) of the matching pool selectable, even if their cooldown hasn't expired. Prevents small libraries from running dry. | | `min_available_ratio` | Always keep at least this fraction (0.01.0) of the matching pool selectable, even if their cooldown hasn't expired. Prevents small libraries from running dry. |
### ProviderCapabilities
`GET /config` returns `providers[]` with per-provider capabilities. Library endpoints return `501` if the active provider lacks the relevant capability.
| Capability | Description |
|------------|-------------|
| `collections` | Provider can list/filter by collections |
| `series` | Provider exposes TV series groupings |
| `genres` | Provider exposes genre metadata |
| `tags` | Provider supports tag filtering |
| `decade` | Provider supports decade filtering |
| `search` | Provider supports free-text search |
| `streaming_protocol` | `hls` or `direct_file` |
| `rescan` | Provider supports triggering a library rescan |
| `transcode` | FFmpeg transcoding is available (local-files only) |
### No-signal state ### No-signal state
`GET /channels/:id/now` and `GET /channels/:id/stream` return `204 No Content` when the current time falls in a gap between blocks. The frontend should display static / noise in this case — matching the broadcast TV experience. `GET /channels/:id/now` and `GET /channels/:id/stream` return `204 No Content` when the current time falls in a gap between blocks. The frontend should display static / noise in this case — matching the broadcast TV experience.
@@ -338,6 +419,9 @@ cargo build -F sqlite,auth-jwt,auth-oidc,jellyfin
# PostgreSQL variant # PostgreSQL variant
cargo build --no-default-features -F postgres,auth-jwt,jellyfin cargo build --no-default-features -F postgres,auth-jwt,jellyfin
# With local files + transcoding
cargo build -F sqlite,auth-jwt,jellyfin,local-files
``` ```
### Docker ### Docker
@@ -357,7 +441,8 @@ k-tv-backend/
│ │ # ScheduledSlot, MediaItem, PlaybackRecord, User, ... │ │ # ScheduledSlot, MediaItem, PlaybackRecord, User, ...
│ ├── value_objects.rs # MediaFilter, FillStrategy, RecyclePolicy, │ ├── value_objects.rs # MediaFilter, FillStrategy, RecyclePolicy,
│ │ # MediaItemId, ContentType, Email, ... │ │ # MediaItemId, ContentType, Email, ...
│ ├── ports.rs # IMediaProvider trait │ ├── ports.rs # IMediaProvider trait, ProviderCapabilities
│ ├── events.rs # Domain event types
│ ├── repositories.rs # ChannelRepository, ScheduleRepository, UserRepository │ ├── repositories.rs # ChannelRepository, ScheduleRepository, UserRepository
│ ├── services.rs # ChannelService, ScheduleEngineService, UserService │ ├── services.rs # ChannelService, ScheduleEngineService, UserService
│ └── errors.rs # DomainError │ └── errors.rs # DomainError
@@ -366,7 +451,9 @@ k-tv-backend/
│ ├── channel_repository.rs # SQLite + Postgres ChannelRepository adapters │ ├── channel_repository.rs # SQLite + Postgres ChannelRepository adapters
│ ├── schedule_repository.rs # SQLite + Postgres ScheduleRepository adapters │ ├── schedule_repository.rs # SQLite + Postgres ScheduleRepository adapters
│ ├── user_repository.rs # SQLite + Postgres UserRepository adapters │ ├── user_repository.rs # SQLite + Postgres UserRepository adapters
│ ├── activity_log_repository/ # Activity log persistence
│ ├── jellyfin.rs # Jellyfin IMediaProvider adapter │ ├── jellyfin.rs # Jellyfin IMediaProvider adapter
│ ├── local_files/ # Local filesystem provider + FFmpeg transcoder
│ ├── auth/ │ ├── auth/
│ │ ├── jwt.rs # JWT create + validate │ │ ├── jwt.rs # JWT create + validate
│ │ └── oidc.rs # OIDC flow (stateless cookie state) │ │ └── oidc.rs # OIDC flow (stateless cookie state)
@@ -376,13 +463,22 @@ k-tv-backend/
├── api/src/ ├── api/src/
│ ├── routes/ │ ├── routes/
│ │ ├── auth.rs # /auth/* endpoints │ │ ├── auth.rs # /auth/* endpoints
│ │ ├── channels.rs # /channels/* endpoints (CRUD, EPG, broadcast) │ │ ├── channels/ # /channels/* endpoints (CRUD, EPG, broadcast)
│ │ ── config.rs # /config endpoint │ │ ── admin.rs # /admin/logs (SSE), /admin/activity
│ │ ├── config.rs # /config endpoint
│ │ ├── files.rs # /files/* endpoints (local-files feature)
│ │ ├── iptv.rs # /iptv/playlist.m3u, /iptv/epg.xml
│ │ └── library.rs # /library/* endpoints
│ ├── config.rs # Config::from_env() │ ├── config.rs # Config::from_env()
│ ├── state.rs # AppState │ ├── state.rs # AppState
│ ├── extractors.rs # CurrentUser (JWT Bearer extractor) │ ├── extractors.rs # CurrentUser (JWT Bearer extractor)
│ ├── error.rs # ApiError → HTTP status mapping │ ├── error.rs # ApiError → HTTP status mapping
│ ├── dto.rs # All request + response types │ ├── dto.rs # All request + response types
│ ├── events.rs # SSE event broadcasting
│ ├── log_layer.rs # Tracing layer → SSE log stream
│ ├── poller.rs # Webhook polling task
│ ├── scheduler.rs # Auto-schedule renewal task
│ ├── webhook.rs # Webhook delivery
│ └── main.rs # Startup wiring │ └── main.rs # Startup wiring
├── migrations_sqlite/ ├── migrations_sqlite/

View File

@@ -1,36 +1,41 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). # K-TV Frontend
## Getting Started Next.js 16 / React 19 EPG viewer and channel manager for [k-tv](../README.md) — the self-hosted linear TV orchestration backend.
First, run the development server: ## Quick Start
```bash ```bash
npm run dev cp .env.local.example .env.local
# or # Edit .env.local — set NEXT_PUBLIC_API_URL to your backend URL
yarn dev
# or
pnpm dev pnpm dev
# or
bun dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Open `http://localhost:3001` in your browser.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. ## Environment Variables
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. | Variable | Where | Description |
|----------|-------|-------------|
| `NEXT_PUBLIC_API_URL` | build arg + runtime | Browser-side API base URL. **Baked in at build time** — must point to the public backend URL. Default: `http://localhost:4000/api/v1` |
| `API_URL` | runtime only | Server-side API URL for Next.js API routes (e.g. stream redirect resolver). Falls back to `NEXT_PUBLIC_API_URL` if not set. Use this to set a private backend address in Docker. |
## Learn More ## Routes
To learn more about Next.js, take a look at the following resources: | Path | Auth | Description |
|------|------|-------------|
| `/tv` | public | TV player — EPG grid, channel switching, HLS/direct video stream |
| `/dashboard` | required | Channel management — create, edit, configure schedule blocks |
| `/admin` | required | Live server log stream + recent activity log |
| `/login` | — | Email/password login |
| `/register` | — | New account registration |
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. ## Architecture
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - **`lib/api.ts`** — typed fetch client wrapping all backend endpoints (`api.auth.*`, `api.channels.*`, `api.schedule.*`, `api.library.*`)
- **`hooks/`** — TanStack Query v5 hooks for all data fetching and mutations; components never fetch directly
- **`context/auth-context.tsx`** — JWT stored in localStorage, `isLoaded` flag prevents flash redirects
- Components are props-only; all business logic lives in hooks
## Deploy on Vercel ## Docker
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. See the [root README](../README.md) for build and deploy instructions.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.