Architecture¶
The system separates radio decoding, transport, presentation, and documentation. The Mac remains the source of truth for live RF data; the Linux relay is the source of truth for remotely visible telemetry history.
System context¶
C4Context
title Antenna Observatory system context
Person(visitor, "Visitor", "Uses the public dashboard")
System(obs, "Antenna Observatory", "Collects, stores, and presents aircraft receiver telemetry")
System_Ext(airplanes, "airplanes.live", "Receives BeastReduce+ aircraft messages")
System_Ext(github, "GitHub", "Version control, CI/CD, and documentation hosting")
System_Ext(cloudflare, "Cloudflare", "DNS and encrypted tunnel ingress")
Rel(visitor, obs, "Views", "HTTPS")
Rel(obs, airplanes, "Feeds decoded frames", "TCP 30004")
Rel(github, obs, "Deploys tested releases", "SSH")
Rel(cloudflare, obs, "Routes dashboard traffic", "Tunnel") Deployment topology¶
flowchart TB
subgraph Home[Mac receiver station]
Ant[Antenna] --> SDR[RTL-SDR USB]
SDR --> Readsb[readsb]
Readsb --> JSON[JSON files]
Readsb --> Beast[Loopback Beast port 30905]
JSON --> Collector[Observatory collector]
Beast --> Collector
Awake[caffeinate LaunchAgent] -. keeps awake .-> Readsb
Collector --> Uplink[Telemetry uploader]
end
Readsb -->|TCP 30004| AL[airplanes.live]
Uplink -->|HTTPS bearer token| Ingest[Relay /api/ingest]
subgraph VPS[Existing Linux server]
Ingest --> Relay[Python relay]
Relay --> SQLite[(SQLite history)]
Relay --> Static[React static application]
Tunnel[cloudflared] --> Relay
Supervisor[Unprivileged supervisor] -. restarts .-> Relay
Supervisor -. restarts .-> Tunnel
end
Browser[Owner browser] -->|HTTPS| CF[Cloudflare edge]
CF --> Tunnel Process ownership¶
flowchart LR
subgraph launchd[macOS user LaunchAgents]
R[local.airplanes-live.readsb]
W[local.antenna-observatory.web]
U[local.antenna-observatory.uplink]
K[local.antenna-observatory.keepawake]
end
subgraph linux[Linux user processes]
SR[relay supervisor] --> RP[relay process]
ST[tunnel supervisor] --> TP[cloudflared]
end
R --> W --> U --> RP
K -. prevents idle sleep .-> R
TP --> RP Trust boundaries¶
flowchart LR
subgraph T1[Trusted Mac]
USB[USB receiver]
COL[Collector]
TOKEN1[Relay token file]
end
subgraph T2[Public network]
HTTPS[Encrypted HTTPS]
TCP[Outbound aircraft feed]
end
subgraph T3[Trusted VPS account]
APP[Relay application]
TOKEN2[Matching relay token]
DB[(History database)]
end
subgraph T4[Public browser]
DASH[Dashboard and read APIs]
end
USB --> COL
TOKEN1 --> COL
COL --> HTTPS --> APP
TOKEN2 --> APP
APP --> DB
APP --> DASH
COL --> TCP The relay binds only to loopback. Cloudflare Tunnel makes the application reachable without opening an inbound VPS port. SSH deployment uses a dedicated key, and the wiki synchronization uses a different repository-scoped deploy key.
Data model¶
erDiagram
SAMPLE {
real ts PK
text payload
}
EVENT {
real ts
text level
text message
}
SNAPSHOT {
real now
string state
object metrics
array aircraft
array signals
object host
object hardware
}
SNAPSHOT ||--o{ SAMPLE : summarized_into
SNAPSHOT ||--o{ EVENT : emits Samples and events use seven-day rolling retention. The latest relay snapshot and a bounded decoder-log tail are also persisted so a relay restart can recover the last known view.