Update brach ohne Meldung ab

Beim Sprung von 1.5.0 auf 1.10.1 endete scripts/update.sh mitten in der
Ausgabe. Kein Fehler, keine Zeile, einfach zurueck zur
Eingabeaufforderung.

Ursache war eine abgeschnittene Leitung:

  git log --oneline "HEAD..$ZIEL" | head -15 | sed ...

head schliesst die Leitung nach 15 Zeilen, git log schreibt weiter und
bekommt SIGPIPE (141). Mit "set -o pipefail" gilt die ganze Kette als
gescheitert, "set -e" beendet das Skript - stumm.

Zeitabhaengig, deshalb monatelang unauffaellig: Solange git log fertig
wurde, bevor head zumachte, ging es gut. Die Schwelle liegt exakt bei
15 Commits, und v1.5.0..v1.10.0 waren die ersten 16.

Behoben, indem der Erzeuger selbst begrenzt (git log -15) statt die
Leitung zu kappen. Dieselbe Behandlung fuer die beiden Tag-Listen.

Dieselbe Falle steckte in der Selbstpruefung von sicherung.sh:

  openssl enc -d ... | head -c 5 | grep -q "PGDMP"

Beide Leser schliessen frueh. Bei kleiner Datenbank steht alles im
Puffer und es faellt nicht auf; mit wachsender Datenbank kippt es - und
dann meldet das Skript eine INTAKTE Sicherung als kaputt und verweigert
das Update. Jetzt liest dd eine feste Menge und endet regulaer.

Damit so etwas nicht wieder stumm passiert:
- Eine ERR-Falle nennt Zeilennummer und Exitcode und erklaert bei 141,
  dass es SIGPIPE ist und ein Fehler im Skript, nicht beim Benutzer.
- Ein Test verbietet "| head" in Skripten mit pipefail und nennt die
  Zeile. Gegenprobe gemacht: Er schlaegt an.
- Bei mehr als 15 Commits steht "... und N weitere" darunter, statt sie
  wortlos wegzulassen.

Nachgestellt: der Ablauf von 1.5.0 auf 1.10.1 laeuft durch.
This commit is contained in:
Florian Wilde
2026-08-24 00:36:08 +02:00
parent 9ea78a4d29
commit 107406e3c0
8 changed files with 162 additions and 19 deletions
+13 -3
View File
@@ -75,9 +75,19 @@ done
# --- Selbstpruefung ----------------------------------------------------------
# Ein Backup, das sich nicht entschluesseln laesst, ist kein Backup.
# Deshalb wird sofort geprueft, ob die Datei wieder lesbar ist.
if openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
-pass env:SICHERUNG_PASSWORT -in "$DATEI" 2>/dev/null \
| head -c 5 | grep -q "PGDMP"; then
# Kein "| head -c 5 | grep -q": Beide schliessen die Leitung, sobald sie
# genug haben. openssl schreibt dann in ein geschlossenes Rohr, bekommt
# SIGPIPE - und mit "set -o pipefail" gilt die ganze Kette als
# gescheitert. Bei einer kleinen Datenbank faellt das nicht auf, weil
# alles schon im Puffer steht; mit wachsender Datenbank kippt es, und
# dann meldet die Selbstpruefung eine INTAKTE Sicherung als kaputt.
#
# "dd" liest eine feste Menge und beendet sich regulaer.
KOPF="$(openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
-pass env:SICHERUNG_PASSWORT -in "$DATEI" 2>/dev/null \
| dd bs=1 count=5 2>/dev/null || true)"
if [ "$KOPF" = "PGDMP" ]; then
echo "$(date -Iseconds) Selbstpruefung bestanden."
else
echo "$(date -Iseconds) FEHLER: Die Sicherung laesst sich nicht entschluesseln!" >&2
+45 -5
View File
@@ -52,6 +52,32 @@ gut() { printf ' %s%s%s\n' "$GRUEN" "$1" "$AUS"; }
warnung() { printf ' %s%s%s\n' "$GELB" "$1" "$AUS"; }
abbruch() { printf '\n%sAbbruch: %s%s\n\n' "$ROT" "$1" "$AUS" >&2; exit 1; }
# --- Unerwarteter Abbruch ----------------------------------------------------
#
# "set -e" beendet das Skript bei jedem Befehl, der nicht 0 zurueckgibt -
# stumm. Auf dem Bildschirm bricht die Ausgabe dann einfach ab, und man
# sucht an der falschen Stelle.
#
# Genau das ist beim Sprung von v1.5.0 auf v1.10.1 passiert: Eine Leitung
# der Form "git log ... | head -15" hat den Erzeuger mit SIGPIPE beendet,
# "pipefail" hat das an die Kette weitergereicht, und das Skript war weg.
#
# Diese Falle sagt jetzt wenigstens, WO es passiert ist. Exitcode 141
# heisst SIGPIPE - dann wurde irgendwo wieder eine Leitung zu frueh
# geschlossen.
unerwartet() {
code=$1
zeile=$2
printf '%sAbbruch in Zeile %s (Exitcode %s).%s\n' "$ROT" "$zeile" "$code" "$AUS" >&2
if [ "$code" = "141" ]; then
printf '%sExitcode 141 ist SIGPIPE: eine Leitung wurde zu frueh geschlossen,%s\n' "$ROT" "$AUS" >&2
printf '%setwa durch "| head". Das ist ein Fehler im Skript, nicht bei dir.%s\n' "$ROT" "$AUS" >&2
fi
printf '%sKam der Abbruch vor Schritt 5, wurde nichts veraendert.%s\n' "$ROT" "$AUS" >&2
}
trap 'unerwartet $? $LINENO' ERR
# --- Aufrufparameter ---------------------------------------------------------
ZIEL=""
@@ -113,7 +139,11 @@ if ! git fetch --tags --quiet origin; then
fi
JETZT="$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)"
NEUESTE="$(git tag --list 'v*' --sort=-v:refname | head -1)"
# Kein "| head -1": Siehe den Hinweis zu SIGPIPE weiter unten. Die erste
# Zeile holt die Shell selbst.
ALLE_TAGS="$(git tag --list 'v*' --sort=-v:refname)"
NEUESTE="${ALLE_TAGS%%$'
'*}"
[ -n "$NEUESTE" ] || abbruch "Es gibt keine veroeffentlichte Fassung (kein Tag)."
@@ -123,7 +153,8 @@ fi
git rev-parse --verify --quiet "$ZIEL^{commit}" >/dev/null \
|| abbruch "Die Fassung '$ZIEL' gibt es nicht. Vorhanden:
$(git tag --list 'v*' --sort=-v:refname | head -5 | sed 's/^/ /')"
$(printf '%s
' "$ALLE_TAGS" | sed -n '1,5p' | sed 's/^/ /')"
printf ' Jetzt: %s\n' "$JETZT"
printf ' Ziel: %s%s%s\n' "$FETT" "$ZIEL" "$AUS"
@@ -152,14 +183,23 @@ if [ -n "$CHANGELOG" ]; then
if (nummer == jetzt) exit
drucken = (nummer != "Unveröffentlicht")
}
drucken { print " " $0 }
' | head -60
drucken && zeilen < 60 { print " " $0; zeilen++ }
'
else
warnung "Kein CHANGELOG.md in $ZIEL gefunden."
fi
printf '\n'
git log --oneline --no-decorate "HEAD..$ZIEL" | head -15 | sed 's/^/ /'
# "-15" statt "| head -15": Bei mehr Commits als der Grenze schliesst head
# die Leitung, git bekommt SIGPIPE, und mit "set -o pipefail" gilt die ganze
# Kette als gescheitert - "set -e" beendet das Skript dann OHNE Meldung.
# Genau das ist beim Sprung von v1.5.0 auf v1.10.1 passiert.
ANZAHL="$(git rev-list --count "HEAD..$ZIEL")"
git log --oneline --no-decorate -15 "HEAD..$ZIEL" | sed 's/^/ /'
if [ "$ANZAHL" -gt 15 ]; then
printf ' ... und %s weitere
' "$((ANZAHL - 15))"
fi
if [ "$NUR_PRUEFEN" = "1" ]; then
printf '\n'