Skip to content
Merged
15 changes: 9 additions & 6 deletions app_config/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,16 @@ websocket:
#observers:
# delete_after: 720h # opt in to 30 days unseen

# BEACON Mobile settings, published unauthenticated at GET /api/v1/info.
# min_app_version: oldest app release allowed to use this server. Older apps
# show an "update required" screen for this server. Strict X.Y.Z (digits only,
# no "v" prefix or -beta suffix); any other value prevents startup.
# Omitted or empty (default) means no requirement. Takes effect on restart.
# Minimum client versions, published unauthenticated at GET /api/v1/info.
# Older clients show an "update required" screen for this server. Each server
# release has a built-in floor; these settings can raise it, never lower it.
# Strict X.Y.Z (digits only, no "v" prefix or -beta suffix); any other value
# prevents startup. Omitted or empty (default) means the built-in floor only.
# Takes effect on restart.
#mobile:
# min_app_version: "0.1.1" # default: "" (no requirement)
# min_app_version: "0.1.1" # BEACON Mobile; built-in floor: none
#web:
# min_web_version: "2.0.3" # Beacon Web; built-in floor: 2.0.3

# CORS configuration.
# Controls which origins, methods and headers are allowed for cross-origin requests.
Expand Down
25 changes: 16 additions & 9 deletions docs/api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,8 @@ Known routes are fully resolved multi-hop paths distilled from packet history.
| Endpoint | Notes |
|---|---|
| `GET /routes` | Plain array, newest `lastSeen` first. Params: `iata`, `hopCount`, `cursor` (last item's `lastSeen`, epoch ms), `cursorId` (last item's `id`), `limit` (default 50). |
| `GET /routes/search?iata=&from=&to=` | Routes in one IATA between two node hash prefixes (hex). All required. |
| `GET /routes/cross?fromIata=&fromHash=&toIata=&toHash=` | Routes that cross IATA boundaries. All required. |
| `GET /routes/search?from=&to=[&iatas=]` | Routes within one IATA between two node hash prefixes. `from`, `to` required (hex, 1–4 bytes; widths may differ). `iatas` optional: comma list, at most 100, omitted = every IATA; the single `iata` is still accepted. At most 500 results, shortest first. `hops` is trimmed to source…destination and `hopCount` = `hops.length`. |
| `GET /routes/cross?fromHash=&toHash=[&iatas=]` | Routes that cross from one IATA into another. `fromHash`, `toHash` required. `iatas` optional: 2–100 codes, omitted = every IATA. The legacy `fromIata`+`toIata` pair is still accepted, but not together with `iatas`. At most 500 results, sorted by `totalHops`, then `crossHop.lastSeen` newest first. |
| `GET /routes/{iata}/{pathKey}/observations` | Retained reports that match a saved route exactly. `pathKey` comes from a route response. Window: `range` (default `24h`, max `720h`) or `since`+`until` (max 30 days); `pageCursor` for the next page; `limit` default 50. |

To page `/routes`, send the last item's `lastSeen` as `cursor` and its `id` as `cursorId`. Routes share a millisecond often (one batch of upserts), and `cursorId` returns the ones that didn't fit on the previous page. `cursor` alone still works but can skip those ties. `cursorId` must be a positive integer and requires `cursor`; otherwise `400`.
Expand All @@ -451,7 +451,9 @@ To page `/routes`, send the last item's `lastSeen` as `cursor` and its `id` as `
}
```

`/routes/cross` returns `[{ sourceSegment, crossHop, targetSegment, totalHops }]`, where the segments are hop arrays as above and `crossHop` is `{ fromNode, toNode, fromIata, toIata, lastSeen }`.
`/routes/cross` returns `[{ sourceSegment, crossHop, targetSegment, totalHops }]`, where the segments are hop arrays as above and `crossHop` is `{ fromNode, toNode, fromIata, toIata, lastSeen }`. `sourceSegment` runs from the source node to the boundary node and `targetSegment` from its neighbor across the boundary to the destination node; `fromNode`/`toNode` are that boundary pair (the last and first hops of the segments). `totalHops` is the two segment lengths combined.

Both searches return `400` for bad input, and also `hash matches too many areas; use a longer hash or fewer iatas` when a short hash resolves in too many IATAs; show that message to the user. A search that hits the 15s timeout returns `503`.

The observations endpoint returns `{ items, hasMore, nextPageCursor, route, windowStart, windowEnd, generatedAt, matchType, matchAvailable, hashSize, pathBytes }`. Each item is `{ id, packetHash, observerId, observerName, heardAt, payloadType, payloadTypeName, rssi, snr }`. It matches on the route's full path bytes, hash size and hop count within its IATA; `limit` is capped at 200. Evidence expires with raw packets, so a route can outlive every observation that produced it.

Expand Down Expand Up @@ -600,22 +602,27 @@ GET /api/v1/info
Public, no key. Counts against the normal rate limit and is sent with `Cache-Control: no-cache`.

```json
{ "minAppVersion": "0.1.1", "serverVersion": "2.0.3" }
{ "minAppVersion": "0.1.1", "minWebVersion": "2.0.3", "serverVersion": "2.0.4" }
```

`minAppVersion` is the server's `mobile.min_app_version`: the oldest BEACON Mobile release
allowed to use this server, as a strict `X.Y.Z` string, or `null` when the operator has not set
one. `serverVersion` is the server's API version (`X.Y.Z`, no `v`), the same value Swagger shows. Servers
older than this endpoint return `404`. See
`minAppVersion` and `minWebVersion` are the oldest BEACON Mobile and Beacon Web releases allowed
to use this server, as strict `X.Y.Z` strings, or `null` when there is no requirement. Each is the
higher of the server's built-in floor and the operator's `mobile.min_app_version` /
`web.min_web_version`. `serverVersion` is the server's API version (`X.Y.Z`, no `v`), the same value
Swagger shows. Servers older than this endpoint return `404`. See
[Mobile-specific concerns](#mobile-specific-concerns) for how the app uses it.

Beacon Web calls `/info` on load and again when the tab regains focus (at most once a minute). When
its own version is lower than `minWebVersion` it shows a full-screen "reload" screen instead of the
app. A `404`, a `null` or malformed value, and network errors never block.

### Admin

All under `/api/v1/admin/`, bearer key required (see [Auth](#auth)).

| Endpoint | Notes |
|---|---|
| `GET /admin/config` | Running CORS settings with defaults applied, `auth.configured`, and `ingest.broker_count` (configured broker workers, not connection status), and `mobile.min_app_version` (read-only; `""` when unset). No credentials, broker addresses, channel material or database settings. |
| `GET /admin/config` | Running CORS settings with defaults applied, `auth.configured`, `ingest.broker_count` (configured broker workers, not connection status), `mobile.min_app_version` and `web.min_web_version` (read-only effective floors; `""` when there is none). No credentials, broker addresses, channel material or database settings. |
| `PUT /admin/config` | Replaces `cors.allowed_origins` until the next restart. JSON body up to 16 KiB. |
| `GET /admin/accounts`, `POST /admin/accounts` | List or create operator account records (name only; not logins). |
| `GET /admin/accounts/{id}`, `DELETE /admin/accounts/{id}` | Fetch or deactivate one. |
Expand Down
3 changes: 2 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ which ones to read.
| `nodes` | When a node is marked stale, when it is deleted, the clock-drift threshold, and the optional foreign repeater flag. | You want `possiblyForeign` on repeaters. |
| `observers` | Optional deletion of observers not seen for a long time. Off by default. | Rarely. |
| `mobile` | `min_app_version`: the oldest BEACON Mobile release allowed to use this server. Older apps show an update screen. Unset by default. | You need users on a newer app release. |
| `web` | `min_web_version`: the oldest Beacon Web release allowed to use this server. Older web builds show a blocking "reload" screen. The server has a built-in floor (2.0.3 today); this can raise it, not lower it. | Your web container lags the server and you want stale browsers to reload. |
| `cors` | Browser cross-origin rules for REST. Default allows any origin, read-only methods. | You are building an admin UI on another origin. |
| `cache` | Redis TTLs per response category. | Rarely. |
| `ingest` | Only store packets from observers in listed countries or continents. | You run a regional instance and want to ignore the rest of the world. |
Expand All @@ -103,7 +104,7 @@ cannot honour. These are the ones people hit:
- 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.
- `mobile.min_app_version` that is not a plain `X.Y.Z` release (`v1.2.3` and `1.2.3-beta` are rejected).
- `mobile.min_app_version` or `web.min_web_version` that is not a plain `X.Y.Z` release (`v1.2.3` and `1.2.3-beta` are rejected).
- An admin key under 16 characters or containing whitespace.
- A negative `ratelimit.requests_per_minute`, `ratelimit.burst` or `websocket.max_connects_per_minute`.
A negative `websocket.max_connections_per_ip` is not caught at startup and rejects every
Expand Down
14 changes: 6 additions & 8 deletions docs/mobile/privacy.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BEACON Mobile Privacy Policy

_Last updated: October 6, 2026_
_Last updated: October 9, 2026_

BEACON Mobile ("the app") is an open-source iOS and Android client for Beacon, a
real-time analyzer for MeshCore radio mesh networks. This policy explains what
Expand All @@ -25,12 +25,12 @@ through Apple or Google. Deleting the app deletes it.

To work, the app connects to:

1. **The Beacon server you choose.** By default this is `dev.meshcore.ca`; you
can add or switch to any other Beacon server, including one you host. The app
1. **The Beacon servers you add.** The app has no built-in server: it only
connects to servers whose address you enter, including ones you host. It
requests packet, node, observer and statistics data from it over HTTPS or
WebSocket. Like any web server, that server sees your IP address and the
requests made. A server you add yourself is run by its own operator, under
that operator's own policies.
requests made. Each server is run by its own operator, under that operator's
own policies.
2. **Map tile providers.** Map imagery is loaded from
[OpenFreeMap](https://openfreemap.org) and elevation data from the public
AWS Terrain Tiles dataset. These providers see your IP address and the map
Expand All @@ -45,9 +45,7 @@ The app displays radio traffic that MeshCore devices broadcast publicly, as
collected by Beacon servers: node names, public keys, advertised positions,
packet routes, signal readings and messages on public channels. This is not
data about you as an app user. If you operate a MeshCore node and want data
about it removed from a Beacon server, contact that server's operator. For the
default server, open an issue at
[github.com/MeshCore-Beacon/beacon-docs/issues](https://github.com/MeshCore-Beacon/beacon-docs/issues).
about it removed from a Beacon server, contact that server's operator.

## Children

Expand Down
6 changes: 3 additions & 3 deletions docs/mobile/support.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ packet analyzer.

## Getting started

The app connects to the public Beacon server at `dev.meshcore.ca` out of the
box, so live packets, the map and analytics appear right away. To use a
different Beacon server, open **Settings → Servers** and add its address.
On first launch, tap **Add server** and enter the address of a Beacon server;
live packets, the map and analytics then appear right away. To add more servers
or switch between them, open **Settings → Servers**.
Running your own server is covered in the
[deployment guide](https://github.com/MeshCore-Beacon/beacon-docs#readme).

Expand Down
10 changes: 6 additions & 4 deletions docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ signed. The flow is the same in both repos; only the version bump differs.
- beacon-web: `version` in `package.json`.

Commit as `chore: bump version to vX.Y.Z`.
3. Fast-forward `main` to `dev`: `git checkout main && git merge --ff-only dev`.
4. Tag and push: `git tag vX.Y.Z && git push origin main --tags`.
3. Open a PR from `dev` into `main` titled `Release vX.Y.Z` and merge it with **Create a merge
commit**. `main` only accepts merge commits, so `dev`'s own commits land on `main` and the next
release PR lists only what is new.
4. Tag the merge commit and push it: `git tag -a vX.Y.Z origin/main -m "vX.Y.Z" && git push origin vX.Y.Z`.
5. The tag builds and publishes the container image in both repos. In beacon-server it also
builds binaries for every supported platform and attaches them to a draft GitHub release.
Open the draft, paste the release notes and publish it.
6. Rebase `dev` on `main` so the histories stay in sync: `git checkout dev && git rebase main`.
Open the draft (or create the release in beacon-web), paste the release notes and publish it.
6. Fast-forward `dev` to `main` so the branches match: `git push origin origin/main:dev`.

## Where to find release notes

Expand Down
4 changes: 4 additions & 0 deletions docs/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ docker compose up -d
Database migrations run when the server starts. Read the release notes first; a release that
needs a config change says so there. Keep server and web on the same `X.Y`.

Since server 2.0.4, a server can require a minimum Beacon Web version (`minWebVersion` at
`GET /api/v1/info`; see `web.min_web_version` in [Configuration](configuration.md)). An older web
build shows a "reload" screen instead of the app, so pull the web image together with the server.

## MeshMapper API authentication rollout

When upgrading to a Beacon build with MeshMapper API authentication, configure the key
Expand Down
Loading