diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index d5c5761b1..8fda82aa4 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -40,7 +40,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: Dockerfile.tar1090 + file: Dockerfile platforms: linux/arm64 push: false load: true diff --git a/Dockerfile b/Dockerfile index 93406badc..d9f92f5a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,17 @@ -FROM debian:bullseye-slim +FROM ghcr.io/sdr-enthusiasts/docker-tar1090:latest -RUN apt-get update && apt-get install -y \ - nginx \ - lighttpd \ - git \ - curl \ - && rm -rf /var/lib/apt/lists/* +# Install Node.js for our adsb.lol proxy +RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /usr/local/share/tar1090/html +# Copy our proxy server (reads from file, avoids circular dependency) +COPY proxy/server.js /opt/proxy/server.js -COPY html/ /usr/local/share/tar1090/html/ -COPY *.conf /usr/local/share/tar1090/ -COPY *.sh /usr/local/share/tar1090/ -RUN chmod +x /usr/local/share/tar1090/*.sh +# Runtime nginx config injection - runs AFTER 07-nginx-configure regenerates config +# Must be numbered > 07 to run after nginx config is generated +# (Build-time sed doesn't persist because tar1090 regenerates nginx config at startup) +COPY docker/08-inject-proxy-config /etc/s6-overlay/startup.d/08-inject-proxy-config +RUN chmod +x /etc/s6-overlay/startup.d/08-inject-proxy-config -COPY docker/lighttpd-tar1090.conf /etc/lighttpd/conf-available/89-tar1090.conf -RUN lighttpd-enable-mod tar1090 - -RUN sed -i 's/server.port\s*=.*/server.port = 8504/' /etc/lighttpd/lighttpd.conf && \ - sed -i 's|server.document-root\s*=.*|server.document-root = "/usr/local/share/tar1090/html/"|' /etc/lighttpd/lighttpd.conf - -COPY <<'EOF' /usr/local/bin/start.sh -#!/bin/bash -set -e - -echo "Starting tar1090 web interface on port 8504..." -lighttpd -D -f /etc/lighttpd/lighttpd.conf -EOF - -RUN chmod +x /usr/local/bin/start.sh - -EXPOSE 8504 - -CMD ["/usr/local/bin/start.sh"] +# Run proxy as a proper s6-overlay service (auto-restart, logging, graceful shutdown) +COPY docker/services.d/proxy /etc/services.d/proxy +RUN chmod +x /etc/services.d/proxy/run diff --git a/Dockerfile.tar1090 b/Dockerfile.tar1090 deleted file mode 100644 index 1d121b09d..000000000 --- a/Dockerfile.tar1090 +++ /dev/null @@ -1,18 +0,0 @@ -FROM ghcr.io/sdr-enthusiasts/docker-tar1090:latest - -USER root - -RUN apt-get update && apt-get install -y \ - curl \ - && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ - && apt-get install -y nodejs \ - && rm -rf /var/lib/apt/lists/* - -COPY proxy/server.js /opt/proxy/server.js - -COPY docker/lighttpd-proxy.conf /etc/lighttpd/conf-enabled/90-proxy.conf - -COPY docker/entrypoint.sh /opt/entrypoint.sh -RUN chmod +x /opt/entrypoint.sh - -ENTRYPOINT ["/opt/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index bd8d2957c..234fa20c3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,52 +1,33 @@ services: - readsb: - image: ghcr.io/sdr-enthusiasts/docker-readsb-protobuf:latest - container_name: readsb - hostname: readsb + tar1090: + build: + context: . + dockerfile: Dockerfile + container_name: tar1090 + hostname: tar1090 restart: unless-stopped network_mode: host environment: - TZ=UTC + # readsb config (built into docker-tar1090) - READSB_DEVICE_TYPE=none + - READSB_NET_ENABLE=true + - READSB_NET_CONNECTOR=${READSB_NET_CONNECTOR:-} - READSB_LAT=${RECEIVER_LAT:-0} - READSB_LON=${RECEIVER_LON:-0} - READSB_ALT=${RECEIVER_ALT:-0} - READSB_RX_LOCATION_ACCURACY=2 - READSB_STATS_RANGE=true - - READSB_NET_ENABLE=true - - DISABLE_WEBAPP=true - - READSB_NET_CONNECTOR=192.168.8.183,30005,beast_in - volumes: - - readsb-autogain:/run/autogain - - readsb-collectd:/run/collectd - tmpfs: - - /var/log:size=32M - - tar1090: - build: - context: . - dockerfile: Dockerfile.tar1090 - container_name: tar1090 - hostname: tar1090 - restart: unless-stopped - network_mode: host - depends_on: - - readsb - environment: - - TZ=UTC - - BEASTHOST=127.0.0.1 - - BEASTPORT=30005 + # tar1090 map config - LAT=${RECEIVER_LAT:-0} - LONG=${RECEIVER_LON:-0} - TAR1090_DEFAULTCENTERLAT=${RECEIVER_LAT:-0} - TAR1090_DEFAULTCENTERLON=${RECEIVER_LON:-0} - - READSB_URL=http://127.0.0.1:80/data/aircraft.json + # adsb.lol proxy config - ADSBLOL_ENABLED=${ADSBLOL_ENABLED:-false} - RECEIVER_LAT=${RECEIVER_LAT:-0} - RECEIVER_LON=${RECEIVER_LON:-0} - ADSBLOL_RADIUS=${ADSBLOL_RADIUS:-40} - PROXY_PORT=3005 - -volumes: - readsb-autogain: - readsb-collectd: + tmpfs: + - /var/log:size=32M diff --git a/docker/08-inject-proxy-config b/docker/08-inject-proxy-config new file mode 100644 index 000000000..a5e06468a --- /dev/null +++ b/docker/08-inject-proxy-config @@ -0,0 +1,22 @@ +#!/command/with-contenv bash +# Inject proxy location block AFTER tar1090's 07-nginx-configure runs +# This must run at startup (not build time) because 07-nginx-configure regenerates the config + +echo "[08-inject-proxy-config] Injecting adsb.lol proxy config into nginx..." + +# Add proxy location for aircraft.json +sed -i '/include \/etc\/nginx\/nginx-tar1090-webroot.conf;/i\ location = /data/aircraft.json { proxy_pass http://127.0.0.1:3005/data/aircraft.json; }' \ + /etc/nginx/sites-enabled/tar1090 + +# Disable binCraft in receiver.json so frontend uses JSON format (required for adsb.lol) +# Uses nginx sub_filter to modify the response on-the-fly +sed -i '/include \/etc\/nginx\/nginx-tar1090-webroot.conf;/i\ location = /data/receiver.json { alias /run/readsb/receiver.json; sub_filter_types application/json; sub_filter '\''\"binCraft\": true'\'' '\''\"binCraft\": false'\''; sub_filter '\''\"zstd\": true'\'' '\''\"zstd\": false'\''; sub_filter_once off; }' \ + /etc/nginx/sites-enabled/tar1090 + +# Verify the injection worked +if grep -q "proxy_pass" /etc/nginx/sites-enabled/tar1090; then + echo "[08-inject-proxy-config] Proxy config injected successfully" +else + echo "[08-inject-proxy-config] ERROR: Failed to inject proxy config" + exit 1 +fi diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index 9785aca1c..000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -e - -echo "Starting aircraft data proxy service..." -node /opt/proxy/server.js & -PROXY_PID=$! - -echo "Waiting for proxy to be ready..." -sleep 2 - -cleanup() { - echo "Shutting down..." - kill $PROXY_PID 2>/dev/null || true - exit 0 -} - -trap cleanup SIGTERM SIGINT - -echo "Starting tar1090..." -exec /init "$@" diff --git a/docker/lighttpd-proxy.conf b/docker/lighttpd-proxy.conf deleted file mode 100644 index 93bd9ce73..000000000 --- a/docker/lighttpd-proxy.conf +++ /dev/null @@ -1,7 +0,0 @@ -server.modules += ( "mod_proxy" ) - -$HTTP["url"] =~ "^/data/aircraft\.json" { - proxy.server = ( "" => ( - ( "host" => "127.0.0.1", "port" => 3005 ) - )) -} diff --git a/docker/lighttpd-tar1090.conf b/docker/lighttpd-tar1090.conf deleted file mode 100644 index 94fe15812..000000000 --- a/docker/lighttpd-tar1090.conf +++ /dev/null @@ -1,20 +0,0 @@ -server.modules += ( "mod_alias" ) -server.modules += ( "mod_setenv" ) - -alias.url += ( - "/data/" => "/run/readsb/" -) - -$HTTP["url"] =~ "^/(libs|flags|images)/" { - setenv.add-response-header += ( "Cache-Control" => "public, max-age=3600" ) -} - -$HTTP["url"] =~ "^/data/" { - setenv.add-response-header += ( "Cache-Control" => "no-cache, no-store, must-revalidate" ) -} - -setenv.add-response-header += ( - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "GET, OPTIONS", - "Access-Control-Allow-Headers" => "Content-Type" -) diff --git a/docker/services.d/proxy/run b/docker/services.d/proxy/run new file mode 100644 index 000000000..798932a62 --- /dev/null +++ b/docker/services.d/proxy/run @@ -0,0 +1,2 @@ +#!/command/with-contenv bash +exec node /opt/proxy/server.js diff --git a/proxy/server.js b/proxy/server.js index 4a30ca42a..f6233fef4 100644 --- a/proxy/server.js +++ b/proxy/server.js @@ -1,12 +1,13 @@ const http = require('http'); const https = require('https'); +const fs = require('fs').promises; -const READSB_URL = process.env.READSB_URL || 'http://127.0.0.1:80/data/aircraft.json'; +const LOCAL_DATA_PATH = process.env.LOCAL_DATA_PATH || '/run/readsb/aircraft.json'; const ADSBLOL_ENABLED = process.env.ADSBLOL_ENABLED === 'true'; const RECEIVER_LAT = parseFloat(process.env.RECEIVER_LAT || '0'); const RECEIVER_LON = parseFloat(process.env.RECEIVER_LON || '0'); const ADSBLOL_RADIUS = parseInt(process.env.ADSBLOL_RADIUS || '40'); -const PORT = parseInt(process.env.PROXY_PORT || '3000'); +const PORT = parseInt(process.env.PROXY_PORT || '3005'); const ADSBLOL_API = `https://api.adsb.lol/v2/lat/${RECEIVER_LAT}/lon/${RECEIVER_LON}/dist/${ADSBLOL_RADIUS}`; @@ -80,31 +81,58 @@ function convertAdsbLolToReadsb(adsbLolData) { }; } -async function getAircraftData() { +async function fetchAdsbLol() { + console.log('Fetching from adsb.lol...'); + const adsbLolData = await fetchUrl(ADSBLOL_API); + const convertedData = convertAdsbLolToReadsb(adsbLolData); + console.log(`adsb.lol: ${convertedData.aircraft?.length || 0} aircraft`); + + // Write to local file so tar1090's backend process can read it try { - console.log('Attempting to fetch from local readsb...'); - const localData = await fetchUrl(READSB_URL); - console.log(`✓ Local readsb: ${localData.aircraft?.length || 0} aircraft`); - return { data: localData, source: 'local' }; + await fs.writeFile(LOCAL_DATA_PATH, JSON.stringify(convertedData)); + } catch (err) { + console.log(`Warning: Could not write to ${LOCAL_DATA_PATH}: ${err.message}`); + } + + return { data: convertedData, source: 'adsb.lol' }; +} + +async function readLocalFile() { + try { + const data = await fs.readFile(LOCAL_DATA_PATH, 'utf8'); + return JSON.parse(data); } catch (error) { - console.log(`✗ Local readsb failed: ${error.message}`); + return null; + } +} - if (!ADSBLOL_ENABLED) { - console.log('✗ adsb.lol fallback disabled'); - throw new Error('Local feed unavailable and fallback disabled'); - } +async function getAircraftData() { + // Try local file first (readsb writes to /run/readsb/aircraft.json) + const localData = await readLocalFile(); + if (localData && localData.aircraft?.length > 0) { + console.log(`Local file: ${localData.aircraft.length} aircraft`); + return { data: localData, source: 'local' }; + } + + // Try adsb.lol fallback if enabled + if (ADSBLOL_ENABLED) { + const reason = localData ? '0 aircraft from local' : 'local file not found'; + console.log(`Falling back to adsb.lol (${reason})...`); try { - console.log('Attempting fallback to adsb.lol...'); - const adsbLolData = await fetchUrl(ADSBLOL_API); - const convertedData = convertAdsbLolToReadsb(adsbLolData); - console.log(`✓ adsb.lol fallback: ${convertedData.aircraft?.length || 0} aircraft`); - return { data: convertedData, source: 'adsb.lol' }; + return await fetchAdsbLol(); } catch (fallbackError) { - console.log(`✗ adsb.lol fallback failed: ${fallbackError.message}`); - throw new Error('Both local and fallback feeds unavailable'); + console.log(`adsb.lol fallback failed: ${fallbackError.message}`); } } + + // Return local data even if empty (if we got a response) + if (localData) { + return { data: localData, source: 'local' }; + } + + // No data sources available + throw new Error('No data sources available'); } const server = http.createServer(async (req, res) => { @@ -118,11 +146,16 @@ const server = http.createServer(async (req, res) => { }); res.end(JSON.stringify(data)); } catch (error) { - res.writeHead(503, { 'Content-Type': 'application/json' }); + console.log(`Data fetch failed: ${error.message}`); + res.writeHead(200, { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'X-Data-Source': 'none' + }); res.end(JSON.stringify({ - error: error.message, - aircraft: [], - now: Date.now() / 1000 + now: Date.now() / 1000, + messages: 0, + aircraft: [] })); } } else if (req.url === '/health') { @@ -136,7 +169,7 @@ const server = http.createServer(async (req, res) => { server.listen(PORT, () => { console.log(`Aircraft data proxy listening on port ${PORT}`); - console.log(`Local feed: ${READSB_URL}`); + console.log(`Local data file: ${LOCAL_DATA_PATH}`); console.log(`adsb.lol fallback: ${ADSBLOL_ENABLED ? 'enabled' : 'disabled'}`); if (ADSBLOL_ENABLED) { console.log(`adsb.lol API: ${ADSBLOL_API}`);