From a5560bb2104d2b1f0b293976f6384ed7fe6233a4 Mon Sep 17 00:00:00 2001 From: Sol455 Date: Mon, 5 Jan 2026 17:43:28 +0000 Subject: [PATCH 1/6] Switch to from-scratch lighttpd Dockerfile Replace SDR Enthusiasts nginx-based image with lightweight from-scratch build using lighttpd + Node.js proxy. Removes redundant internal readsb process - now relies on external readsb container or adsb.lol fallback. --- .github/workflows/docker_build.yml | 2 +- Dockerfile | 36 +++++++++++++++++++++++++----- docker-compose.yml | 2 +- 3 files changed, 33 insertions(+), 7 deletions(-) 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..1d8e12a09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,35 +1,61 @@ FROM debian:bullseye-slim RUN apt-get update && apt-get install -y \ - nginx \ lighttpd \ git \ curl \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js 20.x +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /usr/local/share/tar1090/html COPY html/ /usr/local/share/tar1090/html/ +RUN mkdir -p /run/readsb +COPY data/ /run/readsb/ +RUN chmod -R a+r /run/readsb/ COPY *.conf /usr/local/share/tar1090/ COPY *.sh /usr/local/share/tar1090/ RUN chmod +x /usr/local/share/tar1090/*.sh +# Copy lighttpd configs COPY docker/lighttpd-tar1090.conf /etc/lighttpd/conf-available/89-tar1090.conf -RUN lighttpd-enable-mod tar1090 +COPY docker/lighttpd-proxy.conf /etc/lighttpd/conf-available/90-proxy.conf +RUN lighttpd-enable-mod tar1090 && lighttpd-enable-mod proxy + +# Copy proxy server +COPY proxy/server.js /opt/proxy/server.js -RUN sed -i 's/server.port\s*=.*/server.port = 8504/' /etc/lighttpd/lighttpd.conf && \ +# Configure lighttpd: port 80, document root +RUN sed -i 's/server.port\s*=.*/server.port = 80/' /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..." +echo "Starting aircraft data proxy on port ${PROXY_PORT:-3005}..." +node /opt/proxy/server.js & +PROXY_PID=$! + +cleanup() { + echo "Shutting down..." + kill $PROXY_PID 2>/dev/null || true + exit 0 +} +trap cleanup SIGTERM SIGINT + +sleep 1 +echo "Starting tar1090 web interface on port 80..." lighttpd -D -f /etc/lighttpd/lighttpd.conf EOF RUN chmod +x /usr/local/bin/start.sh -EXPOSE 8504 +EXPOSE 80 CMD ["/usr/local/bin/start.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index bd8d2957c..a1667357e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: tar1090: build: context: . - dockerfile: Dockerfile.tar1090 + dockerfile: Dockerfile container_name: tar1090 hostname: tar1090 restart: unless-stopped From 79c08899e06b5f94921e160cfa7ddced33e4890e Mon Sep 17 00:00:00 2001 From: Sol455 Date: Mon, 5 Jan 2026 17:44:29 +0000 Subject: [PATCH 2/6] Add receiver.json config for tar1090 Required for tar1090 to use JSON format instead of binCraft binary. Served at /data/receiver.json via lighttpd alias. --- data/receiver.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 data/receiver.json diff --git a/data/receiver.json b/data/receiver.json new file mode 100644 index 000000000..8a8a3f889 --- /dev/null +++ b/data/receiver.json @@ -0,0 +1,7 @@ +{ + "version": "tar1090-node", + "refresh": 1000, + "history": 120, + "binCraft": false, + "zstd": false +} From 8cac272b5fa754119aa01fac20c532a0a35339f3 Mon Sep 17 00:00:00 2001 From: Sol455 Date: Mon, 5 Jan 2026 17:45:45 +0000 Subject: [PATCH 3/6] Fix proxy port default and update env example Change proxy default port from 3000 to 3005 to match docker-compose. Update .env.example with SF coordinates. --- .env.example | 7 +++++-- proxy/server.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 2c9108987..dcf2c826d 100644 --- a/.env.example +++ b/.env.example @@ -2,10 +2,10 @@ # Set these to your actual receiver location # Latitude in decimal degrees -RECEIVER_LAT=-34.9192 +RECEIVER_LAT=37.7644 # Longitude in decimal degrees (note: LON not LONG) -RECEIVER_LON=138.6027 +RECEIVER_LON=-122.3954 # Altitude in meters RECEIVER_ALT=110 @@ -18,3 +18,6 @@ ADSBLOL_ENABLED=true # Radius in nautical miles for adsb.lol queries # Aircraft within this radius of your receiver location will be fetched ADSBLOL_RADIUS=40 + +# readsb external feed +READSB_NET_CONNECTOR=192.168.8.183,30005,beast_in diff --git a/proxy/server.js b/proxy/server.js index 4a30ca42a..2f964c0f4 100644 --- a/proxy/server.js +++ b/proxy/server.js @@ -6,7 +6,7 @@ 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}`; From 529639d9d561047e9eccfc4d17acc0431d165b03 Mon Sep 17 00:00:00 2001 From: Sol455 Date: Mon, 5 Jan 2026 17:58:53 +0000 Subject: [PATCH 4/6] Fix fallback to trigger on 0 aircraft, not just errors Previously fallback only triggered when local readsb fetch failed. Now also falls back to adsb.lol when local returns 0 aircraft. --- proxy/server.js | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/proxy/server.js b/proxy/server.js index 2f964c0f4..74ee51ac2 100644 --- a/proxy/server.js +++ b/proxy/server.js @@ -80,31 +80,49 @@ function convertAdsbLolToReadsb(adsbLolData) { }; } +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`); + return { data: convertedData, source: 'adsb.lol' }; +} + async function getAircraftData() { + let localData = null; + + // Try local readsb first 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' }; + localData = await fetchUrl(READSB_URL); + console.log(`Local readsb: ${localData.aircraft?.length || 0} aircraft`); } catch (error) { - console.log(`✗ Local readsb failed: ${error.message}`); + console.log(`Local readsb failed: ${error.message}`); + } - if (!ADSBLOL_ENABLED) { - console.log('✗ adsb.lol fallback disabled'); - throw new Error('Local feed unavailable and fallback disabled'); - } + // Return local data if we have aircraft + if (localData && localData.aircraft?.length > 0) { + return { data: localData, source: 'local' }; + } + // Try adsb.lol fallback if enabled + if (ADSBLOL_ENABLED) { + const reason = localData ? '0 aircraft from local' : 'local fetch failed'; + 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' }; + } + + // Both failed + throw new Error('Both local and fallback feeds unavailable'); } const server = http.createServer(async (req, res) => { From 985d4108350788be0654c6298c1647a274d36c20 Mon Sep 17 00:00:00 2001 From: Sol455 Date: Mon, 5 Jan 2026 18:00:14 +0000 Subject: [PATCH 5/6] Delete unused Dockerfile.tar1090 --- Dockerfile.tar1090 | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Dockerfile.tar1090 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"] From 74a3e3fae621a55f95b8a52fb9028eecf3331a6c Mon Sep 17 00:00:00 2001 From: Sol455 Date: Fri, 9 Jan 2026 14:51:23 +0000 Subject: [PATCH 6/6] WIP: from-scratch build with lighttpd (to be replaced with docker-tar1090 extension) --- Dockerfile | 19 ++++++++++++++----- docker-compose.yml | 6 ++++-- docker/entrypoint.sh | 20 -------------------- docker/lighttpd-proxy.conf | 2 ++ proxy/server.js | 30 ++++++++++++++++-------------- 5 files changed, 36 insertions(+), 41 deletions(-) delete mode 100644 docker/entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 1d8e12a09..085b53868 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,20 +38,29 @@ COPY <<'EOF' /usr/local/bin/start.sh #!/bin/bash set -e -echo "Starting aircraft data proxy on port ${PROXY_PORT:-3005}..." -node /opt/proxy/server.js & -PROXY_PID=$! - cleanup() { echo "Shutting down..." kill $PROXY_PID 2>/dev/null || true + kill $LIGHTTPD_PID 2>/dev/null || true exit 0 } trap cleanup SIGTERM SIGINT +echo "Starting aircraft data proxy on port ${PROXY_PORT:-3005}..." +node /opt/proxy/server.js & +PROXY_PID=$! + sleep 1 echo "Starting tar1090 web interface on port 80..." -lighttpd -D -f /etc/lighttpd/lighttpd.conf +lighttpd -D -f /etc/lighttpd/lighttpd.conf & +LIGHTTPD_PID=$! + +# Wait for either process to exit +wait -n +exit_code=$? +echo "Process exited with code $exit_code, shutting down..." +cleanup +exit $exit_code EOF RUN chmod +x /usr/local/bin/start.sh diff --git a/docker-compose.yml b/docker-compose.yml index a1667357e..b7050846a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,11 +14,11 @@ services: - 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 + - readsb-run:/run/readsb tmpfs: - /var/log:size=32M @@ -40,13 +40,15 @@ services: - 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 - 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-run:/run/readsb:ro volumes: readsb-autogain: readsb-collectd: + readsb-run: 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 index 93bd9ce73..100d7b444 100644 --- a/docker/lighttpd-proxy.conf +++ b/docker/lighttpd-proxy.conf @@ -1,3 +1,5 @@ +# Note: Port 3005 is hardcoded here and in proxy/server.js (PROXY_PORT default). +# Do not change PROXY_PORT env var - it will break the lighttpd proxy config. server.modules += ( "mod_proxy" ) $HTTP["url"] =~ "^/data/aircraft\.json" { diff --git a/proxy/server.js b/proxy/server.js index 74ee51ac2..efe1d8daa 100644 --- a/proxy/server.js +++ b/proxy/server.js @@ -1,7 +1,8 @@ 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'); @@ -88,26 +89,27 @@ async function fetchAdsbLol() { return { data: convertedData, source: 'adsb.lol' }; } -async function getAircraftData() { - let localData = null; - - // Try local readsb first +async function readLocalFile() { try { - console.log('Attempting to fetch from local readsb...'); - localData = await fetchUrl(READSB_URL); - console.log(`Local readsb: ${localData.aircraft?.length || 0} aircraft`); + const data = await fs.readFile(LOCAL_DATA_PATH, 'utf8'); + return JSON.parse(data); } catch (error) { - console.log(`Local readsb failed: ${error.message}`); + return null; } +} + +async function getAircraftData() { + // Try local file first (readsb writes to /run/readsb/aircraft.json) + const localData = await readLocalFile(); - // Return local data if we have aircraft 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 fetch failed'; + const reason = localData ? '0 aircraft from local' : 'local file not found'; console.log(`Falling back to adsb.lol (${reason})...`); try { return await fetchAdsbLol(); @@ -121,8 +123,8 @@ async function getAircraftData() { return { data: localData, source: 'local' }; } - // Both failed - throw new Error('Both local and fallback feeds unavailable'); + // No data sources available + throw new Error('No data sources available'); } const server = http.createServer(async (req, res) => { @@ -154,7 +156,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}`);