#!/usr/bin/env bash # ============================================================================= # Ripster – Installationsskript (Git) # Unterstützt: Debian 11/12, Ubuntu 22.04/24.04 # Benötigt: sudo / root, Internetzugang # # Verwendung: # curl -fsSL https://raw.githubusercontent.com/Mboehmlaender/ripster/main/install.sh | sudo bash # oder: # wget -qO- https://raw.githubusercontent.com/Mboehmlaender/ripster/main/install.sh | sudo bash # # Mit Optionen (nur via Datei möglich): # sudo bash install.sh [Optionen] # # Optionen: # --branch Git-Branch (Standard: main) # --dir Installationsverzeichnis (Standard: /opt/ripster) # --user Systembenutzer für den Dienst (Standard: ripster) # --port Backend-Port (Standard: 3001) # --host Hostname/IP für die Weboberfläche (Standard: Maschinen-IP) # --no-makemkv MakeMKV-Installation überspringen # --no-handbrake HandBrake-Installation überspringen # --no-nginx Nginx-Einrichtung überspringen # --reinstall Vorhandene Installation aktualisieren (Daten bleiben erhalten) # -h, --help Diese Hilfe anzeigen # ============================================================================= set -euo pipefail REPO_URL="https://github.com/Mboehmlaender/ripster.git" REPO_RAW_BASE="https://raw.githubusercontent.com/Mboehmlaender/ripster" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)" BUNDLED_HANDBRAKE_CLI="${SCRIPT_DIR}/bin/HandBrakeCLI" # --- Farben ------------------------------------------------------------------- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; BOLD='\033[1m'; RESET='\033[0m' info() { echo -e "${BLUE}[INFO]${RESET} $*"; } ok() { echo -e "${GREEN}[OK]${RESET} $*"; } warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; } error() { echo -e "${RED}[FEHLER]${RESET} $*" >&2; } header() { echo -e "\n${BOLD}${BLUE}══════════════════════════════════════════${RESET}"; \ echo -e "${BOLD} $*${RESET}"; \ echo -e "${BOLD}${BLUE}══════════════════════════════════════════${RESET}"; } fatal() { error "$*"; exit 1; } # --- Standard-Optionen -------------------------------------------------------- GIT_BRANCH="dev" INSTALL_DIR="/opt/ripster" SERVICE_USER="ripster" BACKEND_PORT="3001" FRONTEND_HOST="" SKIP_MAKEMKV=false SKIP_HANDBRAKE=false HANDBRAKE_INSTALL_MODE="" SKIP_NGINX=false REINSTALL=false # --- Argumente parsen --------------------------------------------------------- while [[ $# -gt 0 ]]; do case "$1" in --branch) GIT_BRANCH="$2"; shift 2 ;; --dir) INSTALL_DIR="$2"; shift 2 ;; --user) SERVICE_USER="$2"; shift 2 ;; --port) BACKEND_PORT="$2"; shift 2 ;; --host) FRONTEND_HOST="$2"; shift 2 ;; --no-makemkv) SKIP_MAKEMKV=true; shift ;; --no-handbrake) SKIP_HANDBRAKE=true; shift ;; --no-nginx) SKIP_NGINX=true; shift ;; --reinstall) REINSTALL=true; shift ;; -h|--help) sed -n '/^# Verwendung/,/^# ====/p' "$0" | head -n -1 | sed 's/^# \?//' exit 0 ;; *) fatal "Unbekannte Option: $1" ;; esac done # --- Voraussetzungen prüfen --------------------------------------------------- header "Ripster Installationsskript (Git)" if [[ $EUID -ne 0 ]]; then fatal "Dieses Skript muss als root ausgeführt werden (sudo bash install.sh)" fi if [[ ! -f /etc/os-release ]]; then fatal "Betriebssystem nicht erkennbar. Nur Debian/Ubuntu wird unterstützt." fi . /etc/os-release case "$ID" in debian|ubuntu|linuxmint|pop) ok "Betriebssystem: $PRETTY_NAME" ;; *) fatal "Nicht unterstütztes OS: $ID. Nur Debian/Ubuntu unterstützt." ;; esac if [[ -z "$FRONTEND_HOST" ]]; then FRONTEND_HOST=$(hostname -I | awk '{print $1}') info "Erkannte IP: $FRONTEND_HOST" fi info "Repository: $REPO_URL" info "Branch: $GIT_BRANCH" info "Installationsverzeichnis: $INSTALL_DIR" info "Systembenutzer: $SERVICE_USER" info "Backend-Port: $BACKEND_PORT" info "Frontend-Host: $FRONTEND_HOST" # --- Hilfsfunktionen ---------------------------------------------------------- command_exists() { command -v "$1" &>/dev/null; } download_file() { local url="$1" local target="$2" if command_exists curl; then curl -fsSL "$url" -o "$target" return 0 fi if command_exists wget; then wget -q "$url" -O "$target" return 0 fi return 1 } install_node() { header "Node.js installieren" local required_major=20 if command_exists node; then local current_major current_major=$(node -e "process.stdout.write(String(process.version.split('.')[0].replace('v','')))") if [[ "$current_major" -ge "$required_major" ]]; then ok "Node.js $(node --version) bereits installiert" return fi warn "Node.js $(node --version) zu alt – Node.js 20 wird installiert" fi info "Installiere Node.js 20.x über NodeSource..." curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y nodejs ok "Node.js $(node --version) installiert" } install_makemkv() { header "MakeMKV installieren" if command_exists makemkvcon; then ok "makemkvcon bereits installiert ($(makemkvcon --version 2>&1 | head -1))" return fi info "Installiere Build-Abhängigkeiten für MakeMKV..." apt-get install -y \ build-essential pkg-config libc6-dev libssl-dev \ libexpat1-dev libavcodec-dev libgl1-mesa-dev \ qtbase5-dev zlib1g-dev wget # Aktuelle Version aus dem offiziellen Linux-Forum-Thread ermitteln. # Der Titel lautet immer: "MakeMKV X.Y.Z for Linux is available" local makemkv_fallback="1.18.3" info "Ermittle aktuelle MakeMKV-Version (forum.makemkv.com)..." local makemkv_version makemkv_version=$(curl -s --max-time 15 \ "https://forum.makemkv.com/forum/viewtopic.php?f=3&t=224" \ | grep -oP 'MakeMKV \K[0-9]+\.[0-9]+\.[0-9]+(?= for Linux)' | head -1 || true) if [[ -z "$makemkv_version" ]]; then warn "MakeMKV-Version konnte nicht ermittelt werden – verwende Fallback $makemkv_fallback" makemkv_version="$makemkv_fallback" else info "Aktuelle Version: $makemkv_version" fi info "Baue MakeMKV $makemkv_version..." local tmp_dir tmp_dir=$(mktemp -d) cd "$tmp_dir" local base_url="https://www.makemkv.com/download" wget -q "${base_url}/makemkv-bin-${makemkv_version}.tar.gz" wget -q "${base_url}/makemkv-oss-${makemkv_version}.tar.gz" tar xf "makemkv-oss-${makemkv_version}.tar.gz" cd "makemkv-oss-${makemkv_version}" ./configure make -j"$(nproc)" make install cd "$tmp_dir" tar xf "makemkv-bin-${makemkv_version}.tar.gz" cd "makemkv-bin-${makemkv_version}" mkdir -p tmp && echo "accepted" > tmp/eula_accepted make -j"$(nproc)" make install cd / rm -rf "$tmp_dir" ok "MakeMKV $makemkv_version installiert" warn "Hinweis: MakeMKV benötigt eine Lizenz oder den Beta-Key." warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053" } select_handbrake_mode() { [[ "$SKIP_HANDBRAKE" == true ]] && return local mode_answer="" echo "" echo "Install HandBrake:" echo "" echo "1. Standard version (apt install handbrake-cli)" echo "2. GPU version with NVDEC (use bundled binary)" if [[ -t 0 ]]; then read -r -p "Select option [1/2]: " mode_answer elif [[ -r /dev/tty ]]; then read -r -p "Select option [1/2]: " mode_answer /dev/null || true if command_exists HandBrakeCLI; then ok "HandBrakeCLI installiert: $(HandBrakeCLI --version 2>&1 | head -1)" return fi if command_exists handbrake-cli; then ok "handbrake-cli installiert: $(handbrake-cli --version 2>&1 | head -1)" return fi fatal "HandBrake wurde installiert, aber kein CLI-Befehl wurde gefunden." } install_handbrake_gpu_bundled() { info "Installiere gebündeltes HandBrakeCLI mit NVDEC..." local bundled_source="$BUNDLED_HANDBRAKE_CLI" local downloaded_tmp="" if [[ ! -f "$bundled_source" ]]; then local remote_url="${REPO_RAW_BASE}/${GIT_BRANCH}/bin/HandBrakeCLI" downloaded_tmp=$(mktemp) info "Lokale Binary fehlt – lade aus Branch '$GIT_BRANCH' nach..." if download_file "$remote_url" "$downloaded_tmp"; then chmod 0755 "$downloaded_tmp" bundled_source="$downloaded_tmp" ok "Bundled HandBrakeCLI temporär heruntergeladen" else rm -f "$downloaded_tmp" 2>/dev/null || true fatal "Bundled Binary fehlt lokal ($BUNDLED_HANDBRAKE_CLI) und Download schlug fehl: $remote_url" fi fi install -m 0755 "$bundled_source" /usr/local/bin/HandBrakeCLI hash -r 2>/dev/null || true if [[ -n "$downloaded_tmp" ]]; then rm -f "$downloaded_tmp" 2>/dev/null || true fi ok "Bundled HandBrakeCLI installiert nach /usr/local/bin/HandBrakeCLI" if command_exists HandBrakeCLI; then ok "HandBrakeCLI Version: $(HandBrakeCLI --version 2>&1 | head -1)" fi } install_handbrake() { header "HandBrake CLI installieren" if [[ -z "$HANDBRAKE_INSTALL_MODE" ]]; then HANDBRAKE_INSTALL_MODE="standard" fi case "$HANDBRAKE_INSTALL_MODE" in standard) install_handbrake_standard ;; gpu) install_handbrake_gpu_bundled ;; *) fatal "Unbekannter HandBrake-Modus: $HANDBRAKE_INSTALL_MODE" ;; esac } # --- apt-Hilfsfunktionen ------------------------------------------------------ # Führt apt-get update aus. Bei Release-Fehlern wird versucht, die Sources zu # reparieren (Proxmox-Container, veraltete Spiegelserver, etc.). apt_update() { local output if output=$(apt-get update 2>&1); then return 0 fi # Release-Datei fehlt → versuche Repair if echo "$output" | grep -q "no longer has a Release file\|does not have a Release file"; then warn "apt-Sources fehlerhaft. Versuche Reparatur..." # Strategie 1: --allow-releaseinfo-change if apt-get update --allow-releaseinfo-change -qq 2>/dev/null; then ok "apt-Update mit --allow-releaseinfo-change erfolgreich" return 0 fi # Strategie 2: Kaputte Einträge aus sources.list.d entfernen und Fallback # auf offizielle Spiegel schreiben if [[ -n "${VERSION_CODENAME:-}" ]]; then warn "Schreibe minimale sources.list für $VERSION_CODENAME..." local main_list=/etc/apt/sources.list # Backup cp "$main_list" "${main_list}.bak-$(date +%Y%m%d%H%M%S)" 2>/dev/null || true case "$ID" in ubuntu) cat > "$main_list" < "$main_list" </dev/null; then ok "apt-Update nach Sources-Reparatur erfolgreich" return 0 fi fi # Strategie 3: Kaputte .list-Dateien in sources.list.d deaktivieren warn "Deaktiviere fehlerhafte Eintraege in /etc/apt/sources.list.d/ ..." local broken_files broken_files=$(apt-get update 2>&1 | grep -oP "(?<=The repository ').*?(?=' )" | \ xargs -I{} grep -rl "{}" /etc/apt/sources.list.d/ 2>/dev/null || true) if [[ -n "$broken_files" ]]; then echo "$broken_files" | while read -r f; do warn "Deaktiviere: $f" mv "$f" "${f}.disabled" 2>/dev/null || true done if apt-get update -qq 2>/dev/null; then ok "apt-Update nach Deaktivierung fehlerhafter Sources erfolgreich" return 0 fi fi error "apt-Update fehlgeschlagen. Bitte Sources manuell pruefen:" echo "$output" fatal "Installation abgebrochen. Repariere /etc/apt/sources.list und starte erneut." else error "apt-Update fehlgeschlagen:" echo "$output" fatal "Installation abgebrochen." fi } # --- HandBrake-Installmodus auswählen ---------------------------------------- select_handbrake_mode # --- Systemabhängigkeiten ----------------------------------------------------- header "Systemabhängigkeiten installieren" info "Paketlisten aktualisieren..." apt_update info "Installiere Basispakete..." apt-get install -y \ curl wget git jq \ mediainfo \ util-linux udev \ ca-certificates gnupg \ lsb-release ok "Basispakete installiert" info "Installiere CD-Ripping-Tools..." apt-get install -y \ cdparanoia \ flac \ lame \ opus-tools \ vorbis-tools ok "CD-Ripping-Tools installiert (cdparanoia, flac, lame, opus-tools, vorbis-tools)" install_node if [[ "$SKIP_MAKEMKV" == false ]]; then install_makemkv else warn "MakeMKV-Installation übersprungen (--no-makemkv)" fi if [[ "$SKIP_HANDBRAKE" == false ]]; then install_handbrake else warn "HandBrake-Installation übersprungen (--no-handbrake)" fi if [[ "$SKIP_NGINX" == false ]]; then if ! command_exists nginx; then info "Installiere nginx..." apt-get install -y nginx fi ok "nginx installiert" fi # --- Systembenutzer anlegen --------------------------------------------------- header "Systembenutzer anlegen" if id "$SERVICE_USER" &>/dev/null; then ok "Benutzer '$SERVICE_USER' existiert bereits" else info "Lege Systembenutzer '$SERVICE_USER' an..." useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER" ok "Benutzer '$SERVICE_USER' angelegt" fi SERVICE_HOME="$(getent passwd "$SERVICE_USER" | cut -d: -f6)" if [[ -z "$SERVICE_HOME" || "$SERVICE_HOME" == "/" || "$SERVICE_HOME" == "/nonexistent" ]]; then SERVICE_HOME="/home/$SERVICE_USER" fi mkdir -p "$SERVICE_HOME" chown "$SERVICE_USER:$SERVICE_USER" "$SERVICE_HOME" 2>/dev/null || true chmod 755 "$SERVICE_HOME" 2>/dev/null || true info "Service-Home für '$SERVICE_USER': $SERVICE_HOME" for grp in cdrom optical disk video render; do if getent group "$grp" &>/dev/null; then usermod -aG "$grp" "$SERVICE_USER" 2>/dev/null || true info "Benutzer '$SERVICE_USER' zur Gruppe '$grp' hinzugefügt" fi done # --- Repository klonen / aktualisieren ---------------------------------------- header "Repository holen (Git)" # Prüfen ob der gewünschte Branch auf dem Remote existiert info "Prüfe Branch '$GIT_BRANCH' auf Remote..." if ! git ls-remote --exit-code --heads "$REPO_URL" "$GIT_BRANCH" &>/dev/null; then fatal "Branch '$GIT_BRANCH' existiert nicht im Repository $REPO_URL.\nVerfügbare Branches: $(git ls-remote --heads "$REPO_URL" | awk '{print $2}' | sed 's|refs/heads/||' | tr '\n' ' ')" fi ok "Branch '$GIT_BRANCH' gefunden" if [[ -d "$INSTALL_DIR/.git" ]]; then if [[ "$REINSTALL" == true ]]; then info "Aktualisiere bestehendes Repository..." # Daten sichern if [[ -d "$INSTALL_DIR/backend/data" ]]; then DATA_BACKUP="/tmp/ripster-data-backup-$(date +%Y%m%d%H%M%S)" cp -a "$INSTALL_DIR/backend/data" "$DATA_BACKUP" info "Datenbank gesichert nach: $DATA_BACKUP" fi # safe.directory nötig wenn das Verzeichnis einem anderen User gehört # (z.B. ripster-Serviceuser nach erstem Install) git config --global --add safe.directory "$INSTALL_DIR" 2>/dev/null || true git -C "$INSTALL_DIR" remote set-branches origin '*' git -C "$INSTALL_DIR" fetch --quiet origin git -C "$INSTALL_DIR" reset --hard HEAD git -C "$INSTALL_DIR" checkout --quiet -B "$GIT_BRANCH" "origin/$GIT_BRANCH" git -C "$INSTALL_DIR" reset --hard "origin/$GIT_BRANCH" ok "Repository aktualisiert auf Branch '$GIT_BRANCH'" else fatal "$INSTALL_DIR enthält bereits ein Git-Repository.\nVerwende --reinstall um zu aktualisieren." fi elif [[ -d "$INSTALL_DIR" && "$REINSTALL" == false ]]; then fatal "Verzeichnis $INSTALL_DIR existiert bereits (kein Git-Repo).\nBitte manuell entfernen oder --reinstall verwenden." else info "Klone $REPO_URL (Branch: $GIT_BRANCH)..." git clone --quiet --branch "$GIT_BRANCH" --depth 1 "$REPO_URL" "$INSTALL_DIR" ok "Repository geklont nach $INSTALL_DIR" fi # Daten- und Log-Verzeichnisse sicherstellen mkdir -p "$INSTALL_DIR/backend/data" mkdir -p "$INSTALL_DIR/backend/logs" mkdir -p "$INSTALL_DIR/backend/data/output/raw" mkdir -p "$INSTALL_DIR/backend/data/output/movies" mkdir -p "$INSTALL_DIR/backend/data/output/cd" mkdir -p "$INSTALL_DIR/backend/data/logs" # Gesicherte Daten zurückspielen if [[ -n "${DATA_BACKUP:-}" && -d "$DATA_BACKUP" ]]; then cp -a "$DATA_BACKUP/." "$INSTALL_DIR/backend/data/" ok "Datenbank wiederhergestellt" fi # --- npm-Abhängigkeiten installieren ----------------------------------------- header "npm-Abhängigkeiten installieren" info "Root-Abhängigkeiten..." npm install --prefix "$INSTALL_DIR" --omit=dev --silent info "Backend-Abhängigkeiten..." npm install --prefix "$INSTALL_DIR/backend" --omit=dev --silent info "Frontend-Abhängigkeiten..." npm install --prefix "$INSTALL_DIR/frontend" --silent ok "npm-Abhängigkeiten installiert" # --- Frontend bauen ----------------------------------------------------------- header "Frontend bauen" info "Baue Frontend für $FRONTEND_HOST..." # Relative URLs verwenden – funktioniert mit jedem Hostnamen/Domain, da nginx # /api/ und /ws auf dem selben Host proxied. Absolute IP-URLs würden Chromes # Private Network Access (PNA) Policy verletzen, wenn das Frontend über einen # Domainnamen aufgerufen wird. rm -f "$INSTALL_DIR/frontend/.env.production.local" npm run build --prefix "$INSTALL_DIR/frontend" --silent ok "Frontend gebaut: $INSTALL_DIR/frontend/dist" # --- Backend-Konfiguration --------------------------------------------------- header "Backend konfigurieren" ENV_FILE="$INSTALL_DIR/backend/.env" if [[ -f "$ENV_FILE" && "$REINSTALL" == true ]]; then warn "Bestehende .env bleibt erhalten (--reinstall)" else info "Erstelle Backend .env..." cat > "$ENV_FILE" </dev/null || true chmod 700 "$MAKEMKV_SERVICE_DIR" 2>/dev/null || true # --- Systemd-Dienst: Backend ------------------------------------------------- header "Systemd-Dienst (Backend) erstellen" cat > /etc/systemd/system/ripster-backend.service < /etc/nginx/sites-available/ripster <