From 1fb8dd85fb551755d57ca6a491a12e4712fe0881 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Sat, 14 Mar 2026 19:07:39 +0000 Subject: [PATCH] 0.9.1-6 Update --- .gitignore | 7 - install-dev.sh | 1025 ++++++++++++++++++++++++++++++++++++++++++++++++ install.sh | 826 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 1851 insertions(+), 7 deletions(-) create mode 100644 install-dev.sh create mode 100644 install.sh diff --git a/.gitignore b/.gitignore index 20372e5..99f32ad 100644 --- a/.gitignore +++ b/.gitignore @@ -80,12 +80,5 @@ Thumbs.db # Scripts # ---------------------------- /scripts/ -/deploy-ripster.sh /setup.sh -/install.sh -/install-dev.sh -/build-handbrake-nvdec.sh -/gitea_setup.sh -/gitea_install.sh -/release.sh /Audible_Tool \ No newline at end of file diff --git a/install-dev.sh b/install-dev.sh new file mode 100644 index 0000000..b7f84ce --- /dev/null +++ b/install-dev.sh @@ -0,0 +1,1025 @@ +#!/usr/bin/env bash +# ============================================================================= +# Ripster – Installationsskript +# Unterstützt: Debian 11/12, Ubuntu 22.04/24.04 +# Benötigt: sudo / root +# +# Verwendung: +# chmod +x install.sh +# sudo ./install.sh [Optionen] +# +# Optionen: +# --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 +# --build-handbrake HandBrake aus Quellcode mit NVDEC-Unterstützung bauen +# --handbrake-version HandBrake-Version für Source-Build (Standard: 1.9.0) +# --handbrake-update-policy +# Bei NVDEC-Self-Build: bei neuer Git-Release behalten, +# nachfragen oder direkt neu bauen (Standard: keep) +# --no-nginx Nginx-Einrichtung überspringen (Frontend läuft dann auf Port 5173) +# --reinstall Vorhandene Installation ersetzen (Daten bleiben erhalten) +# -h, --help Diese Hilfe anzeigen +# ============================================================================= +set -euo pipefail + +# --- 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 -------------------------------------------------------- +INSTALL_DIR="/opt/ripster" +SERVICE_USER="ripster" +BACKEND_PORT="3001" +FRONTEND_HOST="" # wird automatisch ermittelt, wenn leer +SKIP_MAKEMKV=false +SKIP_HANDBRAKE=false +BUILD_HANDBRAKE_NVDEC=false +HANDBRAKE_MODE_SELECTED=false +HANDBRAKE_VERSION="1.9.0" +HANDBRAKE_UPDATE_POLICY="keep" +HANDBRAKE_UPDATE_POLICY_SELECTED=false +SKIP_NGINX=false +REINSTALL=false +SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HANDBRAKE_SELFBUILD_MARKER="/usr/local/share/ripster/handbrake-selfbuild.env" + +# --- Argumente parsen --------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --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 ;; + --build-handbrake) BUILD_HANDBRAKE_NVDEC=true; HANDBRAKE_MODE_SELECTED=true; shift ;; + --handbrake-version) HANDBRAKE_VERSION="$2"; shift 2 ;; + --handbrake-update-policy) + case "$2" in + keep|prompt|build) HANDBRAKE_UPDATE_POLICY="$2"; HANDBRAKE_UPDATE_POLICY_SELECTED=true ;; + *) fatal "Ungültige --handbrake-update-policy: $2 (erlaubt: keep|prompt|build)" ;; + esac + shift 2 ;; + --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" + +if [[ $EUID -ne 0 ]]; then + fatal "Dieses Skript muss als root ausgeführt werden (sudo ./install.sh)" +fi + +# OS-Erkennung +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 + +# Host-IP ermitteln +if [[ -z "$FRONTEND_HOST" ]]; then + FRONTEND_HOST=$(hostname -I | awk '{print $1}') + info "Erkannte IP: $FRONTEND_HOST" +fi + +# Quelldirectory prüfen +[[ -f "$SOURCE_DIR/backend/package.json" ]] || \ + fatal "Ripster-Quellen nicht gefunden in: $SOURCE_DIR" + +info "Quellverzeichnis: $SOURCE_DIR" +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; } + +nginx_replace_or_insert_directive() { + local file="$1" + local directive_regex="$2" + local desired_line="$3" + local anchor_regex="$4" + local directive_sed_regex="${directive_regex//\//\\/}" + local anchor_sed_regex="${anchor_regex//\//\\/}" + local desired_sed_line="${desired_line//\//\\/}" + + if grep -Eq "$directive_regex" "$file"; then + sed -i -E "0,/$directive_sed_regex/s//${desired_sed_line}/" "$file" + return 0 + fi + + sed -i "/$anchor_sed_regex/a\\$desired_line" "$file" +} + +patch_existing_ripster_nginx_site() { + local file="$1" + local backup_file="${file}.bak-$(date +%Y%m%d%H%M%S)" + + [[ -f "$file" ]] || return 1 + + cp -a "$file" "$backup_file" + info "Bestehende nginx-Konfiguration erkannt - ergänze Upload-/Proxy-Settings" + info "Backup erstellt: $backup_file" + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*client_max_body_size[[:space:]]+[^;]+;' \ + ' client_max_body_size 8G;' \ + 'server_name .*;' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_connect_timeout[[:space:]]+[^;]+;' \ + ' proxy_connect_timeout 60s;' \ + 'location /api/ {' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_send_timeout[[:space:]]+[^;]+;' \ + ' proxy_send_timeout 3600s;' \ + 'location /api/ {' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_read_timeout[[:space:]]+[^;]+;' \ + ' proxy_read_timeout 3600s;' \ + 'location /api/ {' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_request_buffering[[:space:]]+[^;]+;' \ + ' proxy_request_buffering off;' \ + 'location /api/ {' +} + +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 + local mk_version_line + mk_version_line="$(makemkvcon --version 2>&1 | head -1 || true)" + if [[ -z "$mk_version_line" || "$mk_version_line" == *"unrecognized option"* ]]; then + mk_version_line="$(makemkvcon 2>&1 | head -1 || true)" + fi + ok "makemkvcon bereits installiert (${mk_version_line:-Version unbekannt})" + 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 + + 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 "Gefundene 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" +} + +handbrake_has_nvdec() { + command_exists HandBrakeCLI || return 1 + HandBrakeCLI --help 2>&1 | grep -qi "nvdec" +} + +handbrake_installed_version() { + command_exists HandBrakeCLI || return 1 + HandBrakeCLI --version 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+){1,3}' | head -1 +} + +handbrake_latest_git_version() { + local latest="" + latest=$(curl -fsSL --max-time 10 "https://api.github.com/repos/HandBrake/HandBrake/releases/latest" 2>/dev/null \ + | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"[^"]+"' \ + | head -1 \ + | sed -E 's/.*"([^"]+)".*/\1/' \ + | sed 's/^v//') + [[ -n "$latest" ]] || return 1 + [[ "$latest" =~ ^[0-9]+(\.[0-9]+){1,3}$ ]] || return 1 + printf '%s\n' "$latest" +} + +handbrake_is_self_built() { + local hb_path="${1:-$(command -v HandBrakeCLI 2>/dev/null || true)}" + local resolved_path="" + [[ -n "$hb_path" ]] || return 1 + [[ "$hb_path" == "/usr/local/bin/HandBrakeCLI" ]] || return 1 + [[ -f "$HANDBRAKE_SELFBUILD_MARKER" ]] && return 0 + resolved_path=$(readlink -f "$hb_path" 2>/dev/null || true) + dpkg -S "$hb_path" >/dev/null 2>&1 && return 1 + [[ -n "$resolved_path" ]] && dpkg -S "$resolved_path" >/dev/null 2>&1 && return 1 + return 0 +} + +remove_non_selfbuilt_handbrake() { + info "Entferne nicht-selbst-gebaute HandBrake-Installationen..." + apt-get remove -y handbrake-cli handbrake 2>/dev/null || true + snap remove handbrake-cli 2>/dev/null || true + + rm -f /usr/bin/HandBrakeCLI \ + /snap/bin/handbrake-cli \ + /snap/bin/HandBrakeCLI + + if [[ -e /usr/local/bin/HandBrakeCLI ]] && ! handbrake_is_self_built "/usr/local/bin/HandBrakeCLI"; then + warn "Entferne fremdes /usr/local/bin/HandBrakeCLI" + rm -f /usr/local/bin/HandBrakeCLI + fi + + hash -r 2>/dev/null || true + ok "Bereinigung abgeschlossen" +} + +remove_selfbuilt_handbrake() { + if [[ -e /usr/local/bin/HandBrakeCLI ]] && handbrake_is_self_built "/usr/local/bin/HandBrakeCLI"; then + warn "Entferne selbst gebautes /usr/local/bin/HandBrakeCLI" + rm -f /usr/local/bin/HandBrakeCLI + fi + rm -f "$HANDBRAKE_SELFBUILD_MARKER" + hash -r 2>/dev/null || true +} + +build_handbrake_nvdec() { + header "HandBrake ${HANDBRAKE_VERSION} mit NVDEC aus Quellcode bauen" + + local cache_dir="/var/cache/ripster/handbrake" + local src_url="https://github.com/HandBrake/HandBrake/releases/download/${HANDBRAKE_VERSION}/HandBrake-${HANDBRAKE_VERSION}-source.tar.bz2" + local tarball="${cache_dir}/HandBrake-${HANDBRAKE_VERSION}-source.tar.bz2" + local src_dir="${cache_dir}/HandBrake-${HANDBRAKE_VERSION}" + + if [[ -t 0 && -d "$cache_dir" ]]; then + local clear_cache_answer="" + warn "Build-Cache gefunden: $cache_dir" + warn "Wenn du ihn löschst, startet der Source-Build wieder komplett von vorne." + read -r -p "Build-Cache jetzt löschen (sudo rm -rf $cache_dir)? [y/N] " clear_cache_answer + case "${clear_cache_answer,,}" in + y|yes|j|ja) + rm -rf "$cache_dir" + info "Build-Cache gelöscht." + ;; + esac + fi + mkdir -p "$cache_dir" + + # Build-Abhängigkeiten + info "Installiere Build-Abhängigkeiten..." + apt-get install -y \ + autoconf automake build-essential clang cmake git \ + libass-dev libbz2-dev libdvdnav-dev libdvdread-dev \ + libfontconfig-dev libfreetype-dev libfribidi-dev libharfbuzz-dev \ + libjansson-dev liblzma-dev libmp3lame-dev libnuma-dev libogg-dev \ + libopus-dev libsamplerate0-dev libspeex-dev libtheora-dev libtool libtool-bin libva-dev \ + libturbojpeg0-dev libvorbis-dev libvpx-dev libx264-dev libxml2-dev \ + m4 meson nasm ninja-build patch pkg-config python3 tar yasm zlib1g-dev \ + >/dev/null + + # CUDA Toolkit für NVDEC-Header + info "Installiere CUDA Toolkit (für NVDEC-Header)..." + if ! dpkg -l 2>/dev/null | grep -q '^ii.*nvidia-cuda-toolkit'; then + apt-get install -y nvidia-cuda-toolkit >/dev/null 2>&1 || { + warn "nvidia-cuda-toolkit nicht verfügbar – versuche Fallback-Header..." + local cuda_keyring="/tmp/cuda-keyring.deb" + local ubuntu_ver="${VERSION_ID//./}" + curl -fsSL "https://developer.download.nvidia.com/compute/cuda/repos/ubuntu${ubuntu_ver}/x86_64/cuda-keyring_1.1-1_all.deb" \ + -o "$cuda_keyring" 2>/dev/null && \ + dpkg -i "$cuda_keyring" 2>/dev/null && \ + apt-get update -qq && \ + apt-get install -y cuda-cudart-dev-12-8 >/dev/null 2>&1 || \ + warn "CUDA-Header konnten nicht installiert werden – NVDEC wird möglicherweise nicht verfügbar sein." + } + fi + ok "Build-Abhängigkeiten installiert" + + if [[ ! -d "$src_dir" ]]; then + if [[ ! -f "$tarball" ]]; then + info "Lade HandBrake ${HANDBRAKE_VERSION} herunter..." + curl -fsSL "$src_url" -o "$tarball" 2>/dev/null || \ + wget -q "$src_url" -O "$tarball" || \ + fatal "HandBrake-Quellcode konnte nicht heruntergeladen werden (${src_url})" + else + info "Nutze vorhandenes HandBrake-Source-Archiv: $tarball" + fi + + info "Entpacke Quellcode..." + tar xjf "$tarball" -C "$cache_dir" + [[ -d "$src_dir" ]] || src_dir=$(find "$cache_dir" -maxdepth 1 -type d -name "HandBrake*" | head -1) + [[ -d "$src_dir" ]] || fatal "HandBrake-Quellverzeichnis nicht gefunden in $cache_dir" + else + info "Nutze vorhandenen HandBrake-Source-Baum: $src_dir" + fi + + cd "$src_dir" + + local configure_log="${src_dir}/build/configure-ripster.log" + local configure_stamp="${src_dir}/build/.ripster-config" + local configure_args="--enable-nvdec --disable-gtk --prefix=/usr/local" + local need_configure="false" + local configure_cmd=( + ./configure + --launch-jobs="$(nproc)" + --enable-nvdec + --disable-gtk + --prefix=/usr/local + ) + + if [[ ! -f "$src_dir/build/GNUmakefile" ]]; then + need_configure="true" + elif [[ ! -f "$configure_stamp" ]]; then + need_configure="true" + elif ! grep -qx "args=${configure_args}" "$configure_stamp"; then + need_configure="true" + fi + + if [[ "$need_configure" == "true" ]]; then + if [[ -d "$src_dir/build" ]]; then + configure_cmd=(./configure --force "${configure_cmd[@]:1}") + fi + + if [[ -f "$src_dir/build/GNUmakefile" ]]; then + info "Vorhandener Build gefunden – aktualisiere Konfiguration (CLI-only)." + else + info "Konfiguriere HandBrake mit NVDEC (CLI-only)..." + fi + + if ! "${configure_cmd[@]}" >"$configure_log" 2>&1; then + tail -n 50 "$configure_log" >&2 || true + fatal "HandBrake-Konfiguration fehlgeschlagen. Vollständiges Log: $configure_log" + fi + tail -n 10 "$configure_log" + + mkdir -p "${src_dir}/build" + cat > "$configure_stamp" </dev/null || true + if ! command_exists HandBrakeCLI; then + warn "HandBrakeCLI nach direkter Installation nicht gefunden – setze Build fort." + make --directory=build -j"$(nproc)" + make --directory=build install + fi + else + info "Baue HandBrake ($(nproc) Threads – bitte warten)..." + make --directory=build -j"$(nproc)" + info "Installiere HandBrake nach /usr/local/bin..." + make --directory=build install + fi + + cd / + hash -r 2>/dev/null || true + + if command_exists HandBrakeCLI; then + local ver + ver=$(HandBrakeCLI --version 2>&1 | head -1) + handbrake_has_nvdec || fatal "HandBrakeCLI ist installiert, aber ohne NVDEC-Unterstützung." + + mkdir -p "$(dirname "$HANDBRAKE_SELFBUILD_MARKER")" + cat > "$HANDBRAKE_SELFBUILD_MARKER" </dev/null | grep -q libnvcuvid; then + ok "libnvcuvid gefunden – NVDEC ist zur Laufzeit verfügbar." + else + warn "libnvcuvid NICHT gefunden. NVDEC benötigt den installierten NVIDIA-Treiber." + fi + else + fatal "HandBrakeCLI nach dem Build nicht gefunden – Build fehlgeschlagen." + fi +} + +has_nvidia_gpu() { + [[ -e /dev/nvidia0 ]] && return 0 + command_exists nvidia-smi && nvidia-smi &>/dev/null && return 0 + command_exists lspci && lspci 2>/dev/null | grep -qi "nvidia" && return 0 + return 1 +} + +install_handbrake() { + header "HandBrake CLI installieren" + + local hb_path + local current_mode="none" + hb_path=$(command -v HandBrakeCLI 2>/dev/null || true) + if [[ -n "$hb_path" ]]; then + if handbrake_has_nvdec; then + current_mode="nvdec" + else + current_mode="standard" + fi + fi + + if [[ "$BUILD_HANDBRAKE_NVDEC" == true ]]; then + info "Installmodus: Source-Build mit NVDEC-Support" + if has_nvidia_gpu; then + info "NVIDIA-GPU erkannt – NVDEC-Build wird verwendet." + fi + + if handbrake_has_nvdec; then + if handbrake_is_self_built "$hb_path"; then + local installed_ver latest_ver answer + installed_ver=$(handbrake_installed_version || true) + latest_ver=$(handbrake_latest_git_version || true) + + if [[ -n "$installed_ver" && -n "$latest_ver" ]] && dpkg --compare-versions "$latest_ver" gt "$installed_ver"; then + case "$HANDBRAKE_UPDATE_POLICY" in + keep) + warn "Neuere HandBrake-Version verfügbar (${latest_ver}, installiert: ${installed_ver}) – behalte aktuelle Installation." + return + ;; + prompt) + if [[ -t 0 ]]; then + read -r -p "Neue HandBrake-Version ${latest_ver} verfügbar (installiert ${installed_ver}). Neu bauen? [y/N] " answer + case "${answer,,}" in + y|yes|j|ja) + info "Aktualisiere auf HandBrake ${latest_ver}." + HANDBRAKE_VERSION="$latest_ver" + ;; + *) + info "Behalte bestehende Installation (${installed_ver})." + return + ;; + esac + else + warn "Neuere HandBrake-Version verfügbar (${latest_ver}), aber kein TTY für Rückfrage – behalte aktuelle Installation." + return + fi + ;; + build) + info "Neuere HandBrake-Version verfügbar (${latest_ver}, installiert: ${installed_ver}) – aktualisiere." + HANDBRAKE_VERSION="$latest_ver" + ;; + esac + else + ok "Selbst gebautes HandBrakeCLI mit NVDEC bereits installiert: $(HandBrakeCLI --version 2>&1 | head -1)" + return + fi + fi + warn "HandBrakeCLI mit NVDEC gefunden (${hb_path}), aber nicht als Selbst-Build erkannt." + fi + + if [[ -n "$hb_path" ]] && ! handbrake_has_nvdec; then + warn "HandBrakeCLI ohne NVDEC gefunden (${hb_path}) – wird ersetzt durch Selbst-Build." + elif [[ -z "$hb_path" ]]; then + info "Kein HandBrakeCLI gefunden – baue aus Quellcode." + fi + + remove_non_selfbuilt_handbrake + build_handbrake_nvdec + return + fi + + info "Installmodus: Standard (APT, ohne NVDEC-Zwang)" + if [[ "$current_mode" == "standard" ]] && ! handbrake_is_self_built "$hb_path"; then + ok "HandBrakeCLI bereits installiert: $(HandBrakeCLI --version 2>&1 | head -1)" + return + fi + + if [[ "$current_mode" == "nvdec" ]]; then + warn "Umschalten von NVDEC-Build auf Standard-Installation." + fi + + remove_selfbuilt_handbrake + remove_non_selfbuilt_handbrake + + info "Installiere HandBrakeCLI aus den Standard-Repositories..." + apt_update + apt-get install -y handbrake-cli >/dev/null + hash -r 2>/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 + ln -sf "$(command -v handbrake-cli)" /usr/local/bin/HandBrakeCLI + hash -r 2>/dev/null || true + ok "HandBrakeCLI Alias angelegt auf: $(command -v handbrake-cli)" + return + fi + + fatal "HandBrake wurde installiert, aber kein CLI-Befehl wurde gefunden." +} + +# --- apt-Hilfsfunktionen ------------------------------------------------------ + +apt_update() { + local output + if output=$(apt-get update 2>&1); then + return 0 + fi + + if echo "$output" | grep -q "no longer has a Release file\|does not have a Release file"; then + warn "apt-Sources fehlerhaft. Versuche Reparatur..." + + if apt-get update --allow-releaseinfo-change -qq 2>/dev/null; then + ok "apt-Update mit --allow-releaseinfo-change erfolgreich" + return 0 + fi + + if [[ -n "${VERSION_CODENAME:-}" ]]; then + warn "Schreibe minimale sources.list für $VERSION_CODENAME..." + local main_list=/etc/apt/sources.list + 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 + + 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 +} + +# --- Systemabhängigkeiten ----------------------------------------------------- +header "Systemabhängigkeiten installieren" + +info "Paketlisten aktualisieren..." +apt_update + +info "Installiere Basispakete..." +apt-get install -y \ + curl wget git \ + mediainfo \ + util-linux udev \ + ca-certificates gnupg \ + lsb-release + +ok "Basispakete installiert" + +# Node.js +install_node + +# MakeMKV +if [[ "$SKIP_MAKEMKV" == false ]]; then + install_makemkv +else + warn "MakeMKV-Installation übersprungen (--no-makemkv)" +fi + +# HandBrake +if [[ "$SKIP_HANDBRAKE" == false ]]; then + install_handbrake +else + warn "HandBrake-Installation übersprungen (--no-handbrake)" +fi + +# Nginx +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" + +# Optisches Laufwerk: Benutzer zur cdrom/optical-Gruppe hinzufügen +for grp in cdrom optical disk; 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 + +# --- Dateien kopieren --------------------------------------------------------- +header "Ripster-Dateien installieren" + +if [[ -d "$INSTALL_DIR" && "$REINSTALL" == false ]]; then + fatal "Verzeichnis $INSTALL_DIR existiert bereits.\nVerwende --reinstall um zu überschreiben (Daten bleiben erhalten)." +fi + +# Bei Reinstall: Daten sichern +if [[ -d "$INSTALL_DIR/backend/data" ]]; then + info "Sichere vorhandene Datenbank..." + cp -a "$INSTALL_DIR/backend/data" "/tmp/ripster-data-backup-$(date +%Y%m%d%H%M%S)" + ok "Datenbank gesichert" +fi + +info "Kopiere Quellen nach $INSTALL_DIR..." +mkdir -p "$INSTALL_DIR" + +rsync -a --delete \ + --exclude='.git' \ + --exclude='node_modules' \ + --exclude='backend/node_modules' \ + --exclude='frontend/node_modules' \ + --exclude='backend/data' \ + --exclude='backend/logs' \ + --exclude='frontend/dist' \ + --exclude='*.sh' \ + --exclude='deploy-ripster.sh' \ + --exclude='debug/' \ + --exclude='site/' \ + --exclude='docs/' \ + "$SOURCE_DIR/" "$INSTALL_DIR/" + +# Datenbank-/Log-Verzeichnisse anlegen +mkdir -p "$INSTALL_DIR/backend/data" +mkdir -p "$INSTALL_DIR/backend/logs" + +# Bei Reinstall: Daten wiederherstellen +if [[ -d "$INSTALL_DIR/../ripster-data-backup" ]]; then + cp -a /tmp/ripster-data-backup-*/ "$INSTALL_DIR/backend/data/" 2>/dev/null || true +fi + +ok "Dateien kopiert" + +# --- npm-Abhängigkeiten installieren ----------------------------------------- +header "npm-Abhängigkeiten installieren" + +info "Installiere Root-Abhängigkeiten..." +npm install --prefix "$INSTALL_DIR" --omit=dev --silent + +info "Installiere Backend-Abhängigkeiten..." +npm install --prefix "$INSTALL_DIR/backend" --omit=dev --silent + +info "Installiere 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" == false ]]; then + warn "Backend .env existiert bereits – wird nicht überschrieben" +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 < 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 +} + +nginx_replace_or_insert_directive() { + local file="$1" + local directive_regex="$2" + local desired_line="$3" + local anchor_regex="$4" + local directive_sed_regex="${directive_regex//\//\\/}" + local anchor_sed_regex="${anchor_regex//\//\\/}" + local desired_sed_line="${desired_line//\//\\/}" + + if grep -Eq "$directive_regex" "$file"; then + sed -i -E "0,/$directive_sed_regex/s//${desired_sed_line}/" "$file" + return 0 + fi + + sed -i "/$anchor_sed_regex/a\\$desired_line" "$file" +} + +patch_existing_ripster_nginx_site() { + local file="$1" + local backup_file="${file}.bak-$(date +%Y%m%d%H%M%S)" + + [[ -f "$file" ]] || return 1 + + cp -a "$file" "$backup_file" + info "Bestehende nginx-Konfiguration erkannt - ergänze Upload-/Proxy-Settings" + info "Backup erstellt: $backup_file" + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*client_max_body_size[[:space:]]+[^;]+;' \ + ' client_max_body_size 8G;' \ + 'server_name .*;' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_connect_timeout[[:space:]]+[^;]+;' \ + ' proxy_connect_timeout 60s;' \ + 'location /api/ {' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_send_timeout[[:space:]]+[^;]+;' \ + ' proxy_send_timeout 3600s;' \ + 'location /api/ {' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_read_timeout[[:space:]]+[^;]+;' \ + ' proxy_read_timeout 3600s;' \ + 'location /api/ {' + + nginx_replace_or_insert_directive \ + "$file" \ + '^[[:space:]]*proxy_request_buffering[[:space:]]+[^;]+;' \ + ' proxy_request_buffering off;' \ + 'location /api/ {' +} + +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 <