mk one
← Alle Notizen

Ausverkauft, bevor die Mail kommt — ein Restock-Wächter mit Bordmitteln

15.09.2026 Automatisierung · Unraid · Telegram Lesezeit ca. 7 Minuten

Ein Netzwerkgerät, das ich haben wollte, ist bei jedem Nachschub innerhalb von Minuten ausverkauft. Die offizielle „Wieder verfügbar“-Mail kommt zuverlässig — nur eben dann, wenn andere schon bestellt haben. Die Lösung war ein Shell-Skript von nicht einmal achtzig Zeilen auf einem Server, der sowieso läuft. Und die interessanteste Zeile darin ist die, die meldet, wenn das Skript selbst nichts mehr sieht.

Das Objekt der Begierde: das UniFi 5G Backup, ein kleines Mobilfunk-Modem von Ubiquiti, das an der heimischen Firewall als Ersatzleitung einspringt, wenn der Internetanschluss ausfällt. Rund 106 Euro, PoE-Kabel dran, fertig — entsprechend groß ist die Nachfrage. Im deutschen Ubiquiti-Forum wurde vom letzten Nachschub berichtet: ein Fenster von etwa einer Viertelstunde an einem Samstagabend, dann war der EU-Store wieder leer.

Gegen eine Viertelstunde hilft kein Newsletter. Es hilft nur, selbst nachzusehen — und zwar öfter, als ein Mensch das freiwillig tut.

Erst schauen, wie der Shop redet

Bevor man irgendetwas baut, lohnt ein Blick darauf, was der Shop einem Skript überhaupt verrät. Viele moderne Shops bauen ihre Seiten zwar im Browser per JavaScript zusammen — liefern aber trotzdem den fertigen Zustand schon im HTML mit, damit die Seite schneller erscheint. Genau das prüft man mit zwei Befehlen:

curl -sL -A "Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0" \
  "https://eu.store.ui.com/eu/en/products/u5g" \
  | grep -oE '"status":"[A-Za-z]+"|"restockEtaAt":"[0-9-]+"'

Die Antwort ist erfreulich deutlich:

"status":"SoldOut"
"status":"SoldOut"
"restockEtaAt":"2026-09-23"

Zwei Statusfelder (das Produkt und seine Variante), und der Shop verrät sogar, wann er den nächsten Nachschub erwartet. Kein Browser-Roboter nötig, keine Automatisierungs-Werkzeuge — die ganze Wahrheit steckt in einem einzigen curl-Aufruf. Damit ist die Prüflogik trivial: Steht irgendwo etwas anderes als SoldOut, ist das Gerät bestellbar.

Wichtig ist die Richtung dieser Logik. Ich suche nicht nach dem Wort „Available“ — ich schlage an, sobald der bekannte Ausverkauft-Zustand verlassen wird. So funktioniert der Wächter auch dann, wenn der Shop sich morgen einen anderen Namen für „lieferbar“ ausdenkt.

Der Wächter

Das Skript läuft bei mir auf dem Unraid-Server, weil der ohnehin durchläuft — jeder Rechner, der 24/7 an ist, tut es. Hier der Kern (gekürzt um Logging-Kosmetik):

#!/bin/bash
set -u

URL="https://eu.store.ui.com/eu/en/products/u5g"
UA="Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0"
DIR=/var/tmp/u5g-tracker          # RAM-Disk: Zustand + Log schonen den USB-Stick
STATE="$DIR/state"
TG_DIR=/boot/config/plugins/dynamix/telegram
MAX_FEHLER=5

mkdir -p "$DIR"

telegram() {
    local token chatid
    token=$(cat "$TG_DIR/token" 2>/dev/null) || true
    chatid=$(cat "$TG_DIR/chatid" 2>/dev/null) || true
    [ -z "${token:-}" ] || [ -z "${chatid:-}" ] && return 1
    curl -s -m 20 "https://api.telegram.org/bot$token/sendMessage" \
        --data-urlencode "chat_id=$chatid" \
        --data-urlencode "text=$1" > /dev/null
}

HTML=$(curl -sL -m 30 -A "$UA" "$URL") || HTML=""
STATI=$(printf '%s' "$HTML" | grep -oE '"status":"[A-Za-z]+"' | cut -d'"' -f4 | sort -u)

ALT=$(cat "$STATE" 2>/dev/null || echo "")
FEHLER=$(cat "$DIR/fehler" 2>/dev/null || echo 0)

# Nichts extrahierbar? Dann ist der Wächter blind -- und meldet genau das.
if [ -z "$STATI" ]; then
    FEHLER=$((FEHLER + 1)); echo "$FEHLER" > "$DIR/fehler"
    [ "$FEHLER" -eq "$MAX_FEHLER" ] && \
        telegram "⚠️ Tracker: Store-Status ${MAX_FEHLER}x nicht lesbar. Ich bin blind. $URL"
    exit 0
fi
echo 0 > "$DIR/fehler"

if printf '%s\n' "$STATI" | grep -qvx "SoldOut"; then NEU="BESTELLBAR"; else NEU="SoldOut"; fi
ETA=$(printf '%s' "$HTML" | grep -oE '"restockEtaAt":"[0-9-]+"' | head -1 | cut -d'"' -f4)

# Gemeldet wird nur der Wechsel, nie der Dauerzustand.
if [ "$NEU" != "$ALT" ]; then
    echo "$NEU" > "$STATE"
    if [ "$NEU" = "BESTELLBAR" ]; then
        telegram "🛒 UniFi 5G Backup ist JETZT bestellbar! $URL"
    elif [ -n "$ALT" ]; then
        telegram "Wieder ausverkauft (nächster Restock lt. Store: ${ETA:-unbekannt})."
    fi
fi

Dazu ein Cron-Eintrag, der es alle drei Minuten aufruft. Auf Unraid legt man dafür eine Datei unter /boot/config/plugins/dynamix/ an, damit der Eintrag einen Neustart überlebt — auf jedem anderen Linux tut es die normale crontab:

echo '*/3 * * * * bash /boot/config/u5g-tracker/check.sh &> /dev/null' \
  > /boot/config/plugins/dynamix/u5g-tracker.cron
update_cron

Zwei Unraid-Eigenheiten stecken in diesen Zeilen. Erstens: aufgerufen wird mit bash <pfad>, denn /boot ist ein FAT-Dateisystem ohne Ausführungsrechte — ein direktes ./check.sh scheitert dort kommentarlos. Zweitens landet der Eintrag nach update_cron nicht in crontab -l, sondern in /etc/cron.d/root; wer an der falschen Stelle nachschaut, hält den Cron für kaputt.

Telegram, ohne einen Bot zu bauen

Der hübscheste Teil ist der, der komplett fehlt: die Einrichtung des Telegram-Bots. Wer auf Unraid die Telegram-Benachrichtigungen der Weboberfläche nutzt, hat Token und Chat-ID nämlich schon im Klartext auf der Platte liegen — unter /boot/config/plugins/dynamix/telegram/ in den Dateien token und chatid. Das Skript liest einfach diese beiden Dateien und spricht die Telegram-API direkt an. Kein neuer Bot, keine neue Chat-ID-Suche, und die Nachricht landet im selben Kanal wie die übrigen Server-Meldungen — mit antippbarem Bestell-Link, sodass der Kauf direkt am Handy klappt.

Die Zeile, auf die es ankommt

Ein Wächter hat eine tückische Eigenschaft: Er ist nur dann nachweislich gesund, wenn er anschlägt. Solange er schweigt, kann das zwei Gründe haben — es gibt nichts zu melden, oder er ist längst tot. Baut der Shop seine Seite um, ändert sich das JSON-Format, greift eine Bot-Erkennung: In allen drei Fällen extrahiert das Skript schlicht keinen Status mehr, und ein naiv gebautes Skript würde ab diesem Moment für immer schweigen. Man wartet dann wochenlang auf eine Nachricht, die nie kommen kann.

Diese Lektion habe ich an anderer Stelle schmerzhaft gelernt: Ein Überwachungsskript von mir lief einmal tagelang scheinbar normal weiter, während seine eigentliche Prüfung längst ins Leere lief. Seitdem gilt bei mir die Regel: Jeder Wächter braucht eine zweite Meldung — die, dass er blind geworden ist. Hier sind das die fünf Fehlschläge in Folge, nach denen sich das Skript einmalig selbst verpetzt. Ein einzelner zählt nicht, das wäre bei jedem Netzwerk-Schluckauf ein Fehlalarm.

Ein Wächter, der stirbt, ohne sich zu melden, ist schlimmer als gar keiner — denn mit ihm hört man auch auf, selbst nachzusehen.

Zwei kleinere Entscheidungen nach demselben Muster: Gemeldet werden nur Übergänge (ausverkauft → bestellbar und zurück), nie der Dauerzustand — sonst käme alle drei Minuten dieselbe Nachricht. Und Zustand samt Log liegen in /var/tmp, also im RAM: Ein Lauf alle drei Minuten würde sonst Tag für Tag auf den USB-Stick schreiben, von dem Unraid bootet. Nach einem Neustart fängt der Wächter dadurch zustandslos an — das kostet schlimmstenfalls eine doppelte Meldung, nie eine verpasste.

Wie oft darf man fragen?

Ein Wort zur Höflichkeit, denn die Grenze zwischen „nachsehen“ und „belästigen“ ist real. Dieser Wächter ruft alle drei Minuten genau eine Produktseite ab, mit normalem Browser-Kennzeichen, ohne Tricks gegen Schutzmaßnahmen — das sind 480 Seitenaufrufe am Tag, weniger als ein einziger menschlicher Besucher erzeugt, der sich durch den Shop klickt. Das ist die Sorte Abruf, für die Webserver gebaut sind. Wer dagegen im Sekundentakt pollt, parallel Dutzende Seiten abgrast oder Sperren umgeht, ist kein Wächter mehr, sondern ein Problem. Und sobald ein Shop eine offizielle Schnittstelle oder einen brauchbaren Benachrichtigungsweg anbietet, gehört der genutzt — hier war er eben nachweislich zu langsam.

Und dann kam der Restock — acht Tage zu früh

Der Shop hatte den nächsten Nachschub für den 23. September angekündigt. Der Wächter lief seit dem Abend des 12. September, alle drei Minuten, und meldete drei Tage lang dasselbe: ausverkauft. Am 15. September um 14:09 Uhr kippte der Status — acht Tage vor dem angekündigten Termin. In derselben Minute lag die Telegram-Nachricht mit dem Bestell-Link auf meinem Handy, und die Bestellung ging noch am selben Tag durch.

Bis zu diesem Alarm hatte das Skript 1.293 Mal nachgesehen. Dreimal in diesen drei Tagen kam vom Shop eine leere Antwort zurück — jeweils ein einzelner Aussetzer, gefolgt von einem normalen Lauf. Die Schwelle von fünf Fehlschlägen in Folge hat also genau das getan, wofür sie da ist: drei Netzwerk-Schluckaufe stillschweigend verdaut und keinen Fehlalarm ausgelöst. Hätte der Shop die Seite umgebaut, hätte sie trotzdem angeschlagen.

Eine Erwartung hat der Tag allerdings nicht bestätigt: Die Viertelstunde aus dem Forum blieb aus. Das Gerät war über fünf Stunden bestellbar, bis der Wächter um 19:33 Uhr die Entwarnung schickte — und um 19:51 Uhr, achtzehn Minuten später, den nächsten Alarm, weil offenbar noch einmal nachgefüllt wurde. Als ich das Protokoll am Abend auswertete, stand das Gerät immer noch im Shop. Hätte ich also einfach abends nachsehen können? Im Rückblick ja — nur weiß man das eben erst im Rückblick, und beim Nachschub davor waren es laut Forum fünfzehn Minuten. Die eigentliche Pointe liegt sowieso woanders: Der angekündigte Termin war um acht Tage falsch. Wer sich den 23. in den Kalender geschrieben hätte, hätte an diesem Nachmittag gar nicht nachgesehen. Ein Wächter schlägt jeden Kalender.

Der Wächter selbst ist damit arbeitslos und wird wieder abgebaut: Cron-Datei löschen, update_cron, Skriptordner entfernen. Ein Wächter ohne Auftrag, der weiterläuft, ist nur die nächste vergessene Baustelle.

Die Fassung zum Nachbauen

Das Skript oben ist auf ein Gerät und einen Telegram-Bot zugeschnitten. Für die Veröffentlichung habe ich es verallgemeinert: Produkt, Suchmuster und Meldeweg stehen in einer Konfigurationsdatei, und statt nur Telegram spricht es ntfy, Gotify, Pushover, Discord, Slack, E-Mail, einen beliebigen Webhook, die Unraid-eigene Benachrichtigung oder ein eigenes Kommando an — auch mehrere zugleich. Dazu kommen ein Trockenlauf, eine Testnachricht und eine Statusabfrage. Der Telegram-Trick von oben steckt weiter drin: Auf Unraid darf man Token und Chat-ID einfach leer lassen. Es liegt unter github.com/maTzko13/restock-watch (MIT-Lizenz), samt Vorlagen für Unraid-Cron und systemd-Timer.

Was ich daraus mitgenommen habe

  • Vor dem Bauen prüfen, was der Shop einem Skript freiwillig verrät — oft steckt der komplette Lagerstatus schon im HTML, und curl plus grep ersetzen jeden Browser-Roboter.
  • Nicht auf „verfügbar“ prüfen, sondern auf das Verlassen von „ausverkauft“ — dann übersteht die Logik auch neue Wortwahl im Shop.
  • Jeder Wächter braucht die zweite Meldung: die, dass er selbst blind geworden ist. Schweigen ist sonst nicht von Gesundheit zu unterscheiden.
  • Meldungen nur bei Zustandswechseln, Zustand in /var/tmp, Abrufe im Minutentakt statt Sekundentakt — Automatisierung darf man auch rücksichtsvoll bauen.
  • Was schon da ist, wiederverwenden: Der Telegram-Bot der Unraid-Benachrichtigungen bedient jedes eigene Skript gleich mit.
  • Die Nachschub-Prognose eines Shops ist eine Schätzung — hier lag sie acht Tage daneben. Wer sich auf sie verlässt, sieht am falschen Tag nach.

← Alle Notizen · RSS-Feed

Dieselbe Bauart — regelmäßig nachsehen, nur Änderungen melden, und sich selbst überwachen — passt auf vieles, was in Firmen sonst niemand im Blick hat: auslaufende Zertifikate, Domains, Garantiefristen, Preisänderungen bei Lieferanten. Wenn Sie so einen Wächter für etwas brauchen, schreiben Sie mir.

Transparenz: Diese Notiz ist mit KI-Unterstützung geschrieben — auf Basis meiner eigenen Aufzeichnungen und Systeme, inhaltlich von mir geprüft. Die redaktionelle Verantwortung liegt bei mir.

Die Konfigurationen hier stammen aus meinem privaten Homelab. Prüfen Sie vor dem Nachbau die Nutzungsbedingungen des jeweiligen Shops — und bauen Sie Abruf-Intervalle großzügig, nicht knapp.