Files
rippy/deploy.sh
T
Hitonabi 19f3dc5330
Ampel / ampel (push) Successful in 30s
fix(zombies): "running" fehlte - ein abgestuerzter RIP wurde NIE gefunden
Der Commander: "Ausserdem ist gerade mitten im Rip das Laufwerk ausgegangen...
glaube ich zumindest." Gemessen war es etwas anderes, und der Fund ist groesser
als der Vorfall.

WAS MESSBAR WAR:
  Job 2182d525   status=running progress=12   (Rip gestartet 14:00:24)
  makemkvcon     laeuft NICHT (per /proc geprueft, PID gegengeprueft)
  Rohdatei       5.167.382.528 Bytes, waechst in 10 s nicht
  Laufwerk       Status 4 (Disc drin), /dev/sr0 + /dev/sg1 da
  Worker-Start   14:07:02  <- mein `docker compose up -d --build`

Das Laufwerk ist also NICHT ausgegangen. Der Rip wurde von MEINEM Deploy
getoetet: `up -d --build` baut den worker-Container neu, und der laufende Rip
stirbt mit ihm. Genau davor warnt der SAVEPOINT seit v3.18 - die Warnung half
nichts, weil sie niemand liest und nichts sie prueft.

DER EIGENTLICHE FUND: Die Zombie-Erkennung lief um 14:09:04 und meldete
`{'geprueft': 0, 'aufgeraeumt': []}` - obwohl der tote Job direkt vor ihr lag.
Ursache:

  ARBEITS_STATI = ("ripping", "transcoding", "canceling")   # zombies.py
  db.update_job(job_id, status="running", ...)              # tasks.py - der Rip

Der Rip setzt "running", gesucht wurde "ripping". Dieser Wert steht
ausschliesslich in Celerys Task-META und NIE in einer Job-Zeile (nachgeprueft:
kein einziger Schreiber im ganzen Baum). Die Zombie-Erkennung aus v3.14 wurde
gebaut, um genau einen abgestuerzten Rip zu finden - und hat ihn nie gesehen.

Besonders tueckisch: `geprueft: 0` sah bei jedem Worker-Start wie "nachgesehen,
alles gesund" aus, waehrend sie nach einem Status suchte, den es nicht gibt.
Deshalb blieb der Job auf "processing 12 %" stehen - mit einer Restzeit-Schaetzung
von 1 h 30 min obendrauf, die es fuer einen toten Prozess nicht geben duerfte.

GEBAUT:
  * "running" in ARBEITS_STATI.
  * Ein Test, der das Auseinanderlaufen MECHANISCH verhindert: Er liest tasks.py,
    sammelt jeden Status, den der Worker per db.update_job in eine Job-Zeile
    schreibt, und verlangt, dass jeder davon entweder ein Arbeitsstatus oder ein
    Endzustand ist. Ein Kommentar haette das nicht verhindert.
  * GET /health/arbeit + eine Sperre in deploy.sh: Laeuft ein Job, bricht der
    Deploy ab (uebersteuerbar mit RIPPY_TROTZDEM=1 - dann ist es eine
    Entscheidung und kein Versehen). Ein Satz Code gegen eine verlorene Stunde.
  * Server-Status zeigt jetzt die eingehaengten FREIGABEN mit freiem Platz, nicht
    nur die Container-Platte (Commander-Wunsch). Genau dort liegen die Rohdaten,
    und bei externem Encoden muessen sie dort liegen - wer wissen wollte, ob noch
    Platz fuer eine Disc ist, sah die falsche Zahl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 16:16:05 +02:00

71 lines
3.1 KiB
Bash

#!/usr/bin/env bash
# Deploy fuer Rippy — deployt den aktuellen 'main'-Stand auf die Ziel-VM.
# Single Source of Truth ist 'main' (kein stable-Branch); die Ampel prueft nur,
# befoerdert nichts. Vor dem Deploy selbst sicherstellen, dass die Ampel fuer den
# zu deployenden Commit GRUEN ist.
#
# Generisch/portabel: Ziel-Host, Repo-URL und .env-Pfad kommen aus der Umgebung —
# KEINE festen Adressen im Repo. Fuer eine Erst-Installation auf einem eigenen Host
# reicht ohnehin der einfache Weg (siehe README):
# git clone <repo> && cd rippy && cp .env.example .env (Werte eintragen)
# docker compose up -d --build
#
# Dieses Skript ist nur die bequeme Remote-Variante: es SSHt zur VM, nutzt dort
# einen EIGENEN Klon (Default ~/notfall-rippy) und fasst ein evtl. vorhandenes
# Projektverzeichnis NIE an (git-Klon dort kollidiert mit dessen Sync). Die .env
# gehoert der VM und wird nur GELESEN (fuer compose-Interpolation).
#
# Konfiguration ueber Umgebungsvariablen:
# RIPPY_VM Ziel-Host fuer ssh, z.B. user@10.0.0.5 (PFLICHT)
# RIPPY_REPO_URL Git-Repo-URL (branch main) (PFLICHT)
# RIPPY_ENV_SRC Pfad zur .env auf der VM (Default: ~/rippy/.env)
# RIPPY_CLONE Arbeits-Klon auf der VM (Default: ~/notfall-rippy)
# Optionales Argument $1: nur einen Dienst neu bauen, z.B. ./deploy.sh api
set -euo pipefail
VM="${RIPPY_VM:?RIPPY_VM setzen, z.B. RIPPY_VM=user@10.0.0.5}"
REPO_URL="${RIPPY_REPO_URL:?RIPPY_REPO_URL setzen (Git-URL des Rippy-Repos)}"
ENV_SRC="${RIPPY_ENV_SRC:-}" # leer -> Remote-Default ~/rippy/.env
CLONE="${RIPPY_CLONE:-}" # leer -> Remote-Default ~/notfall-rippy
DIENST="${1:-}"
ssh "$VM" bash -s -- "$REPO_URL" "$ENV_SRC" "$CLONE" "$DIENST" <<'REMOTE'
set -euo pipefail
REPO_URL="$1"
ENV_SRC="${2:-$HOME/rippy/.env}"
CLONE="${3:-$HOME/notfall-rippy}"
DIENST="$4"
# ⚠️ NICHT DEPLOYEN, WÄHREND EIN JOB LÄUFT (Vorfall 26.07.2026, selbst verursacht)
#
# `docker compose up -d --build` baut den worker-Container neu — und tötet damit
# einen laufenden Rip. Genau das ist passiert: mitten in einem Blu-ray-Rip, bei
# 12 %, nach 5,1 GB. Die Warnung stand im SAVEPOINT und half nichts, weil sie
# niemand las und nichts sie prüfte. Jetzt prüft es das Skript.
#
# Übersteuern mit RIPPY_TROTZDEM=1 — dann ist es eine Entscheidung und kein
# Versehen.
if [ -z "${RIPPY_TROTZDEM:-}" ]; then
ANTWORT="$(curl -s -m 5 http://localhost:8000/health/arbeit 2>/dev/null || true)"
case "$ANTWORT" in
*'"arbeit":true'*|*'"arbeit": true'*)
echo "ABBRUCH: Auf dieser Maschine läuft gerade ein Job." >&2
echo " Ein Rebuild des worker-Containers würde ihn töten." >&2
echo "$ANTWORT" | tr ',' '\n' | grep -E '"(status|titel|progress)"' >&2 || true
echo " Warten, oder bewusst überstimmen: RIPPY_TROTZDEM=1 ./deploy.sh" >&2
exit 1
;;
esac
fi
if [ ! -d "$CLONE/.git" ]; then
git clone --branch main "$REPO_URL" "$CLONE"
fi
cd "$CLONE"
git fetch origin main
git reset --hard origin/main
cp "$ENV_SRC" .env 2>/dev/null || echo "WARNUNG: keine .env unter $ENV_SRC gefunden"
docker compose -p rippy up -d --build $DIENST
docker compose -p rippy ps --format 'table {{.Name}}\t{{.Status}}'
REMOTE