diff --git a/install.sh b/install.sh index cea7985..331e9fe 100755 --- a/install.sh +++ b/install.sh @@ -2,19 +2,30 @@ # # Rippy einrichten — ein Befehl, keine Handarbeit. # -# Das Skript nimmt genau die Schritte ab, an denen man vorher scheitern konnte: -# * Laufwerk finden (BEIDE Geräteknoten — 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 ergänzen, ohne Bestehendes zu überschreiben -# * bauen und starten +# ## Aufbau: erst ALLES prüfen, dann erst ändern # -# Es ist absichtlich WIEDERHOLBAR: mehrfach ausführen ändert nichts kaputt. -# Es ändert NIE einen Wert, der schon in der .env steht. +# Das Skript läuft in zwei klar getrennten Phasen: +# +# Phase 1 PRÜFEN — sieht sich alles an und ändert NICHTS. +# (Weiche) — harte Mängel → Abbruch mit Liste, nichts angefasst. +# Phase 2 EINRICHTEN — Verzeichnisse, Mount, .env, bauen, starten. +# +# Warum das wichtig ist (Commander-Einwand 25.07.2026): Die erste Fassung +# prüfte und änderte abwechselnd — Verzeichnisse wurden angelegt, bevor +# überhaupt klar war, ob der Rest durchläuft. Scheiterte etwas in der Mitte, +# blieb ein halb eingerichteter Rechner zurück. Und der Prüf-Modus war als +# `if`-Zweig durch fünf Schritte gefädelt; so etwas läuft mit der Zeit +# zwangsläufig aus dem Ruder, weil jede neue Aktion daran denken müsste. +# +# Jetzt gibt es GENAU EINEN Prüf-Pfad, den beide Modi benutzen. `--nur-pruefen` +# heißt schlicht: nach Phase 1 aufhören. Damit kann die Prüfung nicht mehr etwas +# anderes behaupten als die Installation tut. +# +# Es ist absichtlich WIEDERHOLBAR: mehrfach ausführen ändert nichts kaputt, und +# es ändert NIE einen Wert, der schon in der .env steht. # # Aufruf: sudo ./install.sh -# sudo ./install.sh --nur-pruefen (nichts anfassen, nur berichten) +# ./install.sh --nur-pruefen (nichts anfassen, nur berichten) set -u @@ -23,77 +34,80 @@ NUR_PRUEFEN=0 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' "$*"; } +# Befunde der Prüfphase. PROBLEME verhindern die Installation, HINWEISE nicht. +PROBLEME="" +HINWEISE="" +problem() { PROBLEME="${PROBLEME}$1"$'\n'; rot " X $1"; } +hinweis() { HINWEISE="${HINWEISE}$1"$'\n'; gelb " ! $1"; } +ok() { gruen " OK $1"; } + +cd "$(dirname "$0")" || exit 1 + echo "======================================================================" echo " Rippy — Einrichtung" echo "======================================================================" -[ "$NUR_PRUEFEN" = 1 ] && gelb "Nur-Prüfen-Modus: es wird nichts geändert." +echo " Phase 1 von 2: prüfen. Es wird noch NICHTS geändert." -# --------------------------------------------------------------------------- -# 1. Voraussetzungen -# --------------------------------------------------------------------------- -titel "1/6 Voraussetzungen" +# =========================================================================== +# PHASE 1 — PRÜFEN. Ab hier bis zur Weiche wird nichts verändert. +# =========================================================================== + +# --- 1. Betriebssystem und Docker ------------------------------------------ +titel "Prüfung 1/5 Betriebssystem und Docker" if [ "$(uname -s)" != "Linux" ]; then - rot " X Das hier ist kein Linux. Rippy braucht einen Linux-Host mit Docker." + problem "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 grundsätzlich nicht.)" - exit 1 +else + ok "Linux erkannt ($(uname -r))" fi -gruen " OK Linux erkannt ($(uname -r))" if ! command -v docker >/dev/null 2>&1; then - rot " X Docker ist nicht installiert." + problem "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))" + ok "Docker gefunden ($(docker --version 2>/dev/null | head -1))" fi +COMPOSE="" if docker compose version >/dev/null 2>&1; then COMPOSE="docker compose" - gruen " OK Compose-Plugin gefunden" + 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." + hinweis "Nur das alte docker-compose gefunden — funktioniert, ist aber veraltet." else - rot " X Docker Compose fehlt." + problem "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" + ok "Als root gestartet" elif [ "$NUR_PRUEFEN" = 1 ]; then - # Der Prüf-Modus ändert nichts und darf deshalb NICHT an root scheitern — + # Die Prüfung ändert nichts und darf deshalb NICHT an root scheitern — # sonst kann man vor der Installation nicht nachsehen, ob alles passt. - gelb " ! Ohne root — für die Prüfung in Ordnung. Die echte Installation" - gelb " braucht sudo (Verzeichnisse unter /srv, Mount-Propagation)." + ok "Ohne root — für die Prüfung völlig in Ordnung" else - rot " X Bitte mit sudo starten: sudo ./install.sh" + problem "Bitte mit sudo starten: sudo ./install.sh" rot " (Verzeichnisse unter /srv und die Mount-Propagation brauchen root.)" rot " Nur nachsehen, ohne etwas zu ändern: ./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" +# --- 2. Optisches Laufwerk ------------------------------------------------- +titel "Prüfung 2/5 Optisches Laufwerk" # MakeMKV spricht Laufwerke über die SCSI-Generic-Schicht an und braucht # deshalb ZWEI Knoten: /dev/srN und den passenden /dev/sgM. Welche sg-Nummer -# dazugehört, ist je Host anders — deshalb wird sie hier über die -# SCSI-Adresse ermittelt statt geraten. Beide Knoten zeigen in /sys auf -# dasselbe Geräteverzeichnis (z. B. 3:0:0:0), das ist der Abgleich. +# dazugehört, ist je Host anders — deshalb wird sie hier über die SCSI-Adresse +# ermittelt statt geraten. Beide Knoten zeigen in /sys auf dasselbe +# Geräteverzeichnis (z. B. 3:0:0:0), das ist der Abgleich. SR="" SG="" LAUFWERK="" @@ -114,12 +128,12 @@ for srpfad in /sys/block/sr*; do done if [ -n "$SR" ]; then - gruen " OK Laufwerk gefunden: $(echo "$LAUFWERK" | tr -s ' ')" + ok "Laufwerk gefunden: $(echo "$LAUFWERK" | tr -s ' ')" gruen " $SR + $SG (SCSI-Adresse abgeglichen, nicht geraten)" else - gelb " ! Kein optisches Laufwerk gefunden." - gelb " Rippy lässt sich trotzdem installieren und kann dann als reine" - gelb " KOMPRIMIER-Maschine für andere Rippy-Instanzen dienen." + hinweis "Kein optisches Laufwerk gefunden — Rippy kann dann nur komprimieren." + gelb " Die Installation läuft trotzdem durch; diese Maschine dient dann" + gelb " als reine KOMPRIMIER-Maschine für andere Rippy-Instanzen." gelb " Zum Rippen fehlt das Laufwerk. Häufigste Ursachen:" gelb " - In einer VM: Laufwerk per USB-Passthrough durchreichen" gelb " (Proxmox: qm set -usb0 host=:,usb3=1)" @@ -128,26 +142,38 @@ else gelb " - Auf echter Hardware: 'ls /dev/sr*' prüfen, Kabel/Strom." fi -# --------------------------------------------------------------------------- -# 3. Verzeichnisse -# --------------------------------------------------------------------------- -titel "3/6 Verzeichnisse" +# --- 3. Verzeichnisse und Platz ------------------------------------------- +titel "Prüfung 3/5 Verzeichnisse und Plattenplatz" + 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 (würde angelegt)" + ok "$d liegt schon da" else - mkdir -p "$d" && gruen " OK $d angelegt" + # Anlegen können wir nur mit root — und ob der Elternpfad beschreibbar ist, + # entscheidet, ob es später klappt. Beides hier schon sagen. + eltern="$(dirname "$d")" + while [ -n "$eltern" ] && [ ! -d "$eltern" ]; do eltern="$(dirname "$eltern")"; done + if [ "$(id -u)" = "0" ] || [ -w "$eltern" ]; then + ok "$d fehlt noch — wird in Phase 2 angelegt" + else + problem "$d fehlt und $eltern ist nicht beschreibbar." + fi fi done -echo " Ablage: $MEDIA_DIR (hier landen die fertigen Filme)" -echo " MakeMKV-Daten: $MAKEMKV_DIR (Schlüsselspeicher, rebuild-fest)" -# --------------------------------------------------------------------------- -# 4. Mount-Propagation -# --------------------------------------------------------------------------- -titel "4/6 Mount-Propagation (für NAS-Freigaben aus dem UI)" +FREI_GB=$(df -BG --output=avail "$(dirname "$MEDIA_DIR")" 2>/dev/null | tail -1 | tr -dc '0-9') +if [ -n "${FREI_GB:-}" ]; then + if [ "$FREI_GB" -lt 60 ]; then + hinweis "Nur ${FREI_GB} GB frei. Eine Blu-ray braucht roh rund 40 GB, eine 4K-UHD bis 100 GB." + gelb " Rippy läuft trotzdem — lege die Ablage später besser auf eine" + gelb " NAS-Freigabe (Einstellungen → Speicherziele)." + else + ok "${FREI_GB} GB frei" + fi +fi + +# --- 4. Mount-Propagation -------------------------------------------------- +titel "Prüfung 4/5 Mount-Propagation (für NAS-Freigaben aus dem UI)" # Rippy hängt NAS-Freigaben aus dem UI ein. Damit die auch in den anderen # Containern sichtbar werden, muss der Ablage-Pfad ein 'shared' Mount sein. @@ -156,15 +182,129 @@ propagation_ok() { findmnt -no PROPAGATION --target "$MEDIA_DIR" 2>/dev/null | grep -q shared } +PROPAGATION_NOETIG=0 if propagation_ok; then - gruen " OK $MEDIA_DIR ist bereits 'shared' — nichts zu tun" -elif [ "$NUR_PRUEFEN" = 1 ]; then - gelb " ! Propagation fehlt (würde eingerichtet)" + ok "$MEDIA_DIR ist bereits 'shared' — nichts zu tun" +elif [ ! -d "$MEDIA_DIR" ]; then + ok "wird nach dem Anlegen des Verzeichnisses geprüft und bei Bedarf gesetzt" + PROPAGATION_NOETIG=1 else + ok "Propagation fehlt — wird in Phase 2 eingerichtet" + PROPAGATION_NOETIG=1 +fi + +# --- 5. Konfiguration ------------------------------------------------------ +titel "Prüfung 5/5 Konfiguration (.env)" + +if [ ! -f .env ]; then + if [ -f .env.example ]; then + ok ".env fehlt — wird in Phase 2 aus .env.example erzeugt" + else + problem ".env.example fehlt. Ist das wirklich das Rippy-Repo?" + fi +else + ok ".env liegt schon da — bestehende Werte bleiben unangetastet" +fi + +# Was in Phase 2 in die .env geschrieben würde — hier nur ANSAGEN. +wirksamer_wert() { + schluessel="$1"; standard="$2" + wert=$(grep -E "^${schluessel}=" .env 2>/dev/null | tail -1 | cut -d= -f2-) + [ -n "${wert:-}" ] && printf '%s' "$wert" || printf '%s' "$standard" +} + +for paar in "OPTICAL_SR:$SR" "OPTICAL_SG:$SG" "MAKEMKV_DATA_HOST:$MAKEMKV_DIR"; do + schluessel="${paar%%:*}"; erkannt="${paar#*:}" + if grep -qE "^${schluessel}=.+" .env 2>/dev/null; then + echo " $schluessel steht schon in der .env — bleibt unverändert" + elif [ -n "$erkannt" ]; then + echo " $schluessel wird auf $erkannt gesetzt" + fi +done + +# Gegenprobe: Zeigen die WIRKSAMEN Gerätepfade auf etwas, das es hier gibt? +# Fängt 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. +for paar in "OPTICAL_SR:/dev/sr0" "OPTICAL_SG:/dev/sg1"; do + schluessel="${paar%%:*}"; standard="${paar##*:}" + # Nur prüfen, was auch wirklich gelten wird + if grep -qE "^${schluessel}=.+" .env 2>/dev/null; then + wirksam=$(wirksamer_wert "$schluessel" "$standard") + elif [ -n "$SR" ]; then + continue # wird gleich korrekt gesetzt + else + wirksam="$standard" + fi + if [ ! -e "$wirksam" ]; then + hinweis "$schluessel zeigt auf $wirksam — dieses Gerät gibt es hier NICHT." + if [ -n "$SR" ]; then + gelb " Gefunden wurde stattdessen: $SR + $SG" + gelb " Trage das in der .env ein (oder lösche die Zeile $schluessel=)." + fi + fi +done + +echo " API-Keys (TMDB/OMDb) brauchst du hier NICHT einzutragen —" +echo " der Einrichtungs-Assistent im Browser fragt sie ab und prüft sie." + +# =========================================================================== +# WEICHE — hier wird entschieden. Bis hierher wurde NICHTS verändert. +# =========================================================================== +titel "Ergebnis der Prüfung" + +ANZ_PROBLEME=$(printf '%s' "$PROBLEME" | grep -c . || true) +ANZ_HINWEISE=$(printf '%s' "$HINWEISE" | grep -c . || true) + +if [ "$ANZ_PROBLEME" -gt 0 ]; then + rot " $ANZ_PROBLEME Punkt(e) verhindern die Installation:" + printf '%s' "$PROBLEME" | grep . | while IFS= read -r z; do rot " - $z"; done + rot "" + rot " Es wurde NICHTS verändert. Bitte das Obige nachholen und erneut starten." + exit 1 +fi + +if [ "$ANZ_HINWEISE" -gt 0 ]; then + gelb " Alles Wichtige ist da. $ANZ_HINWEISE Hinweis(e), die dich nicht aufhalten:" + printf '%s' "$HINWEISE" | grep . | while IFS= read -r z; do gelb " - $z"; done +else + gruen " Alles in Ordnung, keine Einschränkungen." +fi + +if [ "$NUR_PRUEFEN" = 1 ]; then + echo "" + gruen "Prüfung fertig — es wurde nichts verändert." + echo "Zum Installieren: sudo ./install.sh" + exit 0 +fi + +# =========================================================================== +# PHASE 2 — EINRICHTEN. Erst ab hier wird etwas verändert. +# =========================================================================== +echo "" +echo "======================================================================" +echo " Phase 2 von 2: einrichten" +echo "======================================================================" + +titel "Schritt 1/4 Verzeichnisse anlegen" +for d in "$MEDIA_DIR" "$MAKEMKV_DIR"; do + if [ -d "$d" ]; then + ok "$d war schon da" + else + mkdir -p "$d" && ok "$d angelegt" + fi +done +echo " Ablage: $MEDIA_DIR (hier landen die fertigen Filme)" +echo " MakeMKV-Daten: $MAKEMKV_DIR (Schlüsselspeicher, rebuild-fest)" + +titel "Schritt 2/4 Mount-Propagation einrichten" +if propagation_ok; then + ok "war schon 'shared' — nichts zu tun" +elif [ "$PROPAGATION_NOETIG" = 1 ]; then 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" + ok "Propagation eingerichtet" # Neustart-fest machen, sonst ist es nach dem nächsten Reboot wieder weg # und das Einhängen aus dem UI scheitert stillschweigend. UNIT="/etc/systemd/system/$(systemd-escape -p --suffix=mount "$MEDIA_DIR" 2>/dev/null)" @@ -184,13 +324,15 @@ Options=bind,rshared WantedBy=multi-user.target UNITEOF systemctl daemon-reload 2>/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 ausführen." + if systemctl enable "$(basename "$UNIT")" >/dev/null 2>&1; then + ok "neustart-fest gemacht ($(basename "$UNIT"))" + else + gelb " ! Konnte nicht neustart-fest gemacht werden — nach einem Reboot" + gelb " dieses Skript einfach erneut ausführen." + fi fi else - gelb " ! Propagation liess sich nicht einrichten." + gelb " ! Propagation ließ sich nicht einrichten." gelb " Rippy läuft trotzdem — nur das Einhängen von NAS-Freigaben AUS DEM UI" gelb " funktioniert dann nicht. Freigaben stattdessen auf dem Host einhängen," gelb " z. B. unter $MEDIA_DIR/nas. Betrifft nur klassisches Docker mit" @@ -198,34 +340,21 @@ UNITEOF fi fi -# --------------------------------------------------------------------------- -# 5. .env -# --------------------------------------------------------------------------- -titel "5/6 Konfiguration (.env)" - -cd "$(dirname "$0")" || exit 1 - +titel "Schritt 3/4 Konfiguration schreiben" if [ ! -f .env ]; then - if [ "$NUR_PRUEFEN" = 1 ]; then - gelb " ! .env fehlt (würde 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" + cp .env.example .env && ok ".env aus .env.example erzeugt" fi # Setzt einen Wert NUR, wenn er noch nicht (unkommentiert) gesetzt ist. setze_wenn_leer() { schluessel="$1"; wert="$2" - [ "$NUR_PRUEFEN" = 1 ] && { echo " würde setzen: $schluessel=$wert"; return; } [ -f .env ] || return if grep -qE "^${schluessel}=.+" .env; then echo " $schluessel steht schon in der .env — unverändert gelassen" else sed -i "/^#*${schluessel}=/d" .env printf '%s=%s\n' "$schluessel" "$wert" >> .env - gruen " OK $schluessel=$wert eingetragen" + ok "$schluessel=$wert eingetragen" fi } @@ -235,41 +364,7 @@ if [ -n "$SR" ]; then 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 prüft sie." - -# Gegenprobe: Zeigen die WIRKSAMEN Werte auf Geräte, die es hier gibt? Fängt -# 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 Gerät gibt es hier NICHT." - if [ -n "$SR" ]; then - gelb " Gefunden wurde stattdessen: $SR + $SG" - gelb " Trage das in der .env ein (oder lösche 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 " ! Übersprungen (Nur-Prüfen-Modus)." - echo "" - gruen "Prüfung fertig. Ohne --nur-pruefen würde jetzt gebaut und gestartet." - exit 0 -fi - +titel "Schritt 4/4 Bauen und starten" echo " Der erste Bau dauert je nach Internet 3-10 Minuten. Nur beim ersten Mal." echo "" if ! $COMPOSE up -d --build; then