#!/usr/bin/env bash # # Rippy einrichten — ein Befehl, keine Handarbeit. # # Das Skript nimmt genau die Schritte ab, an denen man vorher scheitern konnte: # * Laufwerk finden (BEIDE Geraeteknoten — der sg-Knoten ist je Host anders, # und ein fest eingetragener Standard ist damit auf fremden Hosts falsch) # * Ablage- und MakeMKV-Verzeichnis anlegen # * shared-Mount-Propagation herstellen, falls Docker sie verlangt # * .env anlegen bzw. fehlende Werte ergaenzen, ohne Bestehendes zu ueberschreiben # * bauen und starten # # Es ist absichtlich WIEDERHOLBAR: mehrfach ausfuehren aendert nichts kaputt. # Es aendert NIE einen Wert, der schon in der .env steht. # # Aufruf: sudo ./install.sh # sudo ./install.sh --nur-pruefen (nichts anfassen, nur berichten) set -u NUR_PRUEFEN=0 [ "${1:-}" = "--nur-pruefen" ] && NUR_PRUEFEN=1 MEDIA_DIR="${RIPPY_MEDIA_DIR:-/srv/rippy/media}" MAKEMKV_DIR="${RIPPY_MAKEMKV_DIR:-/srv/rippy/makemkv}" FEHLER=0 rot() { printf '\033[31m%s\033[0m\n' "$*"; } gruen() { printf '\033[32m%s\033[0m\n' "$*"; } gelb() { printf '\033[33m%s\033[0m\n' "$*"; } titel() { printf '\n\033[1m%s\033[0m\n' "$*"; } echo "======================================================================" echo " Rippy — Einrichtung" echo "======================================================================" [ "$NUR_PRUEFEN" = 1 ] && gelb "Nur-Pruefen-Modus: es wird nichts geaendert." # --------------------------------------------------------------------------- # 1. Voraussetzungen # --------------------------------------------------------------------------- titel "1/6 Voraussetzungen" if [ "$(uname -s)" != "Linux" ]; then rot " X Das hier ist kein Linux. Rippy braucht einen Linux-Host mit Docker." rot " (Docker Desktop unter Windows/macOS kann keine optischen Laufwerke" rot " durchreichen — deshalb geht es dort grundsaetzlich nicht.)" exit 1 fi gruen " OK Linux erkannt ($(uname -r))" if ! command -v docker >/dev/null 2>&1; then rot " X Docker ist nicht installiert." rot " Installation: curl -fsSL https://get.docker.com | sudo sh" FEHLER=1 else gruen " OK Docker gefunden ($(docker --version 2>/dev/null | head -1))" fi if docker compose version >/dev/null 2>&1; then COMPOSE="docker compose" gruen " OK Compose-Plugin gefunden" elif command -v docker-compose >/dev/null 2>&1; then COMPOSE="docker-compose" gelb " ! Nur das alte docker-compose gefunden — funktioniert, ist aber veraltet." else rot " X Docker Compose fehlt." rot " Installation (Debian/Ubuntu): sudo apt install docker-compose-plugin" FEHLER=1 fi if [ "$(id -u)" = "0" ]; then gruen " OK Als root gestartet" elif [ "$NUR_PRUEFEN" = 1 ]; then # Der Pruef-Modus aendert nichts und darf deshalb NICHT an root scheitern — # sonst kann man vor der Installation nicht nachsehen, ob alles passt. gelb " ! Ohne root — fuer die Pruefung in Ordnung. Die echte Installation" gelb " braucht sudo (Verzeichnisse unter /srv, Mount-Propagation)." else rot " X Bitte mit sudo starten: sudo ./install.sh" rot " (Verzeichnisse unter /srv und die Mount-Propagation brauchen root.)" rot " Nur nachsehen, ohne etwas zu aendern: ./install.sh --nur-pruefen" FEHLER=1 fi [ "$FEHLER" = 1 ] && { rot ""; rot "Abgebrochen — bitte das Fehlende nachholen."; exit 1; } # --------------------------------------------------------------------------- # 2. Laufwerk suchen # --------------------------------------------------------------------------- titel "2/6 Optisches Laufwerk" # MakeMKV spricht Laufwerke ueber die SCSI-Generic-Schicht an und braucht # deshalb ZWEI Knoten: /dev/srN und den passenden /dev/sgM. Welche sg-Nummer # dazugehoert, ist je Host anders — deshalb wird sie hier ueber die # SCSI-Adresse ermittelt statt geraten. Beide Knoten zeigen in /sys auf # dasselbe Geraeteverzeichnis (z. B. 3:0:0:0), das ist der Abgleich. SR="" SG="" LAUFWERK="" for srpfad in /sys/block/sr*; do [ -e "$srpfad" ] || continue srname=$(basename "$srpfad") adresse=$(basename "$(readlink -f "$srpfad/device" 2>/dev/null)" 2>/dev/null) [ -n "$adresse" ] || continue for sgpfad in /sys/class/scsi_generic/sg*; do [ -e "$sgpfad" ] || continue if [ "$(basename "$(readlink -f "$sgpfad/device" 2>/dev/null)" 2>/dev/null)" = "$adresse" ]; then SR="/dev/$srname" SG="/dev/$(basename "$sgpfad")" LAUFWERK="$(cat "$srpfad/device/vendor" 2>/dev/null) $(cat "$srpfad/device/model" 2>/dev/null)" break 2 fi done done if [ -n "$SR" ]; then gruen " OK Laufwerk gefunden: $(echo "$LAUFWERK" | tr -s ' ')" gruen " $SR + $SG (SCSI-Adresse abgeglichen, nicht geraten)" else gelb " ! Kein optisches Laufwerk gefunden." gelb " Rippy laesst sich trotzdem installieren und kann dann als reine" gelb " KOMPRIMIER-Maschine fuer andere Rippy-Instanzen dienen." gelb " Zum Rippen fehlt das Laufwerk. Haeufigste Ursachen:" gelb " - In einer VM: Laufwerk per USB-Passthrough durchreichen" gelb " (Proxmox: qm set -usb0 host=:,usb3=1)" gelb " NICHT als emuliertes CD-ROM (media=cdrom) — das kann keine" gelb " SCSI-Kommandos und MakeMKV sieht es nie." gelb " - Auf echter Hardware: 'ls /dev/sr*' pruefen, Kabel/Strom." fi # --------------------------------------------------------------------------- # 3. Verzeichnisse # --------------------------------------------------------------------------- titel "3/6 Verzeichnisse" for d in "$MEDIA_DIR" "$MAKEMKV_DIR"; do if [ -d "$d" ]; then gruen " OK $d liegt schon da" elif [ "$NUR_PRUEFEN" = 1 ]; then gelb " ! $d fehlt (wuerde angelegt)" else mkdir -p "$d" && gruen " OK $d angelegt" fi done echo " Ablage: $MEDIA_DIR (hier landen die fertigen Filme)" echo " MakeMKV-Daten: $MAKEMKV_DIR (Schluesselspeicher, rebuild-fest)" # --------------------------------------------------------------------------- # 4. Mount-Propagation # --------------------------------------------------------------------------- titel "4/6 Mount-Propagation (fuer NAS-Freigaben aus dem UI)" # Rippy haengt NAS-Freigaben aus dem UI ein. Damit die auch in den anderen # Containern sichtbar werden, muss der Ablage-Pfad ein 'shared' Mount sein. # Auf den meisten systemd-Hosts ist / schon rshared und es ist nichts zu tun. propagation_ok() { findmnt -no PROPAGATION --target "$MEDIA_DIR" 2>/dev/null | grep -q shared } if propagation_ok; then gruen " OK $MEDIA_DIR ist bereits 'shared' — nichts zu tun" elif [ "$NUR_PRUEFEN" = 1 ]; then gelb " ! Propagation fehlt (wuerde eingerichtet)" else mount --bind "$MEDIA_DIR" "$MEDIA_DIR" 2>/dev/null mount --make-rshared "$MEDIA_DIR" 2>/dev/null if propagation_ok; then gruen " OK Propagation eingerichtet" # Neustart-fest machen, sonst ist es nach dem naechsten Reboot wieder weg # und das Einhaengen aus dem UI scheitert stillschweigend. UNIT="/etc/systemd/system/$(systemd-escape -p --suffix=mount "$MEDIA_DIR" 2>/dev/null)" if [ -n "${UNIT:-}" ] && [ ! -e "$UNIT" ] && command -v systemctl >/dev/null 2>&1; then cat > "$UNIT" </dev/null systemctl enable "$(basename "$UNIT")" >/dev/null 2>&1 \ && gruen " OK Neustart-fest gemacht ($(basename "$UNIT"))" \ || gelb " ! Konnte nicht neustart-fest gemacht werden — nach einem Reboot" gelb " ggf. dieses Skript erneut ausfuehren." fi else gelb " ! Propagation liess sich nicht einrichten." gelb " Rippy laeuft trotzdem — nur das Einhaengen von NAS-Freigaben AUS DEM UI" gelb " funktioniert dann nicht. Freigaben stattdessen auf dem Host einhaengen," gelb " z. B. unter $MEDIA_DIR/nas. Betrifft nur klassisches Docker mit" gelb " SYS_ADMIN — nicht rootless/Podman." fi fi # --------------------------------------------------------------------------- # 5. .env # --------------------------------------------------------------------------- titel "5/6 Konfiguration (.env)" cd "$(dirname "$0")" || exit 1 if [ ! -f .env ]; then if [ "$NUR_PRUEFEN" = 1 ]; then gelb " ! .env fehlt (wuerde aus .env.example erzeugt)" else cp .env.example .env && gruen " OK .env aus .env.example erzeugt" fi else gruen " OK .env liegt schon da — bestehende Werte bleiben unangetastet" fi # Setzt einen Wert NUR, wenn er noch nicht (unkommentiert) gesetzt ist. setze_wenn_leer() { schluessel="$1"; wert="$2" [ "$NUR_PRUEFEN" = 1 ] && { echo " wuerde setzen: $schluessel=$wert"; return; } [ -f .env ] || return if grep -qE "^${schluessel}=.+" .env; then echo " $schluessel steht schon in der .env — unveraendert gelassen" else sed -i "/^#*${schluessel}=/d" .env printf '%s=%s\n' "$schluessel" "$wert" >> .env gruen " OK $schluessel=$wert eingetragen" fi } if [ -n "$SR" ]; then setze_wenn_leer OPTICAL_SR "$SR" setze_wenn_leer OPTICAL_SG "$SG" fi setze_wenn_leer MAKEMKV_DATA_HOST "$MAKEMKV_DIR" echo " API-Keys (TMDB/OMDb) brauchst du hier NICHT einzutragen —" echo " der Einrichtungs-Assistent im Browser fragt sie ab und prueft sie." # Gegenprobe: Zeigen die WIRKSAMEN Werte auf Geraete, die es hier gibt? Faengt # den Fall "'.env' von einem anderen Rechner kopiert" und Tippfehler ab — sonst # startet Docker mit einem Pfad, den es nicht gibt, und die Meldung ist kryptisch. if [ -f .env ]; then for paar in "OPTICAL_SR:/dev/sr0" "OPTICAL_SG:/dev/sg1"; do schluessel="${paar%%:*}"; standard="${paar##*:}" wirksam=$(grep -E "^${schluessel}=" .env 2>/dev/null | tail -1 | cut -d= -f2-) [ -n "${wirksam:-}" ] || wirksam="$standard" if [ ! -e "$wirksam" ]; then gelb " ! $schluessel zeigt auf $wirksam — dieses Geraet gibt es hier NICHT." if [ -n "$SR" ]; then gelb " Gefunden wurde stattdessen: $SR + $SG" gelb " Trage das in der .env ein (oder loesche die Zeile $schluessel=)." else gelb " Es wurde auch kein anderes Laufwerk gefunden — siehe Schritt 2." fi fi done fi # --------------------------------------------------------------------------- # 6. Bauen und starten # --------------------------------------------------------------------------- titel "6/6 Bauen und starten" if [ "$NUR_PRUEFEN" = 1 ]; then gelb " ! Uebersprungen (Nur-Pruefen-Modus)." echo "" gruen "Pruefung fertig. Ohne --nur-pruefen wuerde jetzt gebaut und gestartet." exit 0 fi echo " Der erste Bau dauert je nach Internet 3-10 Minuten. Nur beim ersten Mal." echo "" if ! $COMPOSE up -d --build; then rot "" rot "Der Start ist gescheitert. Die haeufigsten Ursachen:" rot " - Kein Platz auf der Platte: df -h /" rot " - Port 80 schon belegt: ss -tlnp | grep ':80 '" rot " - Download von makemkv.com gedrosselt (Cloudflare): Tarballs von" rot " makemkv.com/download nach docker/worker/vendor/ legen, dann erneut." rot "Die volle Meldung steht oben. Logs: $COMPOSE logs --tail=50" exit 1 fi IP=$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oE 'src [0-9.]+' | awk '{print $2}') [ -n "${IP:-}" ] || IP="" echo "" echo "======================================================================" gruen " Fertig. Rippy laeuft." echo "======================================================================" echo "" echo " Jetzt im Browser oeffnen: http://$IP" echo "" echo " Dort startet der Einrichtungs-Assistent. Er zeigt dir, ob Laufwerk," echo " Worker und Platz in Ordnung sind, und stellt die Kompression passend" echo " zu deiner CPU ein. Mehr ist nicht zu tun." echo "" echo " Status ansehen: $COMPOSE ps" echo " Logs ansehen: $COMPOSE logs -f" echo " Aktualisieren: git pull && sudo ./install.sh" echo ""