diff --git a/app_config/config.yaml.example b/app_config/config.yaml.example index 08bee2c..6ae6432 100644 --- a/app_config/config.yaml.example +++ b/app_config/config.yaml.example @@ -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. diff --git a/docs/api-contract.md b/docs/api-contract.md index 4fd8498..8bcce39 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -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`. @@ -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. @@ -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. | diff --git a/docs/configuration.md b/docs/configuration.md index c679fe6..fe82380 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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. | @@ -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 diff --git a/docs/mobile/privacy.md b/docs/mobile/privacy.md index c64500b..fad3917 100644 --- a/docs/mobile/privacy.md +++ b/docs/mobile/privacy.md @@ -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 @@ -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 @@ -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 diff --git a/docs/mobile/support.md b/docs/mobile/support.md index 49dc1ca..2c0b30b 100644 --- a/docs/mobile/support.md +++ b/docs/mobile/support.md @@ -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). diff --git a/docs/releases.md b/docs/releases.md index 1afc112..40f31ac 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -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 diff --git a/docs/upgrading.md b/docs/upgrading.md index 3f4c5f9..31207df 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -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