diff --git a/Kapitel 13/Tutorial.md b/Kapitel 13/Tutorial.md index 7c1904a..e3a6f6c 100644 --- a/Kapitel 13/Tutorial.md +++ b/Kapitel 13/Tutorial.md @@ -118,12 +118,11 @@ nano /etc/clipper/clipper.env ``` Inhalt: ``` -CLIPPER_INBOX=/srv/clipper/inbox -CLIPPER_IN=/srv/clipper/watch -CLIPPER_OUT=/srv/clipper/out -CLIPPER_TMP=/srv/clipper/temp -CLIPPER_LOG=/srv/clipper/logs/clipper.log -CLIPPER_NC_REMOTE="nc:/" +SFTP_HOST=192.168.51.2 +SFTP_PORT=22 +SFTP_USER=sftp_uploader +SFTP_KEY=/home/clipper/.ssh/nc_sftp_ed25519 +SFTP_DROP_BASE=incoming ``` Dateirechte setzen, damit `clipper` sie lesen darf: ```bash @@ -603,127 +602,127 @@ In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. Er sorgt d 8. **Vorbereitungen für VOD Download und Speicherung** - **8.1 Vorbereitung (einmalig) – rclone an Nextcloud anbinden** - Ort: Clipper-LXC Shell - Öffne hierzu in Proxmox, Putty, oder einer anderen Konsole den Clipper LXC und gebe die folgenden Befehele ein: - ```bash - apt update && apt install -y rclone - ``` - Hiermit installieren wie rclone, was wir später für den Upload zu Nextcloud nutzen werden - - Wechsel dann zu dem vorhin erstellten NUtzer (in diesem Tutorial Clipper) und erstelle eine WebDAV Anbindung an deine Nextcloud. - Hierfür verwnedest du die folgenden Befehle: - + **8.1 Vorbereitung (einmalig) – SFTP-Verbindung testen + Ort: Clipper-LXC Shell + Wir prüfen, ob Clipper mit dem Key des sftp_uploader auf den Nextcloud-Host kommt und die Drop-Zone erreichbar ist. ```bash su - clipper - rclone config create nc webdav \ - url=https://DEINE_DOMAIN/remote.php/dav/files/DEIN_BENUTZERNAME/ \ - vendor=nextcloud \ - user=DEIN_BENUTZERNAME \ - pass=$(rclone obscure 'DEIN_APP_PASSWORT') - rclone ls nc: --config /home/clipper/.config/rclone/rclone.conf + sftp -i ~/.ssh/nc_sftp_ed25519 -oBatchMode=yes -oStrictHostKeyChecking=accept-new -P 22 sftp_uploader@ <<'SFTP' + mkdir incoming + mkdir incoming/_test + ls incoming + rmdir incoming/_test + SFTP + exit ``` + Erwartung: die Ausgabe zeigt incoming. Dann ist die Verbindung korrekt. **8.2 Download/Upload Skript erstellen** **Ort:** Clipper-LXC Shell - Noch immer in der Konsole des Clipper LXC verlässt du mit `exit` den User und bist wieder root User. - Im Anschluss erstellst du eine neue Datei mit + Wir erstellen ein Skript, das ein Twitch-VOD mit yt-dlp lädt und per SFTP nach incoming//.mp4 auf dem Nextcloud-Host hochlädt. + Voraussetzung: In /etc/clipper/clipper.env sind gesetzt: + SFTP_HOST, SFTP_PORT, SFTP_USER, SFTP_KEY, SFTP_DROP_BASE, außerdem CLIPPER_TMP, CLIPPER_OUT. ```bash nano /bin/clipper-vod-get ``` Nun befüllst du sie mit: ```bash - #!/usr/bin/env bash - set -euo pipefail - . /etc/clipper/clipper.env + #!/usr/bin/env bash + set -euo pipefail - ID="${1:?need VOD id}" - URL="${2:-https://www.twitch.tv/videos/${ID}}" + ENV_FILE="/etc/clipper/clipper.env" + [[ -r "$ENV_FILE" ]] || { echo "ENV nicht lesbar: $ENV_FILE" >&2; exit 1; } + source "$ENV_FILE" - TMP="${CLIPPER_TMP}" - OUT_BASE="${CLIPPER_OUT}/${ID}" - LOGDIR="${CLIPPER_LOG}/${ID}" - CONF="/home/clipper/.config/rclone/rclone.conf" + ID="${1:?need VOD id}" + URL="${2:-https://www.twitch.tv/videos/${ID}}" - DST="${CLIPPER_NC_REMOTE}/VODs/${ID}/" + TMP="${CLIPPER_TMP:-/srv/clipper/temp}" + OUT_BASE="${CLIPPER_OUT:-/srv/clipper/out}/${ID}" + LOGDIR="/srv/clipper/logs/${ID}" + FILE="${TMP}/${ID}.mp4" + TEMP="${TMP}/${ID}.temp.mp4" + PART="${TMP}/${ID}.mp4.part" + LOCK="${TMP}/${ID}.lock" - OUT="$TMP/${ID}.%(ext)s" - FILE="$TMP/${ID}.mp4" - TEMP="$TMP/${ID}.temp.mp4" - PART="$TMP/${ID}.mp4.part" - LOCK="$TMP/${ID}.lock" + DROP_BASE="${SFTP_DROP_BASE:-incoming}" + REMOTE_DIR="${DROP_BASE}/${ID}" + REMOTE_FILE="${REMOTE_DIR}/${ID}.mp4" - mkdir -p "$TMP" "$LOGDIR" "$OUT_BASE" - LOG="$LOGDIR/download.log" - log(){ echo "[$(date '+%F %T')] $*"; } - exec > >(tee -a "$LOG") 2>&1 + mkdir -p "$TMP" "$LOGDIR" "$OUT_BASE" + LOG="${LOGDIR}/download.log" + log(){ printf '[%(%F %T)T] %s\n' -1 "$*" ; } + exec > >(tee -a "$LOG") 2>&1 - # ---- atomarer Lock (kein Race zwischen zwei Starts) - exec 9>"$LOCK" - if ! flock -n 9; then - log "LOCK: $ID wird bereits verarbeitet" - exit 0 - fi - trap 'flock -u 9; rm -f "$LOCK"' EXIT + exec 9>"$LOCK" + if ! flock -n 9; then + log "LOCK: $ID wird bereits verarbeitet" + exit 0 + fi + trap 'flock -u 9; rm -f "$LOCK"' EXIT - log "=== Start VOD $ID ===" - log "URL: $URL" - log "DST: $DST" + SFTP_OPTS=(-i "${SFTP_KEY}" -P "${SFTP_PORT:-22}" -oBatchMode=yes -oStrictHostKeyChecking=accept-new) + SFTP_TARGET="${SFTP_USER}@${SFTP_HOST}" - # ---- Prüfe, ob die ZIELDATEI bereits existiert (nicht nur der Ordner) - if rclone lsf "$DST" --config "$CONF" | grep -qx "${ID}.mp4"; then - log "SKIP: $ID.mp4 bereits in Nextcloud" - exit 0 - fi + sftp_batch() { + local cmds + cmds=$(printf "%s\n" "$@") + sftp "${SFTP_OPTS[@]}" "${SFTP_TARGET}" <<< "$cmds" + } - # ---- Resume: unvollständigen Download sauber fortsetzen - if [[ -s "$TEMP" && ! -s "$FILE" ]]; then - log "RESUME: $TEMP -> $FILE" - mv -f "$TEMP" "$FILE" - fi + exists_remote_file() { + local out + out=$(sftp "${SFTP_OPTS[@]}" "${SFTP_TARGET}" <<< "ls -l ${REMOTE_FILE}" 2>&1 || true) + [[ "$out" != *"No such file"* ]] && [[ "$out" != *"not found"* ]] + } - # ---- Gemeinsame rclone-Flags: Nextcloud-Chunking + robuste Timeouts - export RCLONE_WEBDAV_CHUNK_SIZE=100Mi # wirkt bei neueren rclone Versionen + log "=== Start VOD ${ID} ===" + log "URL: ${URL}" + log "DROP: ${REMOTE_FILE}" - rclone move "$FILE" "$DST" \ - --config "$CONF" --create-empty-src-dirs \ - --transfers 1 --checkers 4 \ - --retries 10 --low-level-retries 50 --retries-sleep 30s \ - --timeout 1h --contimeout 1m --expect-continue-timeout 10m \ - -v + if exists_remote_file; then + log "SKIP: ${REMOTE_FILE} existiert bereits" + exit 0 + fi - # ---- Wenn Datei schon da: nur noch hochschieben - if [[ -s "$FILE" ]]; then - log "MOVE (resume): $FILE → $DST" - rclone move "$FILE" "$DST" "${RCLONE_FLAGS[@]}" - rm -f "$PART" "$TEMP" || true - log "CLEANUP: $TMP" - rm -rf "${TMP:?}/"* - log "=== Done VOD $ID (resume path) ===" - exit 0 - fi + if [[ -s "$TEMP" && ! -s "$FILE" ]]; then + log "RESUME: $TEMP -> $FILE" + mv -f "$TEMP" "$FILE" + fi - # ---- Download + Remux - yt-dlp -q --no-progress --retries 20 --fragment-retries 50 --retry-sleep 5 \ - --socket-timeout 30 --hls-prefer-ffmpeg --remux-video mp4 -o "$OUT" "$URL" + OUT="${TMP}/${ID}.%(ext)s" + yt-dlp -q --no-progress --retries 20 --fragment-retries 50 --retry-sleep 5 \ + --socket-timeout 30 --hls-prefer-ffmpeg --remux-video mp4 -o "$OUT" "$URL" - [[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; } + [[ -s "$FILE" ]] || { [[ -s "$TEMP" ]] && mv -f "$TEMP" "$FILE"; } - if [[ ! -s "$FILE" ]]; then - log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)" - exit 2 - fi + if [[ ! -s "$FILE" ]]; then + log "ERROR: Download fehlgeschlagen ($FILE fehlt/leer)" + exit 10 + fi - log "MOVE: $FILE → $DST" - rclone move "$FILE" "$DST" "${RCLONE_FLAGS[@]}" - rm -f "$PART" "$TEMP" || true + sftp_batch "mkdir ${DROP_BASE}" "mkdir ${REMOTE_DIR}" - log "CLEANUP: $TMP" - rm -rf "${TMP:?}/"* + tries=0 + until exists_remote_file; do + tries=$((tries+1)) + log "UPLOAD Try #$tries: $FILE -> ${REMOTE_FILE}" + sftp_batch "reput ${FILE} ${REMOTE_FILE}" "put ${FILE} ${REMOTE_FILE}" || true + sleep $((2*tries)) + [[ $tries -ge 5 ]] && break + done - log "=== Done VOD $ID ===" - " + if ! exists_remote_file; then + log "ERROR: Upload fehlgeschlagen" + exit 20 + fi + + rm -f "$PART" "$TEMP" || true + log "CLEANUP: $TMP" + rm -rf "${TMP:?}/"* + + log "=== Done VOD ${ID} ===" ``` Mit diesem Skript laden wir die aktuellen VODs herunter, laden sie in die Nextcloud für die weitere Verabeitung und räumen wieder auf. Zusätzlich erzeugen wir logs in `/logs/.log`. @@ -735,6 +734,85 @@ In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. Er sorgt d > [!NOTE] > Pro VOD entsteht ein Logfile in `/logs/.log`. Du kannst es live mit `tail -f /logs/.log` verfolgen. + 8.3 **Finalize-Skript auf dem Nextcloud-Host** + Nachdem der Clipper die VOD-Datei in die Drop-Zone incoming/ hochgeladen hat, müssen wir diese Dateien an den endgültigen Platz in Nextcloud verschieben. Nur so erscheinen sie auch im Web-Interface. Genau dafür legen wir jetzt ein Skript an, das automatisch aufgerufen werden kann. + + Das Skript übernimmt drei Aufgaben: + - Dateien aus der Drop-Zone verschieben in den Zielordner innerhalb von Nextcloud. + - Rechte setzen, damit Nextcloud (Benutzer www-data) die Dateien verwalten kann. + - Den Nextcloud-Dateibaum mit occ files:scan aktualisieren, damit die Dateien sofort sichtbar werden. + + Wie zuvor müssen wir ein Konfigurationsdatei anlegen. + ```bash + nano /etc/nc_uploader.conf + ``` + Fülle sie mit: + ```bash + NC_USER=DEIN_NC_USER # Nextcloud-Benutzer, dem die Dateien gehören sollen + NC_TARGET_SUBPATH="Medien/VODs" # Zielordner innerhalb von Nextcloud (wie er im Web erscheint) + NC_DATA="/var/www/nextcloud/data" # Basis-Datenverzeichnis deiner Nextcloud-Instanz + DROP_BASE="/home/sftp_uploader/incoming" # SFTP-Drop-Zone von sftp_uploader + PHP="/usr/bin/php" + OCC="/var/www/nextcloud/occ" + ``` + Speichere die Datei wieder mit `STRG + O` und schließe den Editor mit `STRG + x`. + + Das folgende Skript sorgt dann dafür. dass unser eben hoch geladendes VOD an der richtigen Stelle zu finden ist und über die Weboberfläche erreichbar sein wird. + + ```bash + nano /usr/local/bin/nc_finalize_vod.sh + ``` + + Inhalt: + ```bash + # /usr/local/bin/nc_finalize_vod.sh + #!/usr/bin/env bash + set -euo pipefail + + CONF="/etc/nc_uploader.conf" + [[ -f "$CONF" ]] || { echo "Config fehlt: $CONF" >&2; exit 1; } + # shellcheck disable=SC1090 + source "$CONF" + + VOD_ID="${1:?need VOD id}" + FILE_BASENAME="${2:-${VOD_ID}.mp4}" + + SRC_DIR="${DROP_BASE}/${VOD_ID}" + SRC_FILE="${SRC_DIR}/${FILE_BASENAME}" + DST_DIR="${NC_DATA}/${NC_USER}/files/${NC_TARGET_SUBPATH}/${VOD_ID}" + SCAN_PATH="${NC_USER}/files/${NC_TARGET_SUBPATH}/${VOD_ID}" + + mkdir -p "$DST_DIR" + + if [[ -d "$SRC_DIR" ]]; then + # kompletter VOD-Ordner vorhanden → verschiebe Inhalt (oder Ordner, falls leer) + shopt -s nullglob dotglob + if compgen -G "${SRC_DIR}/*" > /dev/null; then + mv -f "${SRC_DIR}/"* "$DST_DIR"/ + fi + rmdir "$SRC_DIR" 2>/dev/null || true + elif [[ -f "$SRC_FILE" ]]; then + mv -f "$SRC_FILE" "$DST_DIR"/ + else + echo "Quelle nicht gefunden: $SRC_DIR oder $SRC_FILE" >&2 + exit 2 + fi + + chown -R www-data:www-data "$DST_DIR" + "$PHP" "$OCC" files:scan --path="$SCAN_PATH" --quiet + + echo "OK: ${DST_DIR}" + ``` + Wie zuvor auch, müssen wir die Rechte korrekt setzen, damit alles reibungslos funktioniert. + Gebe dazu in der Konsole + ```bash + chmod 755 /usr/local/bin/nc_finalize_vod.sh + chown root:root /usr/local/bin/nc_finalize_vod.sh + ``` + ein. + + Im weiteren Verlauf erstellen wir dann einen Node, der dieses Skript aufruft. Im Anschluss sollte das VOD in dem entsprechenden Ordner landen. + 9. **Merge – Combine** (Node-Name: `Select VODs to Download`) - **Node-Typ:** Merge - **Mode:** Combine @@ -772,13 +850,24 @@ In diesem Schritt erstellen wir den eigentlichen Workflow in **n8n**. Er sorgt d ```bash /bin/clipper-vod-get "{{$('Merge').item.json.data.id}}" "{{ $json.url || ('https://www.twitch.tv/videos/' + $('Merge').item.json.data.id) }}" ``` -Diese 11 Nodes werden das gesamte Grundgerüst der gesamten Automation sein. Wie aber müssen sie verbudnen werden? + +13. **SSH Node 3 – Finalize** (Node-Name: Finalize VOD) + - Node-Typ: SSH + - Credentials: SSH Nextcloud (nc_runner) + - Operation: Execute Command + - Command is an Expression: ON + - Command: + ```bash + /usr/local/bin/nc_finalize_vod.sh {{$('Down 'n' Up').item.json.vodId}} {{$('Down 'n' Up').item.json.filename}} + ``` + +Diese 12 Nodes werden das gesamte Grundgerüst der gesamten Automation sein. Wie aber müssen sie verbudnen werden? Das folgende Schaubild zeigt dir die konkrete Verkabelung ```bash - --- *SSH – Check State* --- *Set – File Information* --- *Set – vods in Array* --- *Split Out – vods* ------ + --- **SSH – Check State** --- **Set – File Information** --- **Set – vods in Array** --- **Split Out – vods** ------ | | -*Cron – Alle 10 Min* ---- | *Select VODs to Download* --- *Einzeldurchlauf* --- *State Datei schreiben* --- *Down 'n' Up* --- (Hier folgen später weitere Nodes, aber da der Einzeldurchlauf ein Loop ist wird der letzte Node mit Einzeldurchlauf verbunden) +**Cron – Alle 10 Min** ---- | **Select VODs to Download** --- **Einzeldurchlauf** --- **State Datei schreiben** --- **Down 'n' Up** --- **Finalize VOD** (Hier folgen später weitere Nodes, aber da der Einzeldurchlauf ein Loop ist wird der letzte Node mit Einzeldurchlauf verbunden) | | --- *Get Twitch VOD IDs* --- *Split Out* – *Twitch VOD* -------------------------------------------------- ```