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/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