Raspberry Pi 5 cyberdeck control panel and radio/sensing node.
GRIDRUNNER provides a local web UI for health checks, backup, radio inventory, ADS-B/SDR mode switching, and event review. Hostname, operator user, hotspot SSID, and event paths are deployment-specific and configured with environment variables.
- Web UI:
http://<device-hostname>.local:8088 - ADS-B map:
http://<device-hostname>.local/tar1090/
- External storage model
- ESP32-C6 edge-node architecture
- ESP32-C6 plane tracker
- ESP32-C6 RF tracker firmware
Run this on the Linux device you want to turn into a GRIDRUNNER node:
curl -fsSL https://github.com/ghraw/jfergs/gridrunner/main/scripts/bootstrap-web.sh | bashThe script downloads GRIDRUNNER to ~/gridrunner, creates the web virtual
environment, installs Python dependencies, and starts the web panel. Open:
http://<device-hostname>.local:8088
Then use the Initial Install panel to preview, install, or skip optional components.
During interactive setup, GRIDRUNNER prompts for the fallback hotspot SSID and password. The Wi-Fi network is created only when known Wi-Fi is unavailable. The password must be at least 8 characters. To configure it again:
cd ~/gridrunner
bash scripts/configure-wifi-hotspot.shBefore using the web panel to install packages or service files, run this once from a terminal on the device:
cd ~/gridrunner
sudo scripts/setup-sudoers.shThis also adds the operator user to available journal-reading groups such as
systemd-journal or adm. Log out and back in, or reboot, before using the
logs control if journal permissions were just changed.
Set a web password before exposing the panel on a shared network:
export GRIDRUNNER_WEB_PASSWORD='choose-a-local-password'
curl -fsSL https://github.com/ghraw/jfergs/gridrunner/main/scripts/bootstrap-web.sh | bashThe panel is intended for local device, trusted LAN, or VPN use. Do not expose
port 8088 directly to the internet. Use Tailscale, WireGuard, or another VPN
for remote access, and set GRIDRUNNER_WEB_PASSWORD before using any shared
network.
Common bootstrap overrides:
export GRIDRUNNER_REPO_URL='https://github.com/jfergs/gridrunner.git'
export GRIDRUNNER_INSTALL_DIR="$HOME/gridrunner"
export GRIDRUNNER_WEB_HOST='0.0.0.0'
export GRIDRUNNER_WEB_PORT='8088'
export GRIDRUNNER_OPERATOR_USER="$(id -un)"
export GRIDRUNNER_DEVICE_HOSTNAME="$(hostname -s)"web/
app.py
templates/index.html
deploy/
systemd/gridrunner-web.service
systemd/gridrunner-events.service
systemd/gridrunner-events.timer
scripts/
adsb-health.sh
bootstrap-web.sh
component-health.sh
disk-health.sh
event-health.sh
install-adsb-readsb.sh
install-items.sh
logs.sh
patch-events-script.sh
rotate-logs.sh
run-events.sh
wifi-status.sh
system-health.sh
system-backup.sh
ham-check.sh
power-control.sh
radio-inventory.sh
setup-sudoers.sh
service-health.sh
radio-mode.sh
wifi-fallback.sh
data/
logs/
state/
radio/
sdr/
The web panel is a small FastAPI app. On the Pi, run it from the project directory:
cd /home/<operator-user>/gridrunner/web
source .venv/bin/activate
export GRIDRUNNER_OPERATOR_USER='<operator-user>'
export GRIDRUNNER_OPERATOR_HOME='/home/<operator-user>'
export GRIDRUNNER_DEVICE_HOSTNAME='<device-hostname>'
export GRIDRUNNER_WEB_PASSWORD='choose-a-local-password'
uvicorn app:app --host 0.0.0.0 --port 8088For local development from a checkout:
cd web
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app:app --reload --host 127.0.0.1 --port 8088The app defaults to the local checkout for scripts/ and /home/operator for
runtime operator files. Set GRIDRUNNER_WEB_PASSWORD before exposing the panel
on Wi-Fi, and prefer VPN access for remote administration. Override these for
the target device:
export GRIDRUNNER_HOME=/path/to/gridrunner
export GRIDRUNNER_OPERATOR_USER='<operator-user>'
export GRIDRUNNER_OPERATOR_HOME=/path/to/operator-home
export GRIDRUNNER_DEVICE_HOSTNAME='<device-hostname>'
export GRIDRUNNER_ADSB_MAP_URL='http://<device-hostname>.local/tar1090/'
export GRIDRUNNER_ADSB_ROUTE_LOOKUP_ENABLED=1
export GRIDRUNNER_ADSB_ROUTE_API_URL='https://api.adsbdb.com/v0/callsign/{callsign}'
export GRIDRUNNER_ADSB_ROUTE_LOOKUP_LIMIT=3
export GRIDRUNNER_ADSB_ROUTE_LOOKUP_TIMEOUT=0.8
export GRIDRUNNER_ADSB_ROUTE_CACHE_SECONDS=900
export GRIDRUNNER_EVENTS_LOG=/path/to/operator-events.log
export GRIDRUNNER_WEB_USER='<operator-user>'
export GRIDRUNNER_WEB_PASSWORD='choose-a-local-password'If GRIDRUNNER_ADSB_MAP_URL is unset, the panel builds the ADS-B map link from
the current request host as http://<current-host>/tar1090/. Set the variable
above when tar1090 lives somewhere else.
The ADS-B Flight board always uses the local readsb aircraft JSON as its baseline. When internet access is available, the panel can optionally enrich a small number of visible flights with route information by callsign. Route lookup is enabled by default, bounded by the limit, timeout, and cache settings above, and fails quietly when the device is offline.
If aircraft data is missing or degraded, the ADS-B panel shows the aircraft JSON
path it expected, readsb.service state, lighttpd.service state, and the
device command to run next:
bash scripts/adsb-health.shHealth and Wi-Fi scripts hide hostname and Wi-Fi connection names by default. Set this only on trusted consoles when full identifiers are needed:
export GRIDRUNNER_SHOW_IDENTIFIERS=1The web panel shows recent events and warns when the events log is missing or stale. The stale threshold defaults to 15 minutes and can be adjusted with:
export GRIDRUNNER_EVENTS_STALE_SECONDS=900When Bluetooth and Network Device scanning are both disabled, the dashboard
shows Events as idle instead of stale. That means the timer can be healthy
while no new event lines are expected. Enable scanning or run a one-shot scan
from Quick Actions or the scan drawer to write fresh events.
Use the real log wrapper instead of an alias-only logs command:
cd ~/gridrunner
bash scripts/logs.shIf journal access is denied, rerun setup and restart the session:
cd ~/gridrunner
sudo scripts/setup-sudoers.shInstall periodic event collection with the Events Service install item, or
from the device:
cd ~/gridrunner
bash scripts/install-items.sh --apply events-serviceThis installs:
/etc/systemd/system/gridrunner-events.service
/etc/systemd/system/gridrunner-events.timer
The timer runs shortly after boot and every five minutes. Bluetooth and
network device discovery scans default to off, so the timer skips legacy scan
work until scan controls are enabled from the web panel. The top Quick Actions
deck provides one-tap Wi-Fi, Bluetooth, Network, and ADS-B Map actions. The
scan drawer provides Bluetooth Scan Now and Wi-Fi Scan Now; each button
stays on the page, shows a scanning state while active, and returns to its idle
label when the run finishes. Use the drawer's Enable Scanning /
Disable Scanning control to arm or park continuous scanning. The runner
stores those controls in:
~/gridrunner/state/scan-controls.env
The dashboard also provides scan profiles:
Low Impact: continuous Bluetooth and network device scans off, interval saved as 15 minutes for future continuous use.Field: continuous Bluetooth and network device scans on, interval 5 minutes.
The profile control is a Low Impact / Field slider in the scan drawer.
Recommended defaults for low-contention field use:
- Keep continuous Bluetooth and Network Devices off unless actively surveying.
- Use
Low Impactas the parked/default profile. - Use
Fieldfor short survey windows, then return toLow Impact. - Keep Bluetooth bursts bounded to 8-12 seconds with
GRIDRUNNER_BTMGMT_FIND_SECONDS. - Prefer ARP-only network device discovery; avoid heavier
nmapsweeps unless intentionally troubleshooting.
When scans are enabled, the timer calls scripts/run-events.sh, which
resolves the operator event script at:
/home/<operator-user>/<operator-user>-events.sh
During events-service install, scripts/patch-events-script.sh bounds legacy
btmgmt find calls and gates known Bluetooth/network discovery commands behind
the dashboard scan controls. Override the Bluetooth scanner timeout with:
export GRIDRUNNER_BTMGMT_FIND_SECONDS=12Check it with:
systemctl status gridrunner-events.timer
systemctl status gridrunner-events.service
journalctl -u gridrunner-events.service -n 80 --no-pagerEvent log rotation is handled by:
bash scripts/rotate-logs.shDefaults:
export GRIDRUNNER_EVENTS_LOG_MAX_BYTES=5242880
export GRIDRUNNER_EVENTS_LOG_KEEP=5Install the managed foreground presence scanner with:
cd ~/gridrunner
bash scripts/install-items.sh --apply presence-scriptThat installs the wrapper at:
/home/<operator-user>/<operator-user>-presence.sh
You can also run the maintained copy directly:
cd ~/gridrunner
bash scripts/ghost-presence.shIt reads the same state/scan-controls.env file as the dashboard and skips
arp-scan --localnet unless Network scanning is set to Continuous or the
process is explicitly launched with GRIDRUNNER_SCAN_NETWORK_ENABLED=1.
Avoid unmanaged arp-scan --localnet loops on wlan0; they put the Wi-Fi
interface into promiscuous mode and can trigger Broadcom scan errors while the
device is connected to known Wi-Fi.
Disk usage guardrails are reported by:
bash scripts/disk-health.shDefaults:
export GRIDRUNNER_DISK_WARN_PERCENT=85
export GRIDRUNNER_DISK_CRITICAL_PERCENT=95
export GRIDRUNNER_BACKUP_KEEP=5GRIDRUNNER keeps service-critical files on internal storage. The web dashboard
Storage panel can use an already-mounted writable USB volume for operator data:
backups, event logs, SDR captures, radio artifacts, ADS-B history, and media
libraries. It writes explicit paths to state/storage.env; it does not format
drives, erase data, move state/, or make boot depend on removable media. The
same panel shows horizontal used/free meters for mounted volumes.
The Storage panel also shows an operator message for the active mode:
internal: internal paths are active.external: USB storage is active for operator data.degraded: external storage is configured but missing or not writable, so runtime scripts fall back to internal backup and event-log paths.
CLI equivalents:
bash scripts/storage-control.sh list
bash scripts/storage-control.sh enable /media/<operator>/<label>
bash scripts/storage-control.sh status
bash scripts/storage-control.sh disableIf the selected USB root is missing or not writable, runtime scripts fall back to internal backup and event-log paths. See docs/storage-model.md for the storage design, allowed movable paths, internal-only paths, and rollback behavior.
The dashboard includes an initial install pane with selectable components.
Checked items are installed, unchecked items are skipped. Use Preview to
print the planned commands before using Install Selected.
Skipped and pending components remain visible in the panel and can be selected
for installation later.
The panel also runs scripts/component-health.sh to show whether each component
is currently detected on the device.
ADS-B setup intentionally does not install the Debian readsb package. On this
GRIDRUNNER setup, Debian/Trixie readsb can lack RTL-SDR support and fail with
ERROR: Unknown device type:0. Use the wiedehopf installer path instead:
cd ~/gridrunner
sudo bash scripts/install-adsb-readsb.shThat helper downloads the wiedehopf installer to a temporary file before execution. For a pinned install, set the expected installer hash first:
export GRIDRUNNER_READSB_INSTALL_SHA256='<expected-sha256>'
sudo -E bash scripts/install-adsb-readsb.shThen it marks readsb held with apt-mark hold readsb when available. Check
RTL-SDR support with:
bash scripts/adsb-health.sh
readsb --help | grep rtlsdrPackage and service installation from the web panel requires non-interactive sudo. If an install item fails with a sudo password/terminal error, run:
cd ~/gridrunner
sudo scripts/setup-sudoers.shIf an apt package fails on a config-file prompt, repair the interrupted install from a terminal with:
sudo DEBIAN_FRONTEND=noninteractive apt-get install -f -y \
-o Dpkg::Options::=--force-confdef \
-o Dpkg::Options::=--force-confoldInstall component labels and defaults are defined in install-items.json.
Install state is written to state/install.json by default. Override the state
directory when needed:
export GRIDRUNNER_STATE_DIR=/path/to/stateThe install panel also includes optional display profiles for compact local
screens. Display: Elecrow RR050 and Display: Waveshare 5-inch HDMI configure
common 800x480 HDMI/GPIO-touch panels. Display: Raspberry Pi Touch records the
official DSI display profile, which Raspberry Pi OS normally handles without a
vendor driver script. Operator Display Mode can start the local screen in the
web UI, ADS-B map, or tmux dashboard mode. See docs/display-setup.md.
After pulling an update on the Pi, use this quick path to confirm privileged web actions, Wi-Fi mode controls, and ADS-B are still healthy:
cd ~/gridrunner
git pull --ff-only
sudo scripts/setup-sudoers.sh
sudo systemctl restart gridrunner-web.service
bash scripts/wifi-status.sh
bash scripts/wifi-fallback.sh hotspot
bash scripts/wifi-status.sh
bash scripts/wifi-fallback.sh known
bash scripts/adsb-health.shThen open http://<device-hostname>.local:8088, confirm the ADS-B data age is
current, confirm Events shows either fresh or intentional idle, and test the
Quick Actions, Enable Hotspot, Connect Known Wi-Fi, ADS-B Mode, and
SDR Mode controls from the web UI. For a fuller pass, use
docs/device-validation.md.
The Web Service install item renders deploy/systemd/gridrunner-web.service
with the current operator, install path, and hostname, then installs it as:
/etc/systemd/system/gridrunner-web.service
After installation, the panel should start on boot. The installer enables the
service but does not restart it immediately, so it does not collide with a
bootstrap web process already using port 8088. Check it with:
systemctl status gridrunner-web.service
journalctl -u gridrunner-web.service -n 100 --no-pagerThe Edge Node MQTT install item installs Mosquitto, creates the edge-node
state directories, renders deploy/systemd/gridrunner-edge-node-ingest.service,
and enables the subscriber service:
bash scripts/install-items.sh --apply edge-node-mqtt
systemctl status gridrunner-edge-node-ingest.serviceThe service subscribes to gridrunner/nodes/+/telemetry and writes validated
latest-state JSON files under state/edge-nodes/ via
scripts/edge-node-ingest.sh.
The web panel includes Restart and Shutdown controls in the upper-right power tray. Each action uses a browser confirmation popup before it submits. The runtime user must have non-interactive sudo permission for:
systemctl reboot
systemctl poweroff
scripts/wifi-fallback.sh is the maintained copy of the fallback hotspot logic. The installed runtime copy currently lives at:
/home/<operator-user>/wifi-fallback.sh
The systemd timer is expected to run the installed script periodically:
systemctl status gridrunner-wifi.timer
systemctl status gridrunner-wifi.serviceCheck current Wi-Fi mode, timer state, service state, and hotspot IP with:
cd ~/gridrunner
bash scripts/wifi-status.shThe web dashboard Wi-Fi panel includes Enable Hotspot for manually switching
to the configured fallback hotspot. The same action is available from a
terminal with:
cd ~/gridrunner
bash scripts/wifi-fallback.sh hotspotUse Connect Known Wi-Fi from the same panel to leave hotspot mode and join a
visible known network. The terminal equivalent is:
cd ~/gridrunner
bash scripts/wifi-fallback.sh knownConfigure or repair the hotspot profile from the device:
cd ~/gridrunner
bash scripts/configure-wifi-hotspot.sh
bash scripts/wifi-fallback.shThe script defaults to GRIDRUNNER-HOTSPOT and also recognizes legacy
Gridrunner-hotspot and DEVICE-HOTSPOT NetworkManager profile names as the
fallback hotspot. The persisted fallback configuration is written to:
~/.config/gridrunner/wifi-fallback.env
To inspect the NetworkManager profile:
nmcli connection show 'Gridrunner-hotspot'
nmcli connection up 'Gridrunner-hotspot'Expected behavior:
- Stay on a known Wi-Fi network when connected.
- Start the configured fallback hotspot when no known network is visible.
- While hotspot is active, keep scanning and switch back to known Wi-Fi when available.
When NetworkManager reports full connectivity on a known Wi-Fi network, the fallback check exits without rescanning. Degraded connectivity checks use a minimum local rescan interval to reduce radio churn:
export GRIDRUNNER_WIFI_RESCAN_MIN_SECONDS=60The last rescan timestamp is stored in ~/gridrunner/state/wifi-rescan.last by
default.
The last fallback action is stored in ~/gridrunner/state/wifi-action.env and
shown by scripts/wifi-status.sh and the web dashboard.
Runtime logs, backups, virtual environments, and generated cache files are intentionally ignored by git.