From 7c1dff6fed075f8160fbfedaa80ca4e3bd747747 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Sun, 11 Oct 2026 12:41:09 -0400 Subject: [PATCH] docs: document MeshMapper repeater import and node detail meshmapper field (#23) --- app_config/config.yaml.example | 14 ++++++++ docker-deployment-type1/data/app/config.yaml | 14 ++++++++ docs/api-contract.md | 38 +++++++++++++++++++- docs/configuration.md | 12 +++---- docs/troubleshooting.md | 4 +-- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/app_config/config.yaml.example b/app_config/config.yaml.example index 6ae6432..c7b5a8c 100644 --- a/app_config/config.yaml.example +++ b/app_config/config.yaml.example @@ -111,6 +111,20 @@ ratelimit: # channels: # enabled: false # refresh_interval: 24h # 24h-168h (one get_channels.php per region per 23.5h) +# +# Repeater site details (get_repeaters.php). Every known IATA that MeshMapper +# lists is fetched from its site, the same way as channels. Each repeater's +# admin-entered site details (power, hardware, antenna, heights, power source, +# notes), CARpeater flag, admin names, confirmed scopes and default scope are +# shown on node detail as `meshmapper`, matched by full public key. Positions, +# status and radio data are not imported. A failed refresh keeps the last good +# list and retries after 1h (or Retry-After); an empty list clears the region. +# Each request counts against the key's shared budget (1,000/day, 30/min), so +# 6h costs 4 requests per IATA per day. Turning it off deletes the import on +# restart. Logged under component=meshmapper.repeaters. +# repeaters: +# enabled: false +# refresh_interval: 6h # 1h-24h iatas: YVR: diff --git a/docker-deployment-type1/data/app/config.yaml b/docker-deployment-type1/data/app/config.yaml index 59f3592..4e56d68 100644 --- a/docker-deployment-type1/data/app/config.yaml +++ b/docker-deployment-type1/data/app/config.yaml @@ -112,6 +112,20 @@ ratelimit: # channels: # enabled: false # refresh_interval: 24h # 24h-168h (one get_channels.php per region per 23.5h) +# +# Repeater site details (get_repeaters.php). Every known IATA that MeshMapper +# lists is fetched from its site, the same way as channels. Each repeater's +# admin-entered site details (power, hardware, antenna, heights, power source, +# notes), CARpeater flag, admin names, confirmed scopes and default scope are +# shown on node detail as `meshmapper`, matched by full public key. Positions, +# status and radio data are not imported. A failed refresh keeps the last good +# list and retries after 1h (or Retry-After); an empty list clears the region. +# Each request counts against the key's shared budget (1,000/day, 30/min), so +# 6h costs 4 requests per IATA per day. Turning it off deletes the import on +# restart. Logged under component=meshmapper.repeaters. +# repeaters: +# enabled: false +# refresh_interval: 6h # 1h-24h iatas: YVR: diff --git a/docs/api-contract.md b/docs/api-contract.md index 8bcce39..de782ba 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -243,12 +243,48 @@ Adverts with a bad signature are not decoded and have no `parsedPayload`. | Endpoint | Notes | |---|---| | `GET /nodes` | Page of node summaries. Cursor is `lastSeen` epoch ms. Default limit 50. | -| `GET /nodes/{nodeId}` | Node detail: capability flags, `minFirmwareVersion`, `iatas` (`[{iata, lastHeard}]`), `neighbors`, clock drift for repeaters and room servers. | +| `GET /nodes/{nodeId}` | Node detail: capability flags, `minFirmwareVersion`, `iatas` (`[{iata, lastHeard}]`), `neighbors`, clock drift for repeaters and room servers, and `meshmapper` when imported (see [MeshMapper repeater details](#meshmapper-repeater-details)). | | `GET /nodes/{nodeId}/observations` | Page of observations of packets the node originated. Cursor is the observation ID. | | `GET /nodes/{nodeId}/neighbors` | Plain array of neighbours: `{ id, name, publicKey, nodeType, nodeTypeName, lat, lng, iata, observationCount, firstSeen, lastSeen, snr }`. | `/nodes` params: `type` (1 companion, 2 repeater, 3 room server, 4 sensor) or `typeName`, the location filters, `name` (partial, case-insensitive), `scope`, `pubkey` (exact hex), `pubkeyPrefix`, `supportsMultibytePaths`, `supportsMultibyteTraces`, `neighbors` (adds `neighborIds`), `cursor`, `limit`. Summaries include `stale` (not seen within `nodes.stale_threshold`) and, when `nodes.mark_foreign` is on, `possiblyForeign`. +#### MeshMapper repeater details + +With `meshmapper.repeaters.enabled`, node detail carries `meshmapper` when MeshMapper's repeater +list has the node's full public key. It is omitted otherwise. Times are epoch ms; `fetchedAt` is +when Beacon last fetched the list. Text is admin-entered on MeshMapper, so escape it. + +```json +"meshmapper": { + "iata": "YOW", + "fetchedAt": 1791234600000, + "site": { + "power": "1W", "hardware": "RAK4631", "antenna": "5.8 dBi fibreglass", + "heightM": 12, "heightAslM": 341.6, "powerSource": "solar", + "notes": "On the fire tower, east face" + }, + "carpeater": false, + "admins": ["MrAlders0n"], + "scopes": [ + { "scope": "yow", "observedAt": 1791200000000, "observedPackets": 37, "proof": "multibyte", "reportedAt": 1791100000000 } + ], + "scopesCheckedAt": 1791100000000, + "defaultScope": { "name": "yow", "state": "named", "updatedAt": 1791230000000, "source": "advert" } +} +``` + +- `site` is always an object; each field is omitted when unset. `powerSource` is `mains`, + `solar`, `battery` or `poe`. `heightAslM` can lag a new `heightM` by a few minutes. +- `carpeater` marks a repeater riding in a car: its position moves. +- `scopes` lists scopes MeshMapper confirmed it carrying. `proof` is `multibyte` or `floor`; + `observedAt`/`observedPackets` (seen forwarding), `reportedAt` (an observer asked it) and + `uploadedAt` (a wardriver's app asked it) are omitted for sources that never confirmed it. + `scopesCheckedAt` present with empty `scopes` means it answered with none. +- `defaultScope.state` is `named`, `unknown` (it sets one MeshMapper can't name yet) or + `none`; `name` is only sent when `named`; `source` is `advert` or `reported`. This is + MeshMapper's view; the top-level `defaultScope` is what Beacon matched from adverts. + #### Foreign repeater indication With `nodes.mark_foreign: true`, repeater nodes carry `possiblyForeign`. It is `true` when the diff --git a/docs/configuration.md b/docs/configuration.md index fe82380..af797a6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -69,7 +69,7 @@ which ones to read. | `log` | Log level and format. | You want JSON logs for a collector. | | `server` | `trusted_proxies`: the proxy addresses allowed to tell Beacon the real client IP. | Always, if Beacon sits behind a proxy. See [Reverse proxy](reverse-proxy.md). | | `ratelimit` | Per-client REST limits for `/api/v1/*`. On by default at 300 requests a minute. | You get 429s you did not expect. | -| `meshmapper` | Import transport scopes, IATA borders, region groups and public channels from MeshMapper instead of listing them by hand. Requires a [MeshMapper API key](#meshmapper-api-key). | You are in a region MeshMapper covers. | +| `meshmapper` | Import transport scopes, IATA borders, region groups and public channels from MeshMapper instead of listing them by hand, and add repeater site details to node detail. Requires a [MeshMapper API key](#meshmapper-api-key). | You are in a region MeshMapper covers. | | `iatas` | Display names, coordinates and optional border files for airport codes. IATAs are created automatically when traffic arrives; this block only decorates them. A `borderFile` path is relative to `config.yaml`; in Docker the compose file mounts only `config.yaml`, so add a mount for the folder too (`- ./data/app/borders:/app/borders:ro` under the `app` service). | You want names on the map or a border drawn. | | `regions` | Groups of IATAs with a name, map centre and zoom. | Always, unless MeshMapper imports them. | | `channel_keys` | Hashtag channels and explicit keys for decrypting group messages. | Always. Without keys, channel messages are stored as hashes only. | @@ -99,8 +99,9 @@ cannot honour. These are the ones people hit: - `server.trusted_proxies` entries that are not CIDRs. One host is `10.0.0.5/32`, not `10.0.0.5`. - `packets.retention` or `analytics.rollup_retention` under `24h`, or any negative duration (`observers.delete_after` is the exception: zero or negative turns it off). -- MeshMapper refresh intervals outside their range: `scopes.refresh_interval` 1h to 24h, - `zones.refresh_interval` and `channels.refresh_interval` 24h to 168h. +- MeshMapper refresh intervals outside their range: `scopes.refresh_interval` and + `repeaters.refresh_interval` 1h to 24h, `zones.refresh_interval` and + `channels.refresh_interval` 24h to 168h. - A `borderFile` that is missing or not a valid GeoJSON Polygon or MultiPolygon Feature, or `nodes.mark_foreign: true` with no border source at all. - `log.level` or `log.format` set to anything other than the listed values. @@ -135,9 +136,8 @@ If you use the YAML setting instead, restarting Beacon is enough. Prefer the env setting when using [backups](backup-export.md): exports refuse a nonempty `meshmapper.api_key` in the saved YAML so the key cannot enter a download. -Beacon sends `X-API-Key` to `get_zones.php`, `get_geojson.php`, `get_scopes.php`, and -`get_channels.php`. Its shared client supports the same header for `get_repeaters.php`, -although Beacon does not currently fetch repeaters. Requests use trusted HTTPS MeshMapper +Beacon sends `X-API-Key` to `get_zones.php`, `get_geojson.php`, `get_scopes.php`, +`get_channels.php`, and `get_repeaters.php`. Requests use trusted HTTPS MeshMapper endpoints and do not follow redirects. `get_zones.php` still needs its `country` parameter; a key covering multiple regions does not change the request shape or remove rate limits. IP exemptions are not authentication. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f4840a5..43b773b 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -60,7 +60,7 @@ Beacon refuses to run with a setting it cannot honour. The message names the key | `scope "x" region "y" is not a configured region` | Use a slug that appears under `regions:`. | | `netip.ParsePrefix("10.0.0.5"): no '/'` | Write `10.0.0.5/32`, not `10.0.0.5`. An empty entry fails with `server.trusted_proxies[0] must be a valid CIDR` instead. | | `packets.retention must be at least 24h` | Raise it. Same for `analytics.rollup_retention`. | -| `meshmapper.scopes.refresh_interval must be between 1h and 24h` | Set it inside that range. Zones and channels intervals are 24h to 168h. | +| `meshmapper.scopes.refresh_interval must be between 1h and 24h` | Set it inside that range. Repeaters is also 1h to 24h; zones and channels intervals are 24h to 168h. | | `nodes.mark_foreign requires an iatas.*.borderFile or meshmapper.zones` | Add a border source or turn `mark_foreign` off. | | `not a well-formed GeoJSON Feature`, `ring 0 is not closed`, `geometry must be Polygon or MultiPolygon` | The border file is invalid. It must be a single GeoJSON Feature with a closed Polygon or MultiPolygon in longitude, latitude order. | | `log level must be debug, info, warn or error` | Check `log.level` and `LOG_LEVEL`. `log format must be text or json` is the same for format. | @@ -96,7 +96,7 @@ the stored messages for the new key and logs ## MeshMapper imports are unconfigured or stale Read `docker compose logs app` for `component=meshmapper`, `meshmapper.zones`, -`meshmapper.scopes`, or `meshmapper.channels`. Refresh records include `last_error`, +`meshmapper.scopes`, `meshmapper.channels`, or `meshmapper.repeaters`. Refresh records include `last_error`, `next_attempt`, and, for regional imports, `checked_at`. | Message | Fix |