Artikel

Der Build-Schalter im Panel war seit Tagen blind

Ein Knopf, der einen Bau auslösen soll, meldet Erfolg und tut nichts. Die Ursache steckt in einer verketteten Befehlszeile — und in einem Paketmanager, der freundlich abbricht.

BlackZackBlackzack

973 Wörter · 5 Min. Lesezeit

  • pelican
  • betrieb
  • fehlersuche

In meinem neuen Aufbau hat jeder Dienst einen Startbefehl, der beim Hochfahren ausgeführt wird. Bei Anwendungen, die gebaut werden müssen, sieht der ungefähr so aus:

[ -f .pelican-build ] && pnpm install && pnpm build && rm .pelican-build; pnpm start

Die Idee: Wenn eine Markierungsdatei existiert, wird vor dem Start installiert und gebaut, danach die Markierung entfernt. Man legt die Datei an, startet neu, und die Anwendung kommt mit einer frischen Fassung hoch.

Das funktioniert. Wenn es funktioniert. Bei mir hat es tagelang nichts getan, und der Dienst startete jedes Mal fröhlich mit der alten Fassung.

Die Suche

Erster Verdacht: Die Markierungsdatei wird nicht gefunden — falsches Verzeichnis, falsche Rechte. Geprüft: Datei liegt richtig, gehört dem richtigen Benutzer.

Zweiter Verdacht: Der Startbefehl wird nicht so ausgeführt, wie ich denke. Also den Befehl von Hand im Container ausgeführt. Und da war es sichtbar.

pnpm install brach ab. Nicht mit einem Absturz, sondern mit einer freundlichen Meldung: Der Paketmanager fand eine Angabe, welche Fassung von sich selbst zu benutzen sei, konnte sie aber nicht bereitstellen und beendete sich mit einem Fehlercode.

Und hier kommt der eigentliche Punkt: Die Verkettung mit && bricht bei einem Fehlercode ab — aber der Rest hinter dem Semikolon läuft weiter. Der Bau fand also nicht statt, der Start schon. Für das Panel sah das aus wie ein erfolgreicher Neustart.

Warum es so lange unentdeckt blieb

Das ist der Teil, den ich mir gemerkt habe.

Die Ausgabe war zu lang. In der Konsole des Panels rauschen beim Start hunderte Zeilen durch. Die Abbruchmeldung stand irgendwo dazwischen, und danach kam eine völlig normale Startmeldung.

Der Dienst lief. Es gab keinen Ausfall, keine Fehlerseite, keine Meldung. Nur eine Anwendung, die nicht das tat, was ich geändert hatte.

Ich habe das Falsche geprüft. Meine Prüfung war „läuft die Seite?" — und die Antwort war ja. Die richtige Prüfung wäre gewesen: „läuft die Seite mit meiner Änderung?"

Genau dafür gibt es eine einfache Antwort, die ich seitdem benutze: Eine sichtbare Fassungsangabe. Eine Versionsnummer oder ein Zeitstempel des Baus, irgendwo abrufbar. Damit ist die Frage in fünf Sekunden beantwortet, statt dass man das Verhalten der Anwendung interpretiert.

Die Behebung

Erstens: Die Ursache des Abbruchs beseitigen — die Angabe zur Paketmanager-Fassung so setzen, dass die Umgebung sie erfüllen kann.

Zweitens, und wichtiger: Den Startbefehl so umbauen, dass ein fehlgeschlagener Bau nicht stillschweigend übergangen wird.

if [ -f .pelican-build ]; then
  pnpm install || { echo "FEHLER: install fehlgeschlagen"; exit 1; }
  pnpm build   || { echo "FEHLER: build fehlgeschlagen"; exit 1; }
  rm .pelican-build
fi
pnpm start

Damit gilt: Ein fehlgeschlagener Bau führt zu einem Dienst, der nicht startet. Das klingt schlimmer und ist besser — ein nicht startender Dienst wird bemerkt, ein still nicht gebauter nicht.

Warnung

Der allgemeine Fall dahinter: Eine Kette aus && und ; in einem Startbefehl ist eine Fehlerbehandlung, die man nicht als solche liest. Wer a && b ; c schreibt, hat entschieden, dass c auch dann läuft, wenn a fehlschlägt — meistens ohne es zu merken. Bei einem Startbefehl, den man einmal schreibt und nie wieder ansieht, ist das eine tickende Uhr.

Was ich zusätzlich geändert habe

Eine Fassungsangabe in der Anwendung. Erreichbar, mit Bauzeitpunkt. Die erste Frage bei „meine Änderung wirkt nicht" ist damit beantwortet, bevor sie zur Fehlersuche wird.

Bauen getrennt vom Starten. Ein Bau ist ein eigener Vorgang mit eigener Ausgabe, den man ansehen und dessen Erfolg man feststellen kann. Der Start ist danach nur noch ein Start. Das ist dieselbe Regel, die ich mir bei Prozessmanagern schon einmal erarbeitet hatte, und die ich beim Umstieg auf ein neues Verfahren prompt wieder aufgegeben hatte.

Nach dem Ausliefern prüfen, was ausgeliefert wurde. Nicht am lokal gebauten Stand messen, sondern am tatsächlich laufenden.

Die Fassungsangabe, die ich seitdem überall einbaue

Der wirksamste Einzelfix aus dieser Geschichte ist kein Fix am Startbefehl, sondern eine Kleinigkeit in der Anwendung: eine abrufbare Angabe, welche Fassung gerade läuft.

{
  "version": "3.4.1",
  "gebaut": "2026-07-30T18:21:04Z",
  "commit": "a3f9c21"
}

Damit ist die Frage „läuft meine Änderung?" in fünf Sekunden beantwortet — statt durch Interpretation des Verhaltens.

Drei Regeln dazu, die aus der Praxis stammen:

Die Angabe entsteht beim Bauen, nicht beim Starten. Sonst zeigt sie den Zeitpunkt des Neustarts und nicht den des Baus.

Sie steht an einem Ort ohne Anmeldung, aber ohne sensible Details. Der Bauzeitpunkt und eine Kennung reichen.

Sie wird bei jedem Start ins Protokoll geschrieben. Damit ist im Nachhinein feststellbar, welche Fassung zu welchem Zeitpunkt lief.

Was ich über Startbefehle gelernt habe

Ein Startbefehl in einem Textfeld ist ein Programm ohne Versionsverwaltung, ohne Prüfung und ohne Formatierung. Das ist eine schlechte Umgebung für Logik.

Meine Regel daraus: Ein Startbefehl ruft ein Skript auf, mehr nicht. Das Skript liegt im Volume, hat einen Namen, kann Kommentare enthalten und lässt sich lesen, ohne in einer Oberfläche zu scrollen.

#!/bin/sh
# Startet den Dienst. Baut vorher, falls .pelican-build existiert.
set -e
...

Das set -e ist der Kern: Ein fehlgeschlagener Schritt beendet das Skript, statt dass der nächste trotzdem läuft. Genau der Fehler, der mich hier drei Tage gekostet hat, ist damit strukturell ausgeschlossen — und zwar für jeden künftigen Schritt, nicht nur für den einen, den ich repariert habe.

Warum ich Bauvorgänge nicht mehr an den Start hänge

Der eigentliche Konstruktionsfehler war nicht die Befehlskette, sondern die Kopplung: Ein Neustart konnte einen Bau auslösen.

Das ist bequem und hat drei unangenehme Folgen.

Ein Neustart dauert plötzlich Minuten statt Sekunden. Bei einem Absturz mit automatischem Neustart wird daraus eine lange Ausfallzeit.

Ein fehlgeschlagener Bau verhindert den Start. Damit kann eine fehlerhafte Änderung einen laufenden Dienst mitnehmen, der vorher in Ordnung war.

Man weiß nie genau, was gerade läuft. Wurde gebaut oder nicht? Die Antwort steht irgendwo in hunderten Zeilen Startausgabe.

Getrennt ist es eindeutig: Ein Bau ist ein Vorgang mit Anfang, Ende und Ergebnis. Ein Start startet — und zwar immer das, was zuletzt erfolgreich gebaut wurde.

Warum ich das aufschreibe

Weil es kein interessanter Fehler ist. Es ist ein billiger, langweiliger Fehler, der drei Tage Verwirrung erzeugt hat — und weil der Grund dafür, dass er drei Tage überlebt hat, in einer Annahme lag: dass ein Knopf, der „Neustart" heißt und einen Neustart auslöst, auch das getan hat, was ich mir dabei gedacht hatte.