diff --git a/.emergent/emergent.yml b/.emergent/emergent.yml index 079d728..d2786c0 100644 --- a/.emergent/emergent.yml +++ b/.emergent/emergent.yml @@ -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-07-23T21:54:52.675455+00:00Z" + "created_at": "2026-07-23T22:02:35.449854+00:00Z" } diff --git a/README.md b/README.md index 3060e9d..2094aeb 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,31 @@ A self-hosted Netflix-style streaming app for movies you legally own. Built with FastAPI + React + MongoDB. -## 📖 Deployment +## ⚡ One-line install (Proxmox / any Linux with root) -**→ See [DEPLOY.md](./DEPLOY.md) for full Proxmox + Docker Compose instructions** -(runs alongside your existing LAMP stack without conflicts). +```bash +curl -fsSL https://raw.githubusercontent.com/YOU/kino-media-server/main/install.sh | sudo bash +``` + +Replace `YOU` with your GitHub username. The installer: +1. Installs Docker + Compose if missing +2. Clones the repo into `/opt/kino` +3. Generates a strong `JWT_SECRET` and admin password +4. Starts the stack via `docker compose` +5. Prints the URL + credentials + +Non-interactive with overrides: +```bash +curl -fsSL https://raw.githubusercontent.com/YOU/kino-media-server/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](./DEPLOY.md)** for LAMP-coexistence, Apache reverse proxy, Radarr integration, and firewalling. ## Features diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..e62c0fd --- /dev/null +++ b/install.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# +# Kino — one-line installer +# +# Usage (interactive): +# curl -fsSL https://raw.githubusercontent.com/YOU/kino-media-server/main/install.sh | sudo bash +# +# Usage (non-interactive, override defaults with env): +# curl -fsSL .../install.sh | sudo KINO_HTTP_PORT=9000 MEDIA_ROOT_HOST=/mnt/nas/movies bash +# +# What it does: +# 1. Installs Docker + Compose if missing (apt/dnf/yum) +# 2. Clones the repo into $KINO_DIR (default /opt/kino) +# 3. Generates a strong JWT_SECRET and (if omitted) ADMIN_PASSWORD +# 4. Writes .env +# 5. Runs `docker compose up -d` +# 6. Prints URL + credentials +# +set -euo pipefail + +# ---------- Defaults (override via env) ---------- +KINO_REPO="${KINO_REPO:-https://github.com/CHANGE_ME/kino-media-server.git}" +KINO_BRANCH="${KINO_BRANCH:-main}" +KINO_DIR="${KINO_DIR:-/opt/kino}" +KINO_HTTP_PORT="${KINO_HTTP_PORT:-8080}" +MEDIA_ROOT_HOST="${MEDIA_ROOT_HOST:-${KINO_DIR}/media}" +DB_NAME="${DB_NAME:-kino}" +ADMIN_EMAIL="${ADMIN_EMAIL:-admin@kino.local}" +ADMIN_NAME="${ADMIN_NAME:-Admin}" +ADMIN_PASSWORD="${ADMIN_PASSWORD:-}" +JWT_SECRET="${JWT_SECRET:-}" + +# ---------- Pretty output ---------- +c_red() { printf "\033[31m%s\033[0m" "$*"; } +c_grn() { printf "\033[32m%s\033[0m" "$*"; } +c_ylw() { printf "\033[33m%s\033[0m" "$*"; } +c_cyn() { printf "\033[36m%s\033[0m" "$*"; } +say() { printf "%s %s\n" "$(c_cyn '›')" "$*"; } +ok() { printf "%s %s\n" "$(c_grn '✓')" "$*"; } +warn() { printf "%s %s\n" "$(c_ylw '!')" "$*"; } +die() { printf "%s %s\n" "$(c_red '✗')" "$*" >&2; exit 1; } + +# ---------- Preflight ---------- +[ "$(id -u)" -eq 0 ] || die "Run as root (or via sudo). Try: curl -fsSL ... | sudo bash" + +if [ "$KINO_REPO" = "https://github.com/CHANGE_ME/kino-media-server.git" ]; then + die "KINO_REPO placeholder — edit install.sh and set your GitHub URL, or pass KINO_REPO=... env var." +fi + +# ---------- OS detection & package manager ---------- +if [ -r /etc/os-release ]; then . /etc/os-release; fi +OS_ID="${ID:-unknown}" +say "Detected OS: ${OS_ID} ${VERSION_ID:-}" + +pkg_install() { + if command -v apt-get >/dev/null 2>&1; then + apt-get update -qq + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$@" + elif command -v dnf >/dev/null 2>&1; then + dnf install -y "$@" + elif command -v yum >/dev/null 2>&1; then + yum install -y "$@" + else + die "No supported package manager (apt/dnf/yum) found." + fi +} + +# ---------- Ensure prerequisites ---------- +need_pkgs=() +command -v curl >/dev/null 2>&1 || need_pkgs+=(curl) +command -v git >/dev/null 2>&1 || need_pkgs+=(git) +command -v openssl >/dev/null 2>&1 || need_pkgs+=(openssl) +command -v ca-certificates >/dev/null 2>&1 || need_pkgs+=(ca-certificates) + +if [ ${#need_pkgs[@]} -gt 0 ]; then + say "Installing prerequisites: ${need_pkgs[*]}" + pkg_install "${need_pkgs[@]}" >/dev/null + ok "Prerequisites installed" +fi + +# ---------- Docker ---------- +if ! command -v docker >/dev/null 2>&1; then + say "Installing Docker via official convenience script" + curl -fsSL https://get.docker.com | sh + systemctl enable --now docker + ok "Docker installed" +else + ok "Docker already installed ($(docker --version))" +fi + +if ! docker compose version >/dev/null 2>&1; then + # docker compose plugin missing — install + say "Installing docker compose plugin" + if command -v apt-get >/dev/null 2>&1; then + pkg_install docker-compose-plugin >/dev/null + else + die "docker compose plugin not found — install it manually for your distro." + fi + ok "Compose plugin installed" +else + ok "Docker Compose already available ($(docker compose version --short))" +fi + +# ---------- Clone or update repo ---------- +if [ -d "$KINO_DIR/.git" ]; then + say "Updating existing checkout at $KINO_DIR" + git -C "$KINO_DIR" fetch --quiet origin "$KINO_BRANCH" + git -C "$KINO_DIR" checkout --quiet "$KINO_BRANCH" + git -C "$KINO_DIR" pull --quiet --ff-only origin "$KINO_BRANCH" || warn "git pull skipped (local changes?)" +else + say "Cloning $KINO_REPO → $KINO_DIR" + mkdir -p "$(dirname "$KINO_DIR")" + git clone --depth 1 --branch "$KINO_BRANCH" "$KINO_REPO" "$KINO_DIR" + ok "Repo cloned" +fi + +cd "$KINO_DIR" + +# ---------- Media root ---------- +mkdir -p "$MEDIA_ROOT_HOST" +ok "Media dir ready: $MEDIA_ROOT_HOST" + +# ---------- Secrets ---------- +if [ -z "$JWT_SECRET" ]; then + JWT_SECRET="$(openssl rand -hex 32)" + ok "Generated random JWT_SECRET" +fi +if [ -z "$ADMIN_PASSWORD" ]; then + ADMIN_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=' | cut -c1-20)" + GENERATED_PASSWORD=1 + ok "Generated random ADMIN_PASSWORD" +fi + +# ---------- Write .env ---------- +if [ -f .env ]; then + cp .env ".env.bak.$(date +%Y%m%d%H%M%S)" + warn ".env exists — backed up. Overwriting with new values." +fi + +cat > .env </dev/null 2>&1; then + ok "Backend healthy at $URL/api/" +else + warn "Backend not responding yet — check: cd $KINO_DIR && docker compose logs -f backend" +fi + +# ---------- Done ---------- +cat <