mirror of
https://github.com/myronblair/kino-app
synced 2026-07-28 05:23:45 -05:00
auto-commit for 28f1e986-c2af-4742-ad64-e0661980843c
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1",
|
||||
"job_id": "08d9a7a1-2a0c-4502-9939-fc5d643c04c4",
|
||||
"created_at": "2026-04-29T16:22:36.278232+00:00Z"
|
||||
"created_at": "2026-07-23T21:54:52.675455+00:00Z"
|
||||
}
|
||||
|
||||
@@ -104,3 +104,4 @@ credentials.json
|
||||
*.pem
|
||||
*.key
|
||||
.credentials
|
||||
frontend/node_modules/.cache/default-development/0.pack
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Kino — Deployment on a Proxmox LAMP host
|
||||
|
||||
Kino is FastAPI + MongoDB + React. It runs happily alongside your existing
|
||||
LAMP stack (Apache/MySQL/PHP) because we run everything in Docker containers
|
||||
on their own network. Apache keeps port 80/443, MySQL is untouched, Kino
|
||||
uses whatever port you pick.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites on the Proxmox host (or LXC / VM)
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install -y docker.io docker-compose-plugin git
|
||||
sudo systemctl enable --now docker
|
||||
|
||||
# Verify
|
||||
docker compose version
|
||||
```
|
||||
|
||||
If you already have Docker and Compose v2, skip this step.
|
||||
|
||||
---
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
# 1. Clone the repo
|
||||
git clone https://github.com/YOU/kino-media-server.git /opt/kino
|
||||
cd /opt/kino
|
||||
|
||||
# 2. Configure
|
||||
cp .env.compose.example .env
|
||||
nano .env
|
||||
# - Set KINO_HTTP_PORT (default 8080; anything not used by Apache)
|
||||
# - Set MEDIA_ROOT_HOST to your bulk storage path (e.g. /mnt/nas/movies)
|
||||
# - Set a strong JWT_SECRET: openssl rand -hex 32
|
||||
# - Set a strong ADMIN_PASSWORD
|
||||
# - Optional: change ADMIN_EMAIL
|
||||
|
||||
# 3. Start
|
||||
docker compose up -d
|
||||
|
||||
# 4. Check
|
||||
docker compose ps
|
||||
docker compose logs -f backend # confirm "Transcode worker started"
|
||||
```
|
||||
|
||||
Visit `http://<proxmox-ip>:8080` (or whatever `KINO_HTTP_PORT` you set).
|
||||
Log in with the admin credentials from `.env`.
|
||||
|
||||
---
|
||||
|
||||
## Running behind your existing Apache (optional, recommended)
|
||||
|
||||
If you already have Apache on port 80/443 with virtual hosts, add a Kino vhost
|
||||
that reverse-proxies to the container. This gives you `https://kino.example.com`
|
||||
instead of `http://ip:8080`.
|
||||
|
||||
**`/etc/apache2/sites-available/kino.conf`**
|
||||
```apache
|
||||
<VirtualHost *:80>
|
||||
ServerName kino.local # or kino.yourdomain.tld
|
||||
|
||||
ProxyPreserveHost On
|
||||
ProxyRequests Off
|
||||
|
||||
# SPA + API
|
||||
ProxyPass / http://127.0.0.1:8080/
|
||||
ProxyPassReverse / http://127.0.0.1:8080/
|
||||
|
||||
# Streaming needs disabled buffering
|
||||
SetEnv proxy-sendchunked 1
|
||||
|
||||
# Multi-GB uploads
|
||||
LimitRequestBody 21474836480
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo a2enmod proxy proxy_http headers
|
||||
sudo a2ensite kino
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
For HTTPS, either terminate TLS at Apache (add certbot/`SSLEngine on`) or
|
||||
put Caddy in front of it.
|
||||
|
||||
---
|
||||
|
||||
## Common ops
|
||||
|
||||
```bash
|
||||
# Update to latest code
|
||||
cd /opt/kino
|
||||
git pull
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
|
||||
# Stop
|
||||
docker compose down
|
||||
|
||||
# Logs
|
||||
docker compose logs -f backend # FastAPI + transcode worker
|
||||
docker compose logs -f frontend # nginx access
|
||||
docker compose logs -f mongo
|
||||
|
||||
# Shell into backend
|
||||
docker compose exec backend bash
|
||||
|
||||
# Backup MongoDB
|
||||
docker compose exec mongo mongodump --out /data/db/backup-$(date +%F)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Where does data live?
|
||||
|
||||
| What | Where |
|
||||
| -------------------------- | ---------------------------------------------------- |
|
||||
| Movie files, HLS, subs | `$MEDIA_ROOT_HOST` on host (bind-mounted) |
|
||||
| MongoDB data | Docker volume `mongo-data` (managed by Docker) |
|
||||
| TMDB / Radarr keys | In MongoDB (`db.settings` collection) |
|
||||
| Uploads via web UI | `$MEDIA_ROOT_HOST/videos/` |
|
||||
|
||||
To reset everything: `docker compose down -v` (deletes the mongo-data volume).
|
||||
|
||||
---
|
||||
|
||||
## Coexistence with your LAMP stack
|
||||
|
||||
| Kino uses | LAMP uses | Conflict? |
|
||||
| ------------------------ | ----------------------- | --------- |
|
||||
| Port `KINO_HTTP_PORT` | Apache 80/443 | No — different ports |
|
||||
| MongoDB (internal only) | MySQL | No — different DBs, Mongo not exposed to host |
|
||||
| ffmpeg (in container) | anything | No |
|
||||
| Docker networks | native host services | No — bridged network |
|
||||
|
||||
You can happily keep running Wordpress, phpMyAdmin, Nextcloud, whatever on the
|
||||
same box.
|
||||
|
||||
---
|
||||
|
||||
## Radarr integration
|
||||
|
||||
If Radarr is running on the same Proxmox host (native or container), point Kino
|
||||
at it via **Admin → Settings → Radarr**:
|
||||
|
||||
- **URL**: `http://<host-ip>:7878` (use the host IP, not `localhost` — containers
|
||||
can't reach the host's `localhost` unless you add `host.docker.internal`)
|
||||
- **API key**: from Radarr → Settings → General → Security
|
||||
|
||||
**Important**: For Kino to actually play the imported movies, the path Radarr
|
||||
reports (e.g. `/movies/Inception (2010)/Inception.mkv`) must be **readable from
|
||||
inside the Kino backend container**. Mount Radarr's media path into Kino:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml — extend the backend volumes
|
||||
backend:
|
||||
volumes:
|
||||
- ${MEDIA_ROOT_HOST:-./media}:/media
|
||||
- /mnt/radarr-movies:/mnt/radarr-movies:ro # <— add this
|
||||
```
|
||||
|
||||
Radarr's file paths must be identical inside both Radarr and Kino containers
|
||||
(easiest: match the paths exactly).
|
||||
|
||||
---
|
||||
|
||||
## Health check
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:${KINO_HTTP_PORT:-8080}/api/ | jq
|
||||
# {"app":"Kino","status":"ok"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Firewalling
|
||||
|
||||
Kino has no built-in rate limiting or IP allowlist. Since you confirmed
|
||||
**internal network only**, keep it that way:
|
||||
|
||||
```bash
|
||||
# ufw example — allow only your LAN
|
||||
sudo ufw allow from 192.168.0.0/16 to any port ${KINO_HTTP_PORT:-8080}
|
||||
sudo ufw deny ${KINO_HTTP_PORT:-8080}
|
||||
```
|
||||
@@ -3,6 +3,11 @@
|
||||
A self-hosted Netflix-style streaming app for movies you legally own.
|
||||
Built with FastAPI + React + MongoDB.
|
||||
|
||||
## 📖 Deployment
|
||||
|
||||
**→ See [DEPLOY.md](./DEPLOY.md) for full Proxmox + Docker Compose instructions**
|
||||
(runs alongside your existing LAMP stack without conflicts).
|
||||
|
||||
## Features
|
||||
|
||||
- Cinematic browse UI with hero banner, horizontal carousels, hover details
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Kino backend — FastAPI + ffmpeg
|
||||
FROM python:3.11-slim
|
||||
|
||||
# ffmpeg for HLS transcoding; util-linux for `nice` (already in base but explicit)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ffmpeg \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python deps first (layer cache)
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy app source
|
||||
COPY . .
|
||||
|
||||
# Create media dirs (will be overridden by volume mount at runtime)
|
||||
RUN mkdir -p /media/videos /media/posters /media/subtitles /media/hls
|
||||
|
||||
EXPOSE 8001
|
||||
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8001", "--proxy-headers"]
|
||||
@@ -0,0 +1,71 @@
|
||||
# Kino — Docker Compose deployment
|
||||
#
|
||||
# Runs alongside existing LAMP stacks without conflicts:
|
||||
# - Apache keeps port 80/443
|
||||
# - MySQL is untouched (Kino uses its own MongoDB in a container)
|
||||
# - Kino exposes a single configurable port (default 8080)
|
||||
#
|
||||
# Usage:
|
||||
# cp .env.compose.example .env
|
||||
# # edit .env — set JWT_SECRET, ADMIN_PASSWORD, KINO_HTTP_PORT, MEDIA_ROOT_HOST
|
||||
# docker compose up -d
|
||||
# # visit http://<proxmox-ip>:8080 (or whatever KINO_HTTP_PORT you set)
|
||||
|
||||
services:
|
||||
mongo:
|
||||
image: mongo:7
|
||||
container_name: kino-mongo
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
networks:
|
||||
- kino
|
||||
# No ports exposed to host — only reachable inside kino network
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: kino-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
MONGO_URL: mongodb://mongo:27017
|
||||
DB_NAME: ${DB_NAME:-kino}
|
||||
CORS_ORIGINS: "*"
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
|
||||
JWT_ALG: HS256
|
||||
JWT_EXPIRE_HOURS: ${JWT_EXPIRE_HOURS:-720}
|
||||
ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@kino.local}
|
||||
ADMIN_PASSWORD: ${ADMIN_PASSWORD:?set ADMIN_PASSWORD in .env}
|
||||
ADMIN_NAME: ${ADMIN_NAME:-Admin}
|
||||
MEDIA_ROOT: /media
|
||||
volumes:
|
||||
# Host media directory (movies, HLS, subtitles) — point at your NAS/ZFS mount
|
||||
- ${MEDIA_ROOT_HOST:-./media}:/media
|
||||
networks:
|
||||
- kino
|
||||
# No direct host port — frontend container reverse-proxies /api → backend:8001
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
container_name: kino-frontend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
# Only Kino's external port. Change KINO_HTTP_PORT to avoid clashes with Apache.
|
||||
- "${KINO_HTTP_PORT:-8080}:80"
|
||||
networks:
|
||||
- kino
|
||||
|
||||
networks:
|
||||
kino:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
driver: local
|
||||
@@ -0,0 +1,29 @@
|
||||
# Kino frontend — React build + nginx reverse proxy
|
||||
# Multi-stage: build with Node, serve with nginx
|
||||
|
||||
# ---------- Build stage ----------
|
||||
FROM node:20-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# When BACKEND_URL is empty, frontend uses relative /api which nginx below proxies.
|
||||
# This keeps auth cookies / CORS trivial and works behind any reverse proxy.
|
||||
ENV REACT_APP_BACKEND_URL=""
|
||||
|
||||
COPY package.json yarn.lock* ./
|
||||
RUN yarn install --frozen-lockfile --network-timeout 600000
|
||||
|
||||
COPY . .
|
||||
RUN yarn build
|
||||
|
||||
# ---------- Serve stage ----------
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
# Custom config: SPA fallback + /api reverse proxy + large uploads + streaming timeouts
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Static build
|
||||
COPY --from=build /app/build /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,45 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
# Allow multi-GB movie uploads
|
||||
client_max_body_size 20g;
|
||||
|
||||
# Long timeouts for streaming + transcode responses
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 60s;
|
||||
|
||||
# gzip static
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
|
||||
|
||||
# Reverse proxy /api → backend service on the compose network
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Streaming needs unbuffered responses for Range requests
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
# SPA static files with client-side routing fallback
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets aggressively (hashed filenames from CRA)
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
root /usr/share/nginx/html;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
@@ -38,52 +38,7 @@
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<a
|
||||
id="emergent-badge"
|
||||
target="_blank"
|
||||
href="https://app.emergent.sh/?utm_source=emergent-badge"
|
||||
style="
|
||||
display: inline-flex !important;
|
||||
box-sizing: border-box;
|
||||
width: 178px;
|
||||
height: 40px;
|
||||
padding: 8px 12px 8px 12px;
|
||||
align-items: center !important;
|
||||
gap: 8px;
|
||||
border-radius: 50px !important;
|
||||
background: #000 !important;
|
||||
position: fixed !important;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
text-decoration: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue",
|
||||
sans-serif !important;
|
||||
font-size: 12px !important;
|
||||
z-index: 9999 !important;
|
||||
"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M15.5702 8.13142C15.7729 8.0412 16.0007 8.18878 15.9892 8.4103C15.8374 11.3192 14.0965 14.0405 11.2531 15.3065C8.40964 16.5725 5.2224 16.0453 2.95912 14.2117C2.78676 14.072 2.82955 13.804 3.03219 13.7137L4.95677 12.8568C5.04866 12.8159 5.15446 12.823 5.24204 12.8725C6.73377 13.7153 8.59176 13.8649 10.2772 13.1145C11.9626 12.3641 13.0947 10.8833 13.4665 9.21075C13.4883 9.11256 13.5539 9.02918 13.6457 8.98827L15.5702 8.13142Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.3066 4.74698L15.5067 5.19653C15.5759 5.35178 15.5061 5.53366 15.3508 5.60278L1.29992 11.8586C1.14467 11.9278 0.962794 11.8579 0.893675 11.7027L0.701732 11.2716L0.693457 11.2531C-1.10317 7.21778 0.711626 2.49007 4.74692 0.693443C8.78221 -1.10318 13.51 0.711693 15.3066 4.74698ZM2.82356 8.55367C2.63552 8.63739 2.41991 8.51617 2.40853 8.31065C2.28373 6.05724 3.53858 3.85787 5.72286 2.88536C7.90715 1.91286 10.3813 2.45199 11.9724 4.05256C12.1175 4.19854 12.0633 4.43988 11.8753 4.5236L2.82356 8.55367Z" fill="white"/>
|
||||
</svg>
|
||||
<p
|
||||
style="
|
||||
color: #FFF !important;
|
||||
font-family: 'Inter', sans-serif !important;
|
||||
font-size: 13px !important;
|
||||
font-style: normal !important;
|
||||
font-weight: 600 !important;
|
||||
line-height: 20px !important;
|
||||
margin: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
"
|
||||
>
|
||||
Made with Emergent
|
||||
</p>
|
||||
</a>
|
||||
<script>
|
||||
<script>
|
||||
!(function (t, e) {
|
||||
var o, n, p, r;
|
||||
e.__SV ||
|
||||
|
||||
Reference in New Issue
Block a user