From 7c218efd8c03d4b06f7a3b402d4cd7153fb69361 Mon Sep 17 00:00:00 2001 From: Josh Poole Date: Sat, 26 Sep 2026 00:29:06 +0100 Subject: [PATCH] 20260926 - Point the tar1090 ADS-B fallback at adsb.retina.fm first tar1090.adsb_upstreams is the ordered list of adsb.lol-format sources the tar1090 proxy tries until one answers, emitted to tar1090.env as ADSB_UPSTREAMS and consumed by offworldlabs/tar1090-node#20. It ships as "https://adsb.retina.fm,https://api.adsb.lol": RETINA's own service first, adsb.lol behind it. Both compose manifests must list the variable. The proxy reads it from its own environment, so a manifest that omits it leaves the proxy on its built-in default with no error anywhere. Verified against jonathan-node-1, whose running container carries only ADSBLOL_ENABLED and ADSBLOL_RADIUS today. No migration. The key is new, so no deployed node has it in its first-boot copy of user.yml and the shipped default reaches all of them. Changing this default later will need a migrate_*(), as the four existing ones do. Co-Authored-By: Claude Opus 5 (1M context) --- config-merger/script/merge_config.py | 7 ++++- config-merger/test/test_merge_config.py | 34 +++++++++++++++++++++++++ config/default.yml | 5 ++++ docker-compose.yml | 1 + staging/docker-compose.staging.yml | 1 + 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/config-merger/script/merge_config.py b/config-merger/script/merge_config.py index 8c1c07d..3ae2f7a 100755 --- a/config-merger/script/merge_config.py +++ b/config-merger/script/merge_config.py @@ -132,7 +132,12 @@ def generate_env_file(config, output_dir): adsblol = bool(tar1090_config.get('adsblol_fallback', False)) and sited lines.append("# adsb.lol integration\n") lines.append(f"ADSBLOL_ENABLED={'true' if adsblol else 'false'}\n") - lines.append(f"ADSBLOL_RADIUS={tar1090_config.get('adsblol_radius', 40)}\n\n") + lines.append(f"ADSBLOL_RADIUS={tar1090_config.get('adsblol_radius', 40)}\n") + # Ordered chain of sources the tar1090 proxy tries until one answers. + # Written even when the fallback is off, so turning it on later needs no + # second config change. + upstreams = str(tar1090_config.get('adsb_upstreams') or 'https://api.adsb.lol').strip() + lines.append(f"ADSB_UPSTREAMS={upstreams}\n\n") # External ADS-B feed for readsb adsb_source = tar1090_config.get('adsb_source', '') diff --git a/config-merger/test/test_merge_config.py b/config-merger/test/test_merge_config.py index 0138a79..d87d02c 100755 --- a/config-merger/test/test_merge_config.py +++ b/config-merger/test/test_merge_config.py @@ -1014,6 +1014,40 @@ def test_tracker_removal_does_not_rewrite_user_yml(self): self.assertEqual(user['network']['ports']['track'], 3003) + def test_upstream_chain_written_to_env(self): + """The chain reaches the proxy as ADSB_UPSTREAMS""" + self.write_yaml(os.path.join(self.defaults_dir, 'default.yml'), { + 'tar1090': { + 'adsblol_fallback': True, + 'adsb_upstreams': 'https://adsb.retina.fm,https://api.adsb.lol', + 'location': {'latitude': 42.2, 'longitude': -72.7, 'altitude': 80}, + } + }) + self.write_yaml(os.path.join(self.defaults_dir, 'forced.yml'), {}) + + self.run_merge() + + with open(os.path.join(self.config_dir, 'tar1090.env')) as f: + env = f.read() + self.assertIn('ADSB_UPSTREAMS=https://adsb.retina.fm,https://api.adsb.lol\n', env) + + def test_upstream_chain_falls_back_to_adsblol_when_unset(self): + """A config predating the key still produces a usable chain""" + self.write_yaml(os.path.join(self.defaults_dir, 'default.yml'), { + 'tar1090': { + 'adsblol_fallback': True, + 'location': {'latitude': 42.2, 'longitude': -72.7, 'altitude': 80}, + } + }) + self.write_yaml(os.path.join(self.defaults_dir, 'forced.yml'), {}) + + self.run_merge() + + with open(os.path.join(self.config_dir, 'tar1090.env')) as f: + env = f.read() + self.assertIn('ADSB_UPSTREAMS=https://api.adsb.lol\n', env) + + class TestAtomicWrites(unittest.TestCase): """The compose .env must be replaced whole, never rewritten in place. diff --git a/config/default.yml b/config/default.yml index 50cad7c..e31ec00 100644 --- a/config/default.yml +++ b/config/default.yml @@ -120,6 +120,11 @@ tar1090: adsb_source: "" # Optional local ADS-B receiver (IP,PORT,PROTOCOL). Empty = rely on adsblol_fallback. adsblol_fallback: true adsblol_radius: 40 + # Ordered, comma-separated list of adsb.lol-format sources the tar1090 proxy + # tries until one answers. A local receiver still wins over all of them. + # adsb.lol refuses ~88% of a node's requests with 429, so RETINA's own + # service goes first and adsb.lol sits behind it. + adsb_upstreams: "https://adsb.retina.fm,https://api.adsb.lol" # retina-tracker sidecar (github.com/offworldlabs/retina-tracker) config. # blah2 has no built-in tracker of its own; this sidecar does all tracking. diff --git a/docker-compose.yml b/docker-compose.yml index 32b3565..c264dd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -120,6 +120,7 @@ services: - TAR1090_DEFAULTCENTERLON=${RECEIVER_LON:-0} # adsb.lol proxy config - ADSBLOL_ENABLED=${ADSBLOL_ENABLED:-false} + - ADSB_UPSTREAMS=${ADSB_UPSTREAMS:-https://api.adsb.lol} - RECEIVER_LAT=${RECEIVER_LAT:-0} - RECEIVER_LON=${RECEIVER_LON:-0} - ADSBLOL_RADIUS=${ADSBLOL_RADIUS:-40} diff --git a/staging/docker-compose.staging.yml b/staging/docker-compose.staging.yml index d19f36b..f770e99 100644 --- a/staging/docker-compose.staging.yml +++ b/staging/docker-compose.staging.yml @@ -106,6 +106,7 @@ services: - TAR1090_DEFAULTCENTERLON=${RECEIVER_LON:--84.65191} # adsb.lol proxy config - ADSBLOL_ENABLED=${ADSBLOL_ENABLED:-true} + - ADSB_UPSTREAMS=${ADSB_UPSTREAMS:-https://api.adsb.lol} - RECEIVER_LAT=${RECEIVER_LAT:-33.939182} - RECEIVER_LON=${RECEIVER_LON:--84.65191} - ADSBLOL_RADIUS=${ADSBLOL_RADIUS:-40}