scan() was doing hundreds of blocking Path.is_file()/stat() calls directly in an async function -- movie/episode/track files often live on the NFS/CIFS-mounted NAS, so each stat call has real network latency. Since the backend runs a single-threaded asyncio event loop, this froze every other request (confirmed live: a plain GET /api/movies hung for 60s+ while a cleanup scan was in flight) for as long as the scan took. Moved every filesystem-touching check (missing files, orphaned HLS dirs) into a plain sync function run via asyncio.to_thread, so the scan can no longer block anything else.
StreamHoard — Personal Media Server (Netflix Clone)
A self-hosted Netflix-style streaming app for movies you legally own. Built with FastAPI + React + MongoDB.
⚡ One-line install (Proxmox / any Linux with root)
curl -fsSL https://raw.githubusercontent.com/myronblair/kino-app/main/install.sh | sudo bash
The installer:
- Installs Docker + Compose if missing
- Clones the repo into
/opt/kino - Generates a strong
JWT_SECRETand admin password - Starts the stack via
docker compose - Prints the URL + credentials
Non-interactive with overrides:
curl -fsSL https://raw.githubusercontent.com/myronblair/kino-app/main/install.sh \
| sudo KINO_HTTP_PORT=9000 \
MEDIA_ROOT_HOST=/mnt/nas/movies \
ADMIN_PASSWORD='choose-your-own' \
bash
📖 Full deployment
See DEPLOY.md for LAMP-coexistence, Apache reverse proxy, Radarr integration, and firewalling.
Features
- Cinematic browse UI with hero banner, horizontal carousels, hover details
- HTML5 video player with HTTP Range request support (proper seeking)
- JWT auth (admin + member roles)
- Admin upload for adding movies (multipart, large files OK)
- My List (watchlist) and Continue Watching (resume playback)
- Search by title/director/cast
- Request queue — users request movies, admin approves & uploads them (Legal alternative to auto-downloading — auto-download of copyrighted material is not implemented.)
Default admin
- Email:
admin@streamhoard.local - Password:
kino-admin-2026
Change ADMIN_EMAIL / ADMIN_PASSWORD in backend/.env before first start
to set your own admin credentials.
Self-hosting on Proxmox
You can run this stack on any Proxmox LXC or VM. Recommended layout:
- Create an Ubuntu 22.04 LXC with at least:
- 2 CPU cores, 4 GB RAM
- 20 GB system disk
- Mount your bulk storage (zfs/nfs) at
/mnt/media
- Install dependencies:
apt update && apt install -y python3-pip nodejs npm yarn mongodb git - Clone & configure:
git clone <your-fork> /opt/kino cd /opt/kino - Backend:
cd backend pip install -r requirements.txt # edit .env: # MONGO_URL=mongodb://localhost:27017 # DB_NAME=kino # MEDIA_ROOT=/mnt/media # ADMIN_EMAIL=you@yourdomain # ADMIN_PASSWORD=<strong> # JWT_SECRET=<openssl rand -hex 32> uvicorn server:app --host 0.0.0.0 --port 8001 - Frontend:
cd frontend yarn install # edit .env: # REACT_APP_BACKEND_URL=https://kino.yourdomain.tld yarn build # serve build/ via nginx or caddy - Recommended: front with Caddy for HTTPS:
kino.yourdomain.tld { handle /api/* { reverse_proxy localhost:8001 } handle { root * /opt/kino/frontend/build try_files {path} /index.html file_server } }
GitHub backup
This repo is the source of truth for code and configuration.
Do not commit your .env files — they contain secrets.
Movie media files should live on your Proxmox storage, not Git.
Adding movies
- Via web UI: Sign in as admin → "Upload" in nav → drag MP4 + fill metadata
- Manually: drop MP4 into
$MEDIA_ROOT/videos/, then create the movie viaPOST /api/movieswithstorage_type=localandstorage_path=<filename>
API quickstart
# Login
curl -X POST $URL/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@streamhoard.local","password":"kino-admin-2026"}'
# List movies
curl $URL/api/movies
# Stream a local movie (note: needs ?auth=<token> for <video> tags)
curl $URL/api/stream/<movie_id>?auth=<token>
Why no auto-download?
Auto-downloading copyrighted movies from the internet is illegal in most jurisdictions (DMCA, EU copyright directive, etc.). StreamHoard is built as a personal media server for content you legally own or that is in the public domain (Internet Archive, Blender Open Movies, etc.).
The "Requests" feature lets users wishlist titles for the admin to add manually — preserving the request UX without crossing into piracy.