- Python 82.1%
- JavaScript 15.5%
- Shell 2.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The live file gained Partyline directory strings on 2026-09-09, edited outside this repo. Re-copied so the mirror matches what is actually served. The map integration is unaffected: the diff is additions only, and the postMessage block that tells the embedded map which language to label in is still present and the file parses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019MFBSTjHR2KBqY4yRWUxsP |
||
| bot | ||
| init_scripts | ||
| map | ||
| plugin | ||
| site | ||
| .gitignore | ||
| README.md | ||
rns-wardrive-tools
A collection of tools for wardriving LoRa coverage using the Reticulum Network Stack and Sideband.
To feed location data to the collector bot you can use either the Sideband plugin below or the dedicated Android app, rns-wardrive-app, which provides a live map, transmission log, and a bi-directional Probe mode for two-way RF link quality surveys.
Components
plugin/ — Sideband Plugin
A Sideband service plugin that runs on your mobile device and periodically sends your GPS location to the collector bot over RNS. If you'd rather use a standalone app instead of running it inside Sideband, see rns-wardrive-app.
Installation:
Copy wardrive_plugin.py and wardrive_config.json to your Sideband plugins directory (configured in Sideband settings under Plugins path).
Configuration (wardrive_config.json):
{
"announce_interval_secs": 30,
"announce_mode": "auto",
"send_mode": "packet",
"bot_hashes": ["<bot collector destination hash>"]
}
| Option | Values | Description |
|---|---|---|
announce_interval_secs |
integer (min 15) | How often to send location in auto mode |
announce_mode |
auto / manual |
auto: sends on interval; manual: sends when you press Sideband's announce button |
send_mode |
announce / packet |
announce: broadcasts on the network; packet: sends directly and privately to the bot |
bot_hashes |
list of hex strings | Collector destination hashes of bots to send to (packet mode only) |
In packet mode, location data is sent encrypted directly to each listed bot — it is not broadcast to the wider network. RSSI/SNR are still recorded on the receiving end.
In announce mode, location is broadcast as a wardrive.beacon announce visible to all nodes.
bot/ — Collector Bot
A server-side RNS daemon that listens for incoming location data, records RF signal statistics, and responds to LXMF commands.
Requirements: rns, lxmf, rnsd running
Installation:
Copy wardrive_bot.py to /opt/reticulum/bin/ (or any location on your $PATH).
Usage:
python3 wardrive_bot.py
The bot writes to ~/wardrive_bot.db (SQLite) and logs to /etc/reticulum/wardrive_bot_logfile.
LXMF commands (send via Sideband or any LXMF client):
| Command | Response |
|---|---|
ping |
Pong! [14:32:01, RSSI: -74dBm, SNR: 6.5dB] — RF stats included if message arrived over LoRa |
status |
Point count and timestamp of last received beacon |
nodes |
Count of discoverable nodes within the configured radius |
destinations |
Collector/probe destination hashes, plus the relay (aggregator) hash if this bot is the aggregator |
announce |
Reannounces all of the bot's destinations after a short randomized delay |
map/ — Coverage Map Server
A Flask web server that reads the bot's database and serves a live interactive coverage heatmap.
Requirements: flask, rns, plus a basemap tileset (see Self-hosted basemap below)
Installation:
Copy map_server.py to /opt/reticulum/bin/ and wardrive_map.json to /etc/reticulum/.
Configuration (wardrive_map.json):
{
"db_path": "~/wardrive_bot.db",
"discovery_dir": "/etc/reticulum/storage/discovery/interfaces",
"listen_on": "0.0.0.0",
"port": 5000,
"page_title": "Wardrive Coverage Map",
"initial_lat": null,
"initial_lon": null,
"initial_zoom": null
}
Set initial_lat, initial_lon, and initial_zoom to pre-position the map on load; leave all three as null to auto-fit to recorded points.
Usage:
python3 map_server.py
Serves on http://0.0.0.0:5000 by default.
Features:
- Live heatmap of recorded coverage points, coloured by RSSI
- Live data stream panel showing incoming packets in real time (source, coordinates, RSSI/SNR/Q)
- Date range filter with day/week/month/year presets
- Light/dark UI theme with tileset selector (Dark, Voyager, Voyager no labels, Light, OSM)
- RNS infrastructure discovery panel — shows RNodes, Backbone, and I2P interfaces that have GPS coordinates, sourced from the local RNS discovery cache
- Toggleable node labels per interface type
Self-hosted basemap
The map renders with MapLibre GL JS against vector tiles served from this machine, using the dark-matter OpenMapTiles style, alongside positron, osm-bright and maptiler-basic. Nothing on the page is fetched from a third party: tiles, glyphs, sprites, JavaScript, and reverse geocoding are all local.
All five basemaps share the single vector tileset — a GL style is only a JSON recipe, so each extra basemap costs a style file and a sprite sheet, not another copy of the map data. The "no labels" entry is generated by dropping the symbol layers from Voyager.
Assets live outside the repo because the tileset is far too large to version:
| Path | Contents |
|---|---|
<tileset_dir>/static/tiles/basemap.pmtiles |
Vector basemap (planet z0-12 + Central FD z13-14) |
<tileset_dir>/static/fonts/ |
Composed glyph stacks |
<tileset_dir>/static/sprite/ |
Style sprite sheet |
<tileset_dir>/static/styles/ |
Localized GL style JSON |
<tileset_dir>/static/js/ |
maplibre-gl + pmtiles |
<tileset_dir>/static/js/ (cont.) |
RTL text plugin for Arabic/Hebrew shaping |
<tileset_dir>/geo/cities.tsv.gz |
Offline reverse-geocoding dataset |
<tileset_dir>/build/ |
Build intermediates — deliberately outside the served tree |
tileset_dir defaults to /mnt/sdb1/tilesets and is set in wardrive_map.json.
Only static/ and geo/ are read at runtime; keep font archives and unlocalized styles
in build/ so they are not exposed over HTTP.
Every style renders {name:nonlatin} beside {name:latin}, so a worldwide basemap shows
place names in local scripts. The glyph stacks cover Cyrillic, Arabic, Hebrew, CJK, Hangul
and Devanagari; Arabic and Hebrew additionally need the RTL text plugin, which is loaded
lazily from local storage.
Build tooling lives in map/tiles/ and map/geo/:
map/tiles/build.sh planet # planet z0-12 (long; needs the planet .pbf)
map/tiles/build.sh cfd # regional z13-14
map/tiles/build.sh merge # stitch + convert to .pmtiles
map/tiles/compose_glyphs.py fonts-src fonts # merge fontstacks for static serving
map/tiles/localize_style.py style.json out.json # strip third-party URLs from a GL style
map/tiles/make_nolabels.py in.json out.json # derive a label-free style variant
map/tiles/check_styles.py # verify styles against tiles/fonts/sprites
map/geo/build_geodb.py geo/cities.tsv.gz # refresh the geocoding dataset
check_styles.py is worth running after adding a basemap: a style that references a
source-layer the tileset lacks, or a fontstack with no glyph directory, renders as a map
quietly missing those features rather than raising an error.
Planetiler writes its scratch to /var/tmp/planetiler-tmp by default. Keep that on
fast storage — if the tileset volume is a spinning disk, building there is many times
slower than building on an SSD and copying the result across.
In production nginx serves /tiles/, /fonts/, /sprite/, and /static/ directly
off disk (see map/nginx/wardrive-map-assets.conf); the Flask routes for those paths
are a fallback so the map still works when hitting the port directly. Serving the
.pmtiles archive requires HTTP range request support, which nginx provides for static
files natively — there is no tile server process.
init_scripts/ — Service Files
Systemd and OpenRC service files for both the bot and the map server.
OpenRC:
cp init_scripts/openrc/wardrive-bot /etc/init.d/
cp init_scripts/openrc/wardrive-map /etc/init.d/
chmod +x /etc/init.d/wardrive-bot /etc/init.d/wardrive-map
rc-update add wardrive-bot default
rc-update add wardrive-map default
Systemd:
cp init_scripts/systemd/wardrive-bot.service /etc/systemd/system/
cp init_scripts/systemd/wardrive-map.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now wardrive-bot wardrive-map
Data Flow
[Sideband + plugin]
|
| wardrive.beacon announce (announce mode, visible to all)
| wardrive.collector packet (packet mode, encrypted to bot only)
|
[wardrive_bot.py] ──── wardrive_bot.db ────[map_server.py]──── browser
|
LXMF replies (ping, status)