feat(install): ein Befehl statt Checkliste - install.sh nimmt die Handarbeit ab
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
Commander-Rueckmeldung: "Das Docker Deployment ist mir zu kompliziert." Zu
Recht - die Installation war eine Checkliste aus sechs Schritten, von denen
zwei nur mit Fachwissen zu schaffen waren.
## Neu: sudo ./install.sh
Nimmt genau die Schritte ab, an denen man scheitern konnte:
1. Prueft Voraussetzungen (Linux, Docker, Compose) und nennt bei jedem Mangel
den Installationsbefehl.
2. FINDET DAS LAUFWERK SELBST. Das war der schlimmste Punkt: MakeMKV braucht
ZWEI Geraeteknoten, und die sg-Nummer ist je Host anders. Das Skript gleicht
sie ueber die SCSI-Adresse in /sys ab statt zu raten. Auf der VM
gegengeprueft: sr0 -> 3:0:0:0, sg1 -> 3:0:0:0, also dasselbe Geraet -
korrekt als /dev/sr0 + /dev/sg1 erkannt.
3. Legt Ablage- und MakeMKV-Verzeichnis an.
4. Richtet die Mount-Propagation ein UND macht sie per systemd-.mount-Unit
neustart-fest. Vorher stand in der README nur "reboot-fest persistieren" -
ohne zu sagen wie; nach einem Reboot scheiterte das Einhaengen von
NAS-Freigaben aus dem UI stillschweigend.
5. Schreibt die .env, ueberschreibt aber NIE einen bestehenden Wert.
6. Baut und startet, und nennt bei Fehlschlag die drei haeufigsten Ursachen
mit Diagnosebefehl.
Wiederholbar (mehrfach ausfuehren aendert nichts kaputt) und damit auch der
Update-Weg: git pull && sudo ./install.sh
## Zwei Fallgruben, die beim Testen auffielen - beide meine eigenen
- --nur-pruefen verlangte root und brach ab. Ein Pruef-Modus, der nichts
aendert, darf daran nicht scheitern - sonst kann man vor der Installation
nicht nachsehen, ob alles passt. Behoben.
- .env.example hatte OPTICAL_SG=/dev/sg1 UNKOMMENTIERT vorbelegt. Damit haette
der Installer den erkannten Wert nicht eingetragen ("steht schon drin") und
auf jedem fremden Host still eine kaputte Konfiguration hinterlassen - genau
das, was er verhindern soll. Beide Geraetezeilen sind jetzt auskommentiert;
Compose hat ohnehin Vorgaben. Dazu eine Gegenprobe im Skript: zeigt ein
wirksamer Wert auf ein Geraet, das es hier nicht gibt (".env von einem
anderen Rechner"), wird das benannt statt kryptisch von Docker gemeldet.
## README neu aufgebaut
Vorher 293 Zeilen, in denen der Schnellstart zwischen lsscsi, sg-Knoten,
USB-Passthrough und mount --make-rshared begraben war. Jetzt: Installation in
zwei Zeilen oben, dann eine Tabelle "Wenn etwas nicht geht" mit den vier
Faellen, die praktisch alles abdecken. Alles Technische steht darunter in
aufklappbaren Abschnitten - inklusive der Handarbeits-Variante fuer die, die
sie wollen.
NEU und ausdruecklich gewuenscht: Abschnitt "Rippy schneller machen" mit dem
VM-CPU-Typ. Erklaert, warum Virtualisierer eine generische CPU ohne AVX2
geben, was das kostet (gemessene 28-55 h je 4K-Film), die genauen Schritte in
Proxmox (herunterfahren - Hardware/Processors/Type auf 'host' - starten, bzw.
qm set <vmid> --cpu host), warum ein Neustart von innen NICHT genuegt, und wie
man nachprueft: Rippy zeigt die Vektorbefehle seit v3.14 selbst an. Dazu der
Nachteil (keine Live-Migration auf andere CPUs) und die Alternative x86-64-v3
fuer Cluster.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+303
@@ -0,0 +1,303 @@
|
||||
#!/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 <vmid> -usb0 host=<hersteller>:<produkt>,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" <<UNITEOF
|
||||
[Unit]
|
||||
Description=Rippy-Ablage als shared mount (fuer NAS-Freigaben aus dem UI)
|
||||
After=local-fs.target
|
||||
|
||||
[Mount]
|
||||
What=$MEDIA_DIR
|
||||
Where=$MEDIA_DIR
|
||||
Type=none
|
||||
Options=bind,rshared
|
||||
|
||||
[Install]
|
||||
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 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="<ip-dieses-rechners>"
|
||||
|
||||
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 ""
|
||||
Reference in New Issue
Block a user