auto-commit for 28f1e986-c2af-4742-ad64-e0661980843c

This commit is contained in:
emergent-agent-e1
2026-07-23 22:01:21 +00:00
parent 21cdd24116
commit 1471d58385
9 changed files with 365 additions and 47 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1", "env_image_name": "fastapi_react_mongo_shadcn_base_image_cloud_arm:release-17042026-1",
"job_id": "08d9a7a1-2a0c-4502-9939-fc5d643c04c4", "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"
} }
+1
View File
@@ -104,3 +104,4 @@ credentials.json
*.pem *.pem
*.key *.key
.credentials .credentials
frontend/node_modules/.cache/default-development/0.pack
+189
View File
@@ -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}
```
+5
View File
@@ -3,6 +3,11 @@
A self-hosted Netflix-style streaming app for movies you legally own. A self-hosted Netflix-style streaming app for movies you legally own.
Built with FastAPI + React + MongoDB. 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 ## Features
- Cinematic browse UI with hero banner, horizontal carousels, hover details - Cinematic browse UI with hero banner, horizontal carousels, hover details
+23
View File
@@ -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"]
+71
View File
@@ -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
+29
View File
@@ -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;"]
+45
View File
@@ -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";
}
}
+1 -46
View File
@@ -38,52 +38,7 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
<a <script>
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,
&quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, Cantarell,
&quot;Open Sans&quot;, &quot;Helvetica Neue&quot;,
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>
!(function (t, e) { !(function (t, e) {
var o, n, p, r; var o, n, p, r;
e.__SV || e.__SV ||