Config, systemd units, and installers for a small personal ADS-B fleet: remote Raspberry Pi feeder nodes at friends' houses stream 1090+978 over Tailscale to a Debian VM hub (Hyper-V, RTL-SDR over USB/IP), which relays every site — separately — to a friend's aggregation server over a persistent SSH tunnel.
node "kcxp" (Pi 4, Carson City) hub (Debian VM on Hyper-V)
┌──────────────────────────────┐ ┌──────────────────────────────────────┐
│ SDR 00001090 → readsb :30005 ├─┐ tailscale │ readsb-ingest@kcxp │
│ SDR 00000978 → dump978 :30978├─┼───────────►│ 1090 beast_in + 978 uat_in │
│ tar1090 :8080 │ │ │ → :32011 beast-reduce out │
└──────────────────────────────┘ │ │ │ │
│ │ ▼ │
node "..." (one per site) ──────┘ │ ssh-forward@kcxp │
│ batcher (ICAO filter) │ friend's server
hub's own SDR (USB/IP from │ → -L 127.0.0.1:29011 ──────────────┼──ssh──► :30011 (kcxp)
Hyper-V host, usbip-watchdog) │ │
└─► readsb :30005 ────────────────────────►│ ssh-forward@home → -L :29004 ────────┼──ssh──► :30004 (home)
│ │ │
├─► fr24feed / adsbexchange (home) │ ssh-master (one connection, │
└─► tar1090 :80 ─────────────────────►│ all forwards) -R :8008 web UI ─────┼──ssh──► :8008
│ adsb-status :8090 (fleet health) ····┼──ssh··► :8009 (optional)
└──────────────────────────────────────┘
One site = one stream = one identity, end to end. Site streams are never merged before an external consumer: each site keeps its own tunnel sink port, its own location on the friend's map, and (eventually) its own aggregator station identities.
| Path | What |
|---|---|
| hub/ | hub install/upgrade, per-site ingest, tunnel, status page |
| node/ | feeder-Pi installer + bench provisioning runbook |
| sites.d/ | fleet registry — one env file per site (.example committed, real files gitignored) |
| docs/ports.md | authoritative port map (SITE_INDEX drives everything) |
| docs/tailscale-acl.md | expected tailnet ACL (hub pulls; nodes initiate nothing) |
| docs/shipping-checklist.md | pre-ship bench checklist |
| tests/ | pytest for the BEAST batcher and status-page logic |
- Generators over hand-edits: everything derived from
sites.d/(tunnel forwards, unit instances) is regenerated byhub/install.sh. Adding a site is one env file + one command on the hub. - Secrets (tailnet keys, real coords/UUIDs, WiFi PSKs) never land in git;
every secret-bearing file has a committed
.exampletwin. - Shell is bash with
set -euo pipefail, shellcheck-clean; units passsystemd-analyze verify. - Template units (
readsb-ingest@,ssh-forward@) rather than per-site copies.
- Hub (existing single-site VM → fleet hub): see hub/README.md.
- New feeder node: follow the runbook in node/README.md top to bottom — including the foreign-LAN dress rehearsal — before shipping.