feat(docs): enhance documentation with Docker deployment and local files provider sections

This commit is contained in:
2026-03-14 04:10:57 +01:00
parent cf92cc49c2
commit f7f4d92376
3 changed files with 355 additions and 4 deletions

View File

@@ -121,13 +121,17 @@ const TOC = [
{ id: "requirements", label: "Requirements" },
{ id: "backend-setup", label: "Backend setup" },
{ id: "frontend-setup", label: "Frontend setup" },
{ id: "docker", label: "Docker deployment" },
{ id: "jellyfin", label: "Connecting Jellyfin" },
{ id: "local-files", label: "Local files" },
{ id: "first-channel", label: "Your first channel" },
{ id: "blocks", label: "Programming blocks" },
{ id: "filters", label: "Filters reference" },
{ id: "strategies", label: "Fill strategies" },
{ id: "recycle-policy", label: "Recycle policy" },
{ id: "import-export", label: "Import & export" },
{ id: "iptv", label: "IPTV export" },
{ id: "channel-password", label: "Channel passwords" },
{ id: "tv-page", label: "Watching TV" },
{ id: "troubleshooting", label: "Troubleshooting" },
];
@@ -359,6 +363,77 @@ npm run dev`}</Pre>
</Note>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="docker">
<H2>Docker deployment</H2>
<P>
The recommended way to run K-TV in production is with Docker Compose.
The repository ships a <Code>compose.yml</Code> that runs the backend
and frontend as separate containers, and an optional{" "}
<Code>compose.traefik.yml</Code> overlay for HTTPS via Traefik.
</P>
<H3>Minimal compose.yml</H3>
<Pre>{`services:
backend:
image: registry.example.com/k-tv-backend:latest
environment:
HOST: 0.0.0.0
DATABASE_URL: sqlite:/app/data/k-tv.db?mode=rwc
CORS_ALLOWED_ORIGINS: https://tv.example.com
JWT_SECRET: <openssl rand -hex 32>
COOKIE_SECRET: <64+ char random string>
SECURE_COOKIE: "true"
PRODUCTION: "true"
JELLYFIN_BASE_URL: http://jellyfin:8096
JELLYFIN_API_KEY: <key>
JELLYFIN_USER_ID: <user-id>
volumes:
- ./data:/app/data
frontend:
image: registry.example.com/k-tv-frontend:latest
environment:
API_URL: http://backend:3000/api/v1
ports:
- "3001:3000"`}</Pre>
<H3>Build-time vs runtime env vars</H3>
<P>
<Code>NEXT_PUBLIC_API_URL</Code> is embedded into the frontend bundle
at build time. It must be passed as a{" "}
<Code>--build-arg</Code> when building the image:
</P>
<Pre>{`docker build \\
--build-arg NEXT_PUBLIC_API_URL=https://tv-api.example.com/api/v1 \\
-t registry.example.com/k-tv-frontend:latest .`}</Pre>
<P>
If you use the provided <Code>compose.yml</Code>, set{" "}
<Code>NEXT_PUBLIC_API_URL</Code> under <Code>build.args</Code> so it
is picked up automatically on every build.
</P>
<P>
<Code>API_URL</Code> (server-side only used by Next.js API routes)
is set at runtime via the container environment and can reference the
backend by its internal Docker hostname:{" "}
<Code>http://backend:3000/api/v1</Code>. It is never baked into the
image.
</P>
<H3>HTTPS with Traefik</H3>
<P>
Merge <Code>compose.traefik.yml</Code> over the base file to add
Traefik labels for automatic TLS certificates and routing:
</P>
<Pre>{`docker compose -f compose.yml -f compose.traefik.yml up -d`}</Pre>
<Note>
Set <Code>SECURE_COOKIE=true</Code> and{" "}
<Code>PRODUCTION=true</Code> whenever the backend is behind HTTPS.
The default cookie secret is publicly known always replace it
before going live.
</Note>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="jellyfin">
<H2>Connecting Jellyfin</H2>
@@ -419,6 +494,66 @@ npm run dev`}</Pre>
</P>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="local-files">
<H2>Local files provider</H2>
<P>
In addition to Jellyfin, K-TV can serve content directly from a
local directory. This is useful when you want to schedule video files
without running a separate media server.
</P>
<H3>Enabling local files</H3>
<P>
Build the backend with the <Code>local-files</Code> Cargo feature
and set the <Code>LOCAL_FILES_DIR</Code> environment variable to the
root of your video library:
</P>
<Pre>{`cargo run --features local-files
# .env
LOCAL_FILES_DIR=/media/videos`}</Pre>
<P>
On startup the backend indexes all video files under{" "}
<Code>LOCAL_FILES_DIR</Code>. Duration is detected via{" "}
<strong className="text-zinc-300">ffprobe</strong> (must be
installed and on <Code>PATH</Code>). Tags are derived from ancestor
directory names; the top-level subdirectory acts as the collection
ID.
</P>
<H3>Rescanning</H3>
<P>
When you add or remove files, trigger a rescan from the Dashboard
(the <strong className="text-zinc-300">Rescan library</strong>{" "}
button appears when the local files provider is active) or call the
API directly:
</P>
<Pre>{`POST /api/v1/files/rescan
Authorization: Bearer <token>
# Response
{ "items_found": 142 }`}</Pre>
<H3>Streaming</H3>
<P>
Local file streams are served by{" "}
<Code>GET /api/v1/files/stream/:id</Code>. This endpoint is{" "}
<strong className="text-zinc-300">public</strong> (no auth required)
and supports <Code>Range</Code> headers for seeking. The frontend
player uses the native <Code>&lt;video&gt;</Code> element for local
files instead of hls.js.
</P>
<H3>Filter support</H3>
<Note>
When the local files provider is active, the series picker and genre
filter are hidden in the block editor those fields are only
supported by Jellyfin. Tags, decade, duration limits, and collection
filters work normally.
</Note>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="first-channel">
<H2>Your first channel</H2>
@@ -743,6 +878,75 @@ Output only valid JSON matching this structure:
</Note>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="iptv">
<H2>IPTV export</H2>
<P>
K-TV can export your channels as a standard IPTV playlist so you can
watch in any IPTV client TiviMate, VLC, Infuse, Jellyfin, and
others.
</P>
<H3>Getting the URLs</H3>
<P>
Open the Dashboard and click the antenna icon on any channel card to
open the IPTV Export dialog. It shows two URLs:
</P>
<Table
head={["URL", "Format", "Purpose"]}
rows={[
[
<Code key="m3u">/iptv/playlist.m3u?token=</Code>,
"M3U",
"Channel list — paste this into your IPTV client as the playlist source.",
],
[
<Code key="xml">/iptv/epg.xml?token=</Code>,
"XMLTV",
"Electronic program guide — paste this as the EPG / guide data source.",
],
]}
/>
<H3>Adding to an IPTV client</H3>
<P>
Copy the M3U URL and add it as a new playlist in your client. If the
client supports XMLTV, also add the EPG URL so programme titles and
descriptions appear in the guide.
</P>
<Warn>
Both URLs contain your session JWT as a query parameter. Anyone with
the URL can access your channels treat it like a password and do
not share it publicly. Rotating your session (logging out and back
in) invalidates the old URLs.
</Warn>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="channel-password">
<H2>Channel passwords</H2>
<P>
Individual channels can be protected with an optional password. When
set, TV viewers are prompted to enter the password before the stream
plays. Channels without a password are always public.
</P>
<H3>Setting a password</H3>
<P>
Enter a password in the <strong className="text-zinc-300">Password</strong>{" "}
field when creating a channel or editing it in the Dashboard. Leave
the field blank to remove an existing password.
</P>
<Warn>
Channel passwords are not end-to-end encrypted. They prevent casual
access someone who can intercept network traffic or extract the
JWT from an IPTV URL can still reach the stream. Do not use channel
passwords as the sole protection for sensitive content.
</Warn>
</Section>
{/* ---------------------------------------------------------------- */}
<Section id="tv-page">
<H2>Watching TV</H2>

View File

@@ -15,7 +15,7 @@ export default function MainLayout({ children }: { children: ReactNode }) {
<header className="sticky top-0 z-50 border-b border-zinc-800 bg-zinc-950/80 backdrop-blur">
<nav className="mx-auto flex h-14 max-w-7xl items-center justify-between px-6">
<Link
href="/tv"
href="/"
className="text-sm font-semibold tracking-widest text-zinc-100 uppercase"
>
K-TV