From a5c6fd5fe481028c7da8edf6d41412f6682cd985 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 25 Aug 2026 00:16:42 +0200 Subject: [PATCH] Initial commit: Distributed Stremio Media Cluster V1 Master API, Admin UI, Go media-node, Docker/Dokploy deploy configs. Co-authored-by: Cursor --- .gitignore | 17 + README.md | 128 + apps/admin-ui/next-env.d.ts | 6 + apps/admin-ui/next.config.js | 16 + apps/admin-ui/package.json | 21 + apps/admin-ui/public/.gitkeep | 1 + apps/admin-ui/src/app/dashboard/page.tsx | 85 + apps/admin-ui/src/app/globals.css | 190 ++ apps/admin-ui/src/app/layout.tsx | 15 + apps/admin-ui/src/app/movies/page.tsx | 170 + apps/admin-ui/src/app/nodes/page.tsx | 213 ++ apps/admin-ui/src/app/page.tsx | 73 + apps/admin-ui/src/app/series/page.tsx | 75 + apps/admin-ui/src/app/settings/page.tsx | 57 + apps/admin-ui/src/app/streams/page.tsx | 85 + apps/admin-ui/src/components/Nav.tsx | 47 + apps/admin-ui/tsconfig.json | 21 + apps/master-api/.env.example | 21 + apps/master-api/package.json | 38 + .../20250824120000_init/migration.sql | 312 ++ .../prisma/migrations/migration_lock.toml | 2 + apps/master-api/prisma/schema.prisma | 280 ++ apps/master-api/prisma/seed.ts | 35 + apps/master-api/src/admin/routes.ts | 375 +++ apps/master-api/src/app.ts | 93 + apps/master-api/src/auth/routes.ts | 124 + apps/master-api/src/config.ts | 30 + apps/master-api/src/database/client.ts | 8 + apps/master-api/src/media/sync.ts | 62 + .../src/metadata/filename-parser.test.ts | 36 + .../src/metadata/filename-parser.ts | 105 + apps/master-api/src/metadata/service.ts | 235 ++ apps/master-api/src/nodes/routes.ts | 290 ++ apps/master-api/src/playback/service.ts | 109 + apps/master-api/src/security/crypto.ts | 39 + apps/master-api/src/security/errors.ts | 25 + apps/master-api/src/stremio/routes.ts | 363 +++ apps/master-api/src/websocket/manager.ts | 317 ++ apps/master-api/tsconfig.json | 17 + deploy/docker/Dockerfile.admin-ui | 27 + deploy/docker/Dockerfile.master-api | 34 + deploy/docker/docker-compose.dokploy.yml | 88 + deploy/docker/docker-compose.yml | 62 + deploy/dokploy/.env.example | 31 + deploy/master/.env.example | 23 + deploy/master/backup.sh | 21 + deploy/node/build.sh | 21 + deploy/node/config.example.yaml | 22 + deploy/node/install.sh | 63 + deploy/node/media-node.service | 25 + docs/deployment.md | 71 + docs/dokploy.md | 137 + docs/npm-setup.md | 64 + node/media-node/cmd/media-node/main.go | 453 +++ node/media-node/go.mod | 11 + node/media-node/internal/config/config.go | 99 + node/media-node/internal/control/client.go | 332 ++ node/media-node/internal/database/store.go | 256 ++ node/media-node/internal/health/disk_unix.go | 15 + .../internal/health/disk_windows.go | 7 + node/media-node/internal/health/health.go | 46 + node/media-node/internal/scanner/parser.go | 98 + .../internal/scanner/parser_test.go | 36 + node/media-node/internal/scanner/scanner.go | 298 ++ node/media-node/internal/streaming/server.go | 380 +++ package.json | 18 + packages/protocol/package.json | 18 + packages/protocol/src/index.ts | 86 + packages/protocol/tsconfig.json | 14 + packages/shared-types/package.json | 14 + packages/shared-types/src/index.ts | 105 + packages/shared-types/tsconfig.json | 14 + pnpm-lock.yaml | 2764 +++++++++++++++++ pnpm-workspace.yaml | 10 + 74 files changed, 9899 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 apps/admin-ui/next-env.d.ts create mode 100644 apps/admin-ui/next.config.js create mode 100644 apps/admin-ui/package.json create mode 100644 apps/admin-ui/public/.gitkeep create mode 100644 apps/admin-ui/src/app/dashboard/page.tsx create mode 100644 apps/admin-ui/src/app/globals.css create mode 100644 apps/admin-ui/src/app/layout.tsx create mode 100644 apps/admin-ui/src/app/movies/page.tsx create mode 100644 apps/admin-ui/src/app/nodes/page.tsx create mode 100644 apps/admin-ui/src/app/page.tsx create mode 100644 apps/admin-ui/src/app/series/page.tsx create mode 100644 apps/admin-ui/src/app/settings/page.tsx create mode 100644 apps/admin-ui/src/app/streams/page.tsx create mode 100644 apps/admin-ui/src/components/Nav.tsx create mode 100644 apps/admin-ui/tsconfig.json create mode 100644 apps/master-api/.env.example create mode 100644 apps/master-api/package.json create mode 100644 apps/master-api/prisma/migrations/20250824120000_init/migration.sql create mode 100644 apps/master-api/prisma/migrations/migration_lock.toml create mode 100644 apps/master-api/prisma/schema.prisma create mode 100644 apps/master-api/prisma/seed.ts create mode 100644 apps/master-api/src/admin/routes.ts create mode 100644 apps/master-api/src/app.ts create mode 100644 apps/master-api/src/auth/routes.ts create mode 100644 apps/master-api/src/config.ts create mode 100644 apps/master-api/src/database/client.ts create mode 100644 apps/master-api/src/media/sync.ts create mode 100644 apps/master-api/src/metadata/filename-parser.test.ts create mode 100644 apps/master-api/src/metadata/filename-parser.ts create mode 100644 apps/master-api/src/metadata/service.ts create mode 100644 apps/master-api/src/nodes/routes.ts create mode 100644 apps/master-api/src/playback/service.ts create mode 100644 apps/master-api/src/security/crypto.ts create mode 100644 apps/master-api/src/security/errors.ts create mode 100644 apps/master-api/src/stremio/routes.ts create mode 100644 apps/master-api/src/websocket/manager.ts create mode 100644 apps/master-api/tsconfig.json create mode 100644 deploy/docker/Dockerfile.admin-ui create mode 100644 deploy/docker/Dockerfile.master-api create mode 100644 deploy/docker/docker-compose.dokploy.yml create mode 100644 deploy/docker/docker-compose.yml create mode 100644 deploy/dokploy/.env.example create mode 100644 deploy/master/.env.example create mode 100644 deploy/master/backup.sh create mode 100644 deploy/node/build.sh create mode 100644 deploy/node/config.example.yaml create mode 100644 deploy/node/install.sh create mode 100644 deploy/node/media-node.service create mode 100644 docs/deployment.md create mode 100644 docs/dokploy.md create mode 100644 docs/npm-setup.md create mode 100644 node/media-node/cmd/media-node/main.go create mode 100644 node/media-node/go.mod create mode 100644 node/media-node/internal/config/config.go create mode 100644 node/media-node/internal/control/client.go create mode 100644 node/media-node/internal/database/store.go create mode 100644 node/media-node/internal/health/disk_unix.go create mode 100644 node/media-node/internal/health/disk_windows.go create mode 100644 node/media-node/internal/health/health.go create mode 100644 node/media-node/internal/scanner/parser.go create mode 100644 node/media-node/internal/scanner/parser_test.go create mode 100644 node/media-node/internal/scanner/scanner.go create mode 100644 node/media-node/internal/streaming/server.go create mode 100644 package.json create mode 100644 packages/protocol/package.json create mode 100644 packages/protocol/src/index.ts create mode 100644 packages/protocol/tsconfig.json create mode 100644 packages/shared-types/package.json create mode 100644 packages/shared-types/src/index.ts create mode 100644 packages/shared-types/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd51654 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +node_modules/ +dist/ +.next/ +.env +.env.local +*.log +.DS_Store +coverage/ +.turbo/ +*.db +*.db-journal +*.tsbuildinfo +node/media-node/media-node +node/media-node/media-node.exe +apps/admin-ui/.next/ +apps/master-api/dist/ +packages/*/dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff7c363 --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# Distributed Stremio Media Cluster + +Privé gedistribueerd mediaplatform voor **Stremio**: één Master, onbeperkt Media Nodes, video rechtstreeks van node naar player. + +## Architectuur + +``` +Stremio ──HTTPS──► Master (catalog / meta / stream) + ▲ + │ WSS control + Media Nodes + │ +Stremio ◄──HTTPS video──┘ (via Nginx Proxy Manager) +``` + +Harde eisen: + +- geen VPN / WireGuard / SMB / NFS tussen locaties +- geen Docker op nodes +- geen transcoding in V1 +- geen raw filesystem paths publiek +- server-side playback sessions (geen simpele 1-uur signed URLs) + +## Stack + +| Component | Techniek | +|-----------|----------| +| Master API | TypeScript, Fastify, Prisma, PostgreSQL, Redis | +| Admin UI | Next.js | +| Media Node | Go binary + SQLite + systemd | +| Proxy | Nginx Proxy Manager (GUI only) | + +## Repository + +``` +apps/master-api/ Master + Stremio addon +apps/admin-ui/ Admin dashboard +node/media-node/ Go media node +packages/ Gedeelde types + protocol +deploy/docker/ Docker Compose Master +deploy/node/ Installer + systemd +docs/ Deployment + NPM setup +``` + +## Snel starten (dev) + +```bash +npx pnpm install + +# Postgres + Redis +cd deploy/docker +docker compose up -d postgres redis + +# Master +cd ../../apps/master-api +cp .env.example .env +# DATABASE_URL=postgresql://media:media_secret@localhost:5432/media_cluster +npx prisma migrate deploy +npx pnpm db:seed +npx pnpm dev + +# Admin UI +cd ../admin-ui +npx pnpm dev +``` + +- API: http://localhost:3000 +- UI: http://localhost:3001 +- Login: `admin@localhost` / `changeme123` + +## Media Node + +Op Linux (productie): + +```bash +cd node/media-node +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o media-node ./cmd/media-node +sudo ./media-node install +``` + +CLI: + +``` +media-node install|register|run|scan|diagnostics|config|start|stop|restart|status|version +``` + +## Productie + +Zie: + +- [docs/deployment.md](docs/deployment.md) +- [docs/dokploy.md](docs/dokploy.md) — **Master op Dokploy (aanbevolen)** +- [docs/npm-setup.md](docs/npm-setup.md) + +```bash +cd deploy/docker +cp ../master/.env.example ../master/.env +docker compose up -d --build +``` + +Of in Dokploy: Compose-app met pad `deploy/docker/docker-compose.dokploy.yml`. + +## Stremio installeren + +1. Admin UI → **Instellingen** → addon token +2. URL in Stremio: + `https://master.example.com/stremio//manifest.json` + +## V1 features + +- Node enrollment + unieke credentials +- WebSocket control channel + heartbeat + reconnect +- Movies/Series scanner + library sync +- Filename parsing + TMDB matching (optioneel) +- Stremio catalogs / meta / streams +- Playback sessions met idle + absolute expiry +- HTTP Range streaming (direct play) +- Offline node detectie + duplicate media +- Admin: dashboard, nodes, library, streams, matching ignore/refresh +- Rate limits, security headers, redacted token logs +- Backup script + release build script + +## Tests + +```bash +cd apps/master-api && npx pnpm test +cd node/media-node && go test ./... +``` diff --git a/apps/admin-ui/next-env.d.ts b/apps/admin-ui/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/apps/admin-ui/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/admin-ui/next.config.js b/apps/admin-ui/next.config.js new file mode 100644 index 0000000..66e1074 --- /dev/null +++ b/apps/admin-ui/next.config.js @@ -0,0 +1,16 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + // standalone only needed for Docker; skip on Windows to avoid symlink EPERM + output: process.env.DOCKER_BUILD === "1" ? "standalone" : undefined, + async rewrites() { + const apiUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3000"; + return [ + { + source: "/api/:path*", + destination: `${apiUrl}/api/:path*`, + }, + ]; + }, +}; + +module.exports = nextConfig; diff --git a/apps/admin-ui/package.json b/apps/admin-ui/package.json new file mode 100644 index 0000000..01e6522 --- /dev/null +++ b/apps/admin-ui/package.json @@ -0,0 +1,21 @@ +{ + "name": "@media-cluster/admin-ui", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev -p 3001", + "build": "next build", + "start": "next start -p 3001" + }, + "dependencies": { + "next": "^15.1.6", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.7", + "@types/react": "^19.0.7", + "@types/react-dom": "^19.0.3", + "typescript": "^5.7.3" + } +} diff --git a/apps/admin-ui/public/.gitkeep b/apps/admin-ui/public/.gitkeep new file mode 100644 index 0000000..c0a8ea4 --- /dev/null +++ b/apps/admin-ui/public/.gitkeep @@ -0,0 +1 @@ +# Keep this file so Next.js public assets directory exists diff --git a/apps/admin-ui/src/app/dashboard/page.tsx b/apps/admin-ui/src/app/dashboard/page.tsx new file mode 100644 index 0000000..c868f19 --- /dev/null +++ b/apps/admin-ui/src/app/dashboard/page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Nav, useAuth } from "@/components/Nav"; + +interface Dashboard { + nodesOnline: number; + nodesOffline: number; + totalStorage: string; + freeStorage: string; + movies: number; + series: number; + episodes: number; + activeStreams: number; + currentBandwidth: string; +} + +function formatBytes(bytes: string): string { + const n = BigInt(bytes); + const units = ["B", "KB", "MB", "GB", "TB"]; + let val = Number(n); + let i = 0; + while (val >= 1024 && i < units.length - 1) { + val /= 1024; + i++; + } + return `${val.toFixed(1)} ${units[i]}`; +} + +export default function DashboardPage() { + useAuth(); + const [data, setData] = useState(null); + + useEffect(() => { + fetch("/api/v1/admin/dashboard", { credentials: "include" }) + .then((r) => r.json()) + .then(setData) + .catch(console.error); + }, []); + + return ( + <> +