feat(docs): enhance documentation with Docker deployment and local files provider sections
This commit is contained in:
@@ -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><video></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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,152 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Home() {
|
||||
redirect("/tv");
|
||||
export default function LandingPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 text-zinc-100">
|
||||
{/* Header */}
|
||||
<header className="flex items-center justify-between px-6 py-4 border-b border-zinc-800">
|
||||
<span className="text-lg font-bold tracking-tight text-zinc-100">
|
||||
K-TV
|
||||
</span>
|
||||
<nav className="flex items-center gap-6 text-sm text-zinc-400">
|
||||
<Link href="/tv" className="hover:text-zinc-100 transition-colors">
|
||||
TV
|
||||
</Link>
|
||||
<Link href="/docs" className="hover:text-zinc-100 transition-colors">
|
||||
Docs
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
className="rounded-md border border-zinc-700 px-3 py-1.5 text-zinc-300 hover:border-zinc-500 hover:text-zinc-100 transition-colors"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="mx-auto max-w-4xl px-6 py-24 text-center">
|
||||
<h1 className="mb-4 text-5xl font-bold tracking-tight text-zinc-100 leading-tight">
|
||||
Your media library,
|
||||
<br />
|
||||
broadcast as linear TV
|
||||
</h1>
|
||||
<p className="mb-10 text-lg text-zinc-400 max-w-2xl mx-auto leading-relaxed">
|
||||
K-TV turns your self-hosted media collection into algorithmic TV
|
||||
channels. Define programming blocks, set filters, and let the
|
||||
scheduler fill them. Viewers tune in mid-show — no seeking, just TV.
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<Link
|
||||
href="/tv"
|
||||
className="rounded-lg bg-zinc-100 px-6 py-2.5 text-sm font-semibold text-zinc-900 hover:bg-white transition-colors"
|
||||
>
|
||||
Watch TV
|
||||
</Link>
|
||||
<Link
|
||||
href="/docs"
|
||||
className="rounded-lg border border-zinc-700 px-6 py-2.5 text-sm font-semibold text-zinc-300 hover:border-zinc-500 hover:text-zinc-100 transition-colors"
|
||||
>
|
||||
Read the docs
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Feature cards */}
|
||||
<section className="mx-auto max-w-5xl px-6 pb-20">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-6">
|
||||
<h3 className="mb-2 font-semibold text-zinc-100">
|
||||
Linear scheduling
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
Draw time blocks on a 24-hour timeline. Each block has its own
|
||||
filters, fill strategy, and recycle policy. Schedules are
|
||||
generated on demand and valid for 48 hours.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-6">
|
||||
<h3 className="mb-2 font-semibold text-zinc-100">
|
||||
Jellyfin & local files
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
Connect your Jellyfin server or point K-TV at a local directory.
|
||||
Filter by genre, decade, tags, series, or collection. All
|
||||
providers share the same scheduling engine.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-6">
|
||||
<h3 className="mb-2 font-semibold text-zinc-100">
|
||||
EPG & live program guide
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
A full electronic program guide shows what is on now and what is
|
||||
coming up. Keyboard shortcuts, channel numbers, subtitles, and an
|
||||
"Up next" banner are all built in.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-zinc-800 bg-zinc-900/50 p-6">
|
||||
<h3 className="mb-2 font-semibold text-zinc-100">
|
||||
Import, export & IPTV
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
Channels are plain JSON — paste one from an LLM, share it with a
|
||||
friend, or import it across instances. Export an M3U playlist and
|
||||
XMLTV EPG to watch in TiviMate, VLC, or Infuse.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick-start */}
|
||||
<section className="mx-auto max-w-3xl px-6 pb-24">
|
||||
<h2 className="mb-4 text-xl font-semibold text-zinc-100">
|
||||
Quick start
|
||||
</h2>
|
||||
<pre className="overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-900 p-5 font-mono text-[13px] leading-relaxed text-zinc-300">{`# Docker (recommended)
|
||||
git clone <repo-url> k-tv && cd k-tv
|
||||
cp .env.example .env # fill in JWT_SECRET, COOKIE_SECRET, JELLYFIN_*
|
||||
docker compose up -d
|
||||
|
||||
# Or run from source
|
||||
cd k-tv-backend && cargo run
|
||||
cd k-tv-frontend && npm install && npm run dev`}</pre>
|
||||
<p className="mt-3 text-sm text-zinc-500">
|
||||
See the{" "}
|
||||
<Link
|
||||
href="/docs"
|
||||
className="text-zinc-400 underline underline-offset-2 hover:text-zinc-200"
|
||||
>
|
||||
docs
|
||||
</Link>{" "}
|
||||
for full environment variable reference and Docker deployment
|
||||
instructions.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-zinc-800 px-6 py-6">
|
||||
<div className="mx-auto flex max-w-5xl items-center justify-between text-sm text-zinc-500">
|
||||
<span>K-TV</span>
|
||||
<nav className="flex gap-5">
|
||||
<Link href="/tv" className="hover:text-zinc-300 transition-colors">
|
||||
TV
|
||||
</Link>
|
||||
<Link
|
||||
href="/docs"
|
||||
className="hover:text-zinc-300 transition-colors"
|
||||
>
|
||||
Docs
|
||||
</Link>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="hover:text-zinc-300 transition-colors"
|
||||
>
|
||||
Dashboard
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user