docker-planesnitch: Verpfeift Jedes Flugzeug, Das Es Wagt, Über Dich zu Fliegen

Ich wohne unter einer Einflugschneise. Militärische Transportflugzeuge, Regierungsjets, Polizeihubschrauber, ab und zu ein Notfall-Squawk, das alles fliegt mir über den Kopf und ich hatte keine Ahnung, was davon was war. Klar, ich hätte Flightradar24 aufmachen und den ganzen Tag auf eine Karte starren können, aber ich bin kein Rentner mit Fernglas. Ich wollte etwas, das den Himmel für mich im Auge behält und mich anbrüllt, wenn etwas Interessantes auftaucht.
Also habe ich planesnitch gebaut. Ein Docker-Container, der öffentliche ADS-B-APIs abfragt, Flugzeuge gegen deine Beobachtungslisten prüft und Alarme an Telegram oder Webhooks feuert. Militärjets, Regierungsschnüffler, Notfall-Squawks, zwielichtige Tiefflieger, was immer du ihm zu beobachten aufträgst. Mehrere Standorte gleichzeitig. Keine SDR-Hardware, keine Antenne, kein Scheiß. Nur eine Konfigurationsdatei und eine Internetverbindung.

Wie Es Funktioniert

In jedem Abfragezyklus (konfigurierbar, Standard 1 Minute) holt planesnitch Flugzeugdaten von bis zu 4 öffentlichen ADS-B-APIs, parallel:

Es dedupliziert über den ICAO-Hex-Code und behält den Eintrag mit den meisten Datenfeldern. Manche Quellen liefern angereicherte Daten, Flugzeugtyp, Halter oder Betreiber, Kennzeichen, Baujahr, während andere dir nur rohe ADS-B-Felder geben. Planesnitch behält automatisch den reichhaltigsten Eintrag pro Flugzeug.
Danach schickt es jedes Flugzeug durch deine Beobachtungslisten und Alarmregeln. Treffer gefunden? Es formatiert eine Nachricht und feuert sie auf deinen Telegram-Bot oder deinen Webhook-Endpoint. Cooldowns pro Alarm und pro Flugzeug verhindern, dass es dich wegen derselben C-17 zuspammt, die drei Stunden lang Runden dreht.
Wenn du deinen eigenen ADS-B-Empfänger mit ultrafeeder betreibst, kannst du planesnitch auch darauf richten, dieselbe Konfiguration, du fügst nur eine lokale Quell-URL hinzu.

Einrichtung

# Grab the config and edit it
curl -sL 
  https://raw.githubusercontent.com/psyb0t/docker-planesnitch/main/config.yaml.example 
  -o config.yaml
# Optional: download military/gov/police watchlists
mkdir -p csv
BASE=https://raw.githubusercontent.com/sdr-enthusiasts/plane-alert-db/main
curl -sLo csv/plane-alert-mil.csv $BASE/plane-alert-mil.csv
curl -sLo csv/plane-alert-gov.csv $BASE/plane-alert-gov.csv
curl -sLo csv/plane-alert-pol.csv $BASE/plane-alert-pol.csv
# Run it
docker run 
  -v ./config.yaml:/app/config.yaml:ro 
  -v ./csv:/csv:ro 
  psyb0t/planesnitch

Das war’s. Du trägst deine Koordinaten und den Token deines Telegram-Bots in die Konfiguration ein, und schon petzt du.

Die Konfiguration

Eine einzige YAML-Datei steuert alles. Du legst fest, wo du bist, worauf geachtet wird, wann alarmiert wird, und wohin es geht.

Standorte

Überwache so viele Orte, wie du willst. Dein Haus, dein Büro, Omas Haus, Area 51, jeder mit eigenem Suchradius.

locations:
  home:
    name: "Home"
    lat: 38.8719
    lon: -77.0563
    radius: 150km
  area51:
    name: "Area 51"
    lat: 37.2350
    lon: -115.8111
    radius: 50nm

Einheitensuffixe funktionieren überall, km, mi, nm, ft, m. Nackte Zahlen fallen bei Entfernungen auf km zurück und bei Höhen auf ft.
Automatische Gruppierung naher Standorte. Wenn zwei Standorte nah genug beieinander liegen, dass ein einziger API-Aufruf beide abdeckt, fasst planesnitch sie automatisch zusammen und macht eine Anfrage nach oben statt zweier. Du konfigurierst Haus und Büro beide mit 50km Radius und sie liegen 30km auseinander? Ein API-Treffer, die Ergebnisse an beide Standorte verteilt. Definier ein Dutzend überlappende Punkte, planesnitch führt weiter zusammen, bis eine Gruppe an den maximalen Umkreis stößt, dann fängt es eine neue an. Das hindert dich daran, dein Rate-Limit-Kontingent mit redundanten Abfragen auf denselben Fleck Himmel zu verbrennen.
Cooldowns pro Quelle. Jede vorgelagerte Quelle bekommt ihren eigenen Rate-Limit-Cooldown, wenn adsb.lol anfängt, 429 zu werfen, nimmt sich diese eine Quelle nach ihrem eigenen Zeitplan zurück, während adsb.fi und dein lokaler ultrafeeder ganz normal weiter abfragen. Keine einzelne API-Drosselung legt den ganzen Feed lahm.

Beobachtungslisten

Sechs Arten von Beobachtungslisten sagen planesnitch, wen es verpfeifen soll:

watchlists:
  # Emergency squawk codes
  emergencies:
    type: squawk
    values: ["7500", "7600", "7700", "7400", "7777"]
  # 8,709 military aircraft from community database
  military:
    type: icao_csv
    source: plane-alert-mil.csv
  # Government aircraft
  government:
    type: icao_csv
    source: plane-alert-gov.csv
  # Stalk specific aircraft by ICAO hex
  my_planes:
    type: icao
    values: ["4ca123", "a12345"]
  # Stalk by aircraft type — any A400M, Rafale, or Alpha Jet
  cool_jets:
    type: icao_type
    values: ["A400", "RFAL", "AJET"]
  # Everything within radius
  everything:
    type: all
  # WTF just buzzed my house
  low_flyers:
    type: proximity
    min_altitude: 0ft
    max_altitude: 3000ft

Die Squawk-Codes sind die Paniktasten der Luftfahrt, 7700 ist allgemeiner Notfall, 7600 ist Funkausfall, 7500 ist Entführung, 7400 ist Notfall eines unbemannten Luftfahrzeugs, 7777 ist militärisches Abfangen. Die Sorte Scheiß, von der du wissen willst, wenn sie 6 Meilen von deinem Haus entfernt passiert.
Der Typ icao_csv greift auf plane-alert-db zu, eine von der Community gepflegte Datenbank mit über 15.000 interessanten Luftfahrzeugen, betreut von den feinen Wahnsinnigen der Plane-Spotting-Szene:

  • Militär, 8.709 Luftfahrzeuge
  • Regierung, 1.743 Luftfahrzeuge
  • Polizei, 932 Luftfahrzeuge
  • Zivil, 4.530 bemerkenswerte Luftfahrzeuge
  • Privatsphäre (PIA), 94 datenschutzbewusste Betreiber
  • Alles, 15.914 zusammen

Die Beobachtungsliste icao_type (v1.6) matcht auf den Typenbezeichner nach ICAO doc 8643, den 3-4 Zeichen langen Code, mit dem die Luftfahrt das Flugzeugmuster selbst identifiziert, nicht das Kennzeichen. C17 ist jede C-17 Globemaster auf dem Planeten. B738 ist jede 737-800. RFAL ist jede Rafale. AJET ist jeder Alpha Jet. Wirf die Bezeichner, die dich interessieren, in values: und du bekommst Alarme für jede Zelle dieses Musters, die in deinen Radius fliegt, egal wem sie gehört oder welches Kennzeichen sie trägt.
Die Näherungs-Beobachtungsliste ist zum Einfangen von Tieffliegern da. Du setzt einen Höhenbereich und planesnitch alarmiert dich, wenn irgendetwas innerhalb deines Radius unterhalb dieser Decke fliegt. Praktisch, um “was zum Teufel war das denn” zu beantworten, wenn um 2 Uhr nachts etwas deine Fenster zum Klappern bringt.

Alarme

Du verbindest Beobachtungslisten mit Benachrichtigungszielen. Optional nach Standort filtern, lässt du das weg, werden alle Standorte geprüft. Cooldowns verhindern Spam:

alerts:
  - name: "Emergency Alert"
    watchlists: [emergencies]
    cooldown: 1m
    notify: [tg_emergencies]
  - name: "Military Spotter"
    watchlists: [military, government]
    cooldown: 5m
    notify: [tg_spotting]
  - name: "Everything at Home"
    locations: [home]
    watchlists: [everything]
    cooldown: 1m
    notify: [tg_main]

Dauer-Strings unterstützen s, m, h, also gehen 5m, 1h30m, 90s, oder schlichte Sekunden. Verschiedene Alarme können an verschiedene Telegram-Kanäle gehen, Notfälle an den einen, militärisches Spotting an den nächsten, Tiefflieger an einen dritten.

Benachrichtigungen

Telegram und Webhooks. Route verschiedene Alarme an verschiedene Ziele:

notifications:
  tg_emergencies:
    type: telegram
    bot_token: "123456:ABC-DEF"
    chat_id: "-100123456789"
  tg_spotting:
    type: telegram
    bot_token: "123456:ABC-DEF"
    chat_id: "-100987654321"
  my_webhook:
    type: webhook
    url: "https://example.com/hook"
    headers:
      Authorization: "Bearer xxx"

Flugzeugfotos (v1.6)

Wenn ein Flugzeug einen ICAO-Typenbezeichner hat (etwa C17, B738, RFAL), holt planesnitch das passende Flugzeugfoto von doc8643.com und:

  • hängt es der Telegram-Nachricht als Foto an (der Alarmtext wird zur Bildunterschrift, und wenn die Unterschrift über Telegrams Grenze von 1024 Zeichen geht, wird sie in eine Textnachricht gefolgt vom Foto aufgeteilt, damit dir der Inhalt nie verloren geht)
  • bettet die JPEG-Bytes (base64) in Webhook-Payloads ein, im neuen Feld image_base64, null, wenn kein Bild im Cache liegt

Bilder werden auf der Platte unter /images im Container gecacht, mounte -v ./images:/images, um sie über Neustarts zu retten. Der Cache ist nach Typenbezeichner geschlüsselt, alle über 8.000 C-17 teilen sich also eine Fotodatei. Fehlschläge werden als .notfound-Marker festgehalten, damit Typen ohne doc8643-Foto nicht in jedem Zyklus neu angefragt werden. Lösche das Cache-Verzeichnis, um eine Auffrischung zu erzwingen.

Das Cache-Verzeichnis benutzt atomare Schreibvorgänge (.tmp plus os.replace) und einen asyncio.Lock pro Typ, damit sich mehrere gleichzeitig feuernde Alarme zum selben Flugzeugtyp beim Holen nicht gegenseitig in die Quere kommen. Typenbezeichner aus den vorgelagerten ADS-B-Feeds werden gegen ^[A-Z0-9]{1,8}$ geprüft, bevor das Dateisystem überhaupt angefasst wird, kein Pfad-Traversal, das durch ein vergiftetes t-Feld eingeschmuggelt wird, und jede Antwort mit einem Content-Type, der nicht image/* ist, wird abgelehnt, damit Cloudflares HTML-Challenge-Seiten den Cache nicht vergiften können.

Bilder Abbestellen, pro Ziel (v1.7)

Fotos sind großartig für einen persönlichen Telegram-Kanal. Sie sind ein verdammter Albtraum für einen Home-Assistant-Webhook-Empfänger, der in jedem Zyklus 80 KB base64 durchkauen muss, oder für einen vollen Spotter-Chat, in dem längst jeder weiß, wie eine C-17 aussieht. Also nimmt jedes Benachrichtigungsziel attach_image: false an, um auf reinen Text herunterzugehen:

notifications:
  tg_main:
    type: telegram
    bot_token: "..."
    chat_id: "..."
    attach_image: false   # plain sendMessage, no photo
  my_webhook:
    type: webhook
    url: "https://example.com/hook"
    attach_image: false   # image_base64 will be null

Der Standard ist true, den reinen Text wählst du also aktiv. Der clevere Teil: wenn alle Benachrichtigungsziele an einer bestimmten Alarmregel abbestellen, überspringt planesnitch das Holen von doc8643 für diesen Alarm komplett. Kein vergeudeter Netzwerkzugriff, kein vergeudeter Plattenzugriff, kein unnötig genommenes Lock. Ziele mit attach_image=true und attach_image=false am selben Alarm zu mischen holt trotzdem nur einmal und bedient beide, der Foto-Cache ist gemeinsam.

Wie die Alarme Aussehen

Telegram-Alarme sind mit Emojis formatiert und enthalten alle Daten, die du auf einen Blick haben willst:

🔔 Emergency Alert
🚨 squawk 7700 (EMERGENCY)
✈️ RYR1234
🛩️ BOEING 737-800 | EI-ABC | 2015
💼 RYANAIR
📍 45.5000, 28.1000 | 3,200 ft
📏 6 nm from home
💨 280 kts
🗺️ https://globe.adsb.fi/?icao=4ca123
🔔 Military Spotter
✈️ TEDDY64
🛩️ BOEING C-17A Globemaster III | 94-0067 | 1994
💼 USAF
🏷️ USAF — USAF
📍 37.9306, -78.7019 | 12,350 ft
📏 99 nm from home
💨 413 kts
📡 squawk 1613
🗺️ https://globe.adsb.fi/?icao=ae07e1

Klick auf den Link und du bekommst eine Echtzeitkarte des Flugzeugs auf globe.adsb.fi. Die Squawk-Zeile enthält die Bedeutung, planesnitch hat eine eingebaute Datenbank mit den Bedeutungen und Geltungsbereichen der Squawk-Codes, es sagt dir also, warum dieser Code zählt.
Webhook-Payloads sind JSON-Arrays mit vollständigen Metadaten, Flugzeugdetails, Trefferbegründung, Infos zur Beobachtungsliste, CSV-Metadaten, wenn es aus plane-alert-db kommt, Entfernung zum Standort, und Anzeigeeinheiten. Alles, was du brauchst, um darauf eigene Integrationen zu bauen.

Anzeigeeinheiten

Drei Voreinstellungen steuern, wie Höhe, Entfernung und Geschwindigkeit in den Alarmen auftauchen:

display_units: aviation   # ft / nm / kts (default)
display_units: metric     # m / km / km/h
display_units: imperial   # ft / mi / mph

Die Umrechnung passiert beim Senden. Die interne Rechnerei ist immer metrisch. Nimm, was für deine Lage Sinn ergibt, bist du Pilot, Luftfahrteinheiten. Bist du ein normaler Mensch, metrisch. Bist du Amerikaner, imperial.

Hot Reload und Health

Konfigurationsänderungen werden im nächsten Abfragezyklus übernommen, ohne den Container neu zu starten. CSV-Beobachtungslisten frischen sich alle 24 Stunden selbst auf. Ein Health-Endpoint auf Port 8080 gibt Uptime, Zeitpunkt der letzten Abfrage und Flugzeuganzahl heraus, für Monitoring und Orchestrierung.

Unterm Strich

Ein Docker-Container, der den Himmel beobachtet und jedes interessante Flugzeug bei deinem Telegram verpetzt. Militärjets, Regierungsmaschinen, Notfall-Squawks, zwielichtige Tiefflieger, bestimmte Kennzeichen, ganze Flugzeugmuster, was immer du willst. Mehrere Standorte, mehrere Quellen, mehrere Benachrichtigungsziele, doc8643-Flugzeugfotos direkt an deine Alarme gehängt, alles aus einer einzigen YAML-Datei.
Keine SDR-Hardware. Keine Antenne. Kein eigener Empfänger. Nur öffentliche ADS-B-APIs und eine paranoide Konfigurationsdatei.

Jetzt Mit Agent-Skill

Das Repo liefert einen Agent-Skill unter .agents/skills/planesnitch/, von der CI bei Tag-Pushes auf ClawHub veröffentlicht. Er dokumentiert die ganze Beobachtungsfläche, die Militär-, Regierungs- und Polizei-CSVs von plane-alert-db, die Notfall-Squawks 7500/7600/7700, eigene ICAO-Hex- und Typenlisten, Höhenschwellen für Tiefflieger, oder einfach “alles”, wenn du dich selbst hasst, quer über alle kostenlosen ADS-B-APIs, aus denen es ziehen kann.
Statt also einem Assistenten jedes Mal deinen eigenen Flugzeugschnüffler zu erklären, installiert er den Skill und weiß schon, welche Stellschrauben es gibt.
Hol es dir: github.com/psyb0t/docker-planesnitch
Unter WTFPL lizenziert, denn Flugzeuge zu verpetzen sollte keinen Lizenzvertrag erfordern.

Rein Damit In Deinen Agenten

Damit dein Assistent den Flugzeugschnüffler aufsetzen kann, ohne dass du ihm die Konfiguration vorliest. Alles unter .agents/ ist in einem einzigen Marketplace katalogisiert, also sind es zwei Befehle:

claude plugin marketplace add psyb0t/agents
claude plugin install planesnitch@psyb0t

Codex nutzt denselben Marketplace mit einem anderen Verb, codex plugin add planesnitch@psyb0t, weil es kein codex plugin install gibt. Er findet den Skill außerdem von allein in einem Checkout des Repos, da er .agents/skills/ nativ scannt, ganz ohne Installation.