From a17123af9f8e839b9ae3339ee78d2409487fb187 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:06:10 -0400 Subject: [PATCH 1/8] chore: add .github/CODEOWNERS for main branch policy Signed-off-by: MrAlders0n --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 181841b..d369ebd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -# Default owners for everything -* @446564 +# Every change to main needs a review from the repo owner +* @MrAlders0n From 5a60f1e00b3e7c13c416382d4a53f4ea84b7b0f4 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:06:12 -0400 Subject: [PATCH 2/8] chore: add .github/workflows/main-source-guard.yml for main branch policy Signed-off-by: MrAlders0n --- .github/workflows/main-source-guard.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/main-source-guard.yml diff --git a/.github/workflows/main-source-guard.yml b/.github/workflows/main-source-guard.yml new file mode 100644 index 0000000..911186f --- /dev/null +++ b/.github/workflows/main-source-guard.yml @@ -0,0 +1,24 @@ +name: Main PR source guard + +# Runs from main's copy of this file, so a PR can't edit the check away. +on: + pull_request_target: + branches: [main] + types: [opened, reopened, synchronize, edited] + +permissions: {} + +jobs: + main-source-is-dev: + name: main-source-is-dev + runs-on: ubuntu-latest + steps: + - name: Require PR from this repo's dev branch + env: + HEAD_REF: ${{ github.event.pull_request.head.ref }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + if [ "$HEAD_REF" != "dev" ] || [ "$HEAD_REPO" != "$GITHUB_REPOSITORY" ]; then + echo "::error::PRs into main must come from $GITHUB_REPOSITORY:dev (got $HEAD_REPO:$HEAD_REF)" + exit 1 + fi From ccf67b1eea1685319a4fa2bf7703aee411208fa1 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Sun, 4 Oct 2026 23:22:36 -0400 Subject: [PATCH 3/8] docs(meshmapper): document API key setup (#14) --- README.md | 5 ++ app_config/.env.example | 7 +++ app_config/config.yaml.example | 10 +++- docker-deployment-type1/data/app/config.yaml | 10 +++- docker-deployment-type2/README.md | 5 ++ .../server/data/app/config.yaml | 10 +++- docs/backup-export.md | 4 ++ docs/configuration.md | 55 ++++++++++++++++++- docs/security.md | 13 ++++- docs/troubleshooting.md | 31 +++++++++++ docs/upgrading.md | 25 ++++++++- 11 files changed, 168 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7404752..f7d9a4b 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,11 @@ Everything else in the file is optional and explained in [Configuration](docs/configuration.md): the admin API key, log settings, Redis, and the web app's map view, tabs, themes, name and banner. +If you enable MeshMapper imports, also set `MESHMAPPER_API_KEY`. Your local MeshMapper +regional or grouped-region admin can generate a regional key or a key covering multiple +regions. See [MeshMapper API key](docs/configuration.md#meshmapper-api-key) for setup and +the server rollout requirements. Keep this key out of `VITE_*` settings. + ### 3. Describe your network in `config.yaml` ```bash diff --git a/app_config/.env.example b/app_config/.env.example index 41353e2..d85bd62 100644 --- a/app_config/.env.example +++ b/app_config/.env.example @@ -15,6 +15,13 @@ LISTEN_ADDR=:8080 # At least 16 characters, no whitespace; e.g. `openssl rand -hex 32`. # BEACON_API_KEY= +# MeshMapper regional or grouped-region API key for enabled imports. +# Your local MeshMapper regional or grouped-region admin can generate the key; +# grouped-region keys cover multiple regions. Never use the mobile App key. +# Overrides meshmapper.api_key, even when empty. Keep this out of VITE_* settings. +# Use the environment setting with backups; saved YAML keys prevent exports. +# MESHMAPPER_API_KEY= + # Override log.level / log.format from config.yaml (defaults: info / text). # LOG_LEVEL=info # LOG_FORMAT=json diff --git a/app_config/config.yaml.example b/app_config/config.yaml.example index 6491285..c30fe48 100644 --- a/app_config/config.yaml.example +++ b/app_config/config.yaml.example @@ -45,7 +45,14 @@ ratelimit: # A 429 returns error.code=rate_limited and Retry-After (1 or 60 seconds). burst: 300 -# MeshMapper integrations (optional, no API key). Import transport scopes and +# MeshMapper integrations (optional, require a regional or grouped-region API key). +# Your local MeshMapper regional or grouped-region admin can generate the key. +# A grouped-region key covers multiple regions, including imported group members. +# MESHMAPPER_API_KEY overrides meshmapper.api_key, even when empty. Use the +# environment for secrets, especially with backups; never use the mobile App key. +# Missing keys leave imports unconfigured; cached data and the rest of Beacon remain. +# 401/403 failures retain cached data and report authentication/permission errors. +# Import transport scopes and # IATA border outlines from MeshMapper instead of maintaining scopes: and # iatas.*.borderFile by hand. # @@ -63,6 +70,7 @@ ratelimit: # Region-filtered scope lists and scope stats show imported names only under the # IATAs whose catalogue lists them; IATAs without a catalogue show manual scopes only. #meshmapper: +# api_key: "" # scopes: # enabled: false # refresh_interval: 1h # 1h-24h (MeshMapper allows one get_scopes.php per region per 55m); one source per 15s tick diff --git a/docker-deployment-type1/data/app/config.yaml b/docker-deployment-type1/data/app/config.yaml index 6dfc478..59f3592 100644 --- a/docker-deployment-type1/data/app/config.yaml +++ b/docker-deployment-type1/data/app/config.yaml @@ -46,7 +46,14 @@ ratelimit: # A 429 returns error.code=rate_limited and Retry-After (1 or 60 seconds). burst: 300 -# MeshMapper integrations (optional, no API key). Import transport scopes and +# MeshMapper integrations (optional, require a regional or grouped-region API key). +# Your local MeshMapper regional or grouped-region admin can generate the key. +# A grouped-region key covers multiple regions, including imported group members. +# MESHMAPPER_API_KEY overrides meshmapper.api_key, even when empty. Use the +# environment for secrets, especially with backups; never use the mobile App key. +# Missing keys leave imports unconfigured; cached data and the rest of Beacon remain. +# 401/403 failures retain cached data and report authentication/permission errors. +# Import transport scopes and # IATA border outlines from MeshMapper instead of maintaining scopes: and # iatas.*.borderFile by hand. # @@ -64,6 +71,7 @@ ratelimit: # Region-filtered scope lists and scope stats show imported names only under the # IATAs whose catalogue lists them; IATAs without a catalogue show manual scopes only. #meshmapper: +# api_key: "" # scopes: # enabled: false # refresh_interval: 1h # 1h-24h (MeshMapper allows one get_scopes.php per region per 55m); one source per 15s tick diff --git a/docker-deployment-type2/README.md b/docker-deployment-type2/README.md index de6a7ff..ec620a3 100644 --- a/docker-deployment-type2/README.md +++ b/docker-deployment-type2/README.md @@ -45,6 +45,11 @@ docker-deployment-type2/ Caddy redirects anything that is not `/api/*` or `/ws` to that host. The password inside `POSTGRES_DSN` must match `POSTGRES_PASSWORD` in `docker-compose.yml`. + For MeshMapper imports, set `MESHMAPPER_API_KEY` in this server host's `.env`. + Your local MeshMapper regional or grouped-region admin can generate the regional or + grouped-region key. Do not copy it to the web host or a `VITE_*` setting. See + [MeshMapper API key](../docs/configuration.md#meshmapper-api-key). + 3. Edit `data/app/config.yaml` for your network (see [Configuration](../docs/configuration.md#configyaml)). Because the web app will be served from another origin, browsers may only open the WebSocket if that origin is listed: diff --git a/docker-deployment-type2/server/data/app/config.yaml b/docker-deployment-type2/server/data/app/config.yaml index 6dfc478..59f3592 100644 --- a/docker-deployment-type2/server/data/app/config.yaml +++ b/docker-deployment-type2/server/data/app/config.yaml @@ -46,7 +46,14 @@ ratelimit: # A 429 returns error.code=rate_limited and Retry-After (1 or 60 seconds). burst: 300 -# MeshMapper integrations (optional, no API key). Import transport scopes and +# MeshMapper integrations (optional, require a regional or grouped-region API key). +# Your local MeshMapper regional or grouped-region admin can generate the key. +# A grouped-region key covers multiple regions, including imported group members. +# MESHMAPPER_API_KEY overrides meshmapper.api_key, even when empty. Use the +# environment for secrets, especially with backups; never use the mobile App key. +# Missing keys leave imports unconfigured; cached data and the rest of Beacon remain. +# 401/403 failures retain cached data and report authentication/permission errors. +# Import transport scopes and # IATA border outlines from MeshMapper instead of maintaining scopes: and # iatas.*.borderFile by hand. # @@ -64,6 +71,7 @@ ratelimit: # Region-filtered scope lists and scope stats show imported names only under the # IATAs whose catalogue lists them; IATAs without a catalogue show manual scopes only. #meshmapper: +# api_key: "" # scopes: # enabled: false # refresh_interval: 1h # 1h-24h (MeshMapper allows one get_scopes.php per region per 55m); one source per 15s tick diff --git a/docs/backup-export.md b/docs/backup-export.md index 355ef37..0bea075 100644 --- a/docs/backup-export.md +++ b/docs/backup-export.md @@ -22,6 +22,10 @@ changes, PostgreSQL roles and cluster settings, Redis and deployment files are n ## Requirements and limits +- Keep the MeshMapper API key in `MESHMAPPER_API_KEY`, with `meshmapper.api_key` empty or + omitted in the saved YAML. Both CLI exports and admin downloads refuse a nonempty saved + MeshMapper key because the YAML is included verbatim. Environment secrets remain excluded; + preserve them separately for recovery. See [MeshMapper API key](configuration.md#meshmapper-api-key). - `pg_dump` must be in the **same runtime** as the exporter (the CLI's environment, or the server's PATH for the download), with a major version at least as new as the database's. A client on the Docker host or in another container doesn't count. diff --git a/docs/configuration.md b/docs/configuration.md index 9d12ac0..24c0690 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -23,6 +23,7 @@ to open). | `MQTT_BROKER_1_PASSWORD` | unset | Subscriber password for broker 1. | | `MQTT_BROKER_2_URL`, `_USERNAME`, `_PASSWORD` | unset | The same for an optional second broker. A packet heard through both brokers is stored once. | | `BEACON_API_KEY` | unset | Bearer key for `/api/v1/admin/*`. When set, even to an empty string, it replaces `auth.api_key` from the config file. See [Admin API key](#admin-api-key). | +| `MESHMAPPER_API_KEY` | unset | Regional or grouped-region key for enabled MeshMapper imports. When set, even to an empty string, it replaces `meshmapper.api_key`. Missing keys leave imports unconfigured while Beacon keeps running. See [MeshMapper API key](#meshmapper-api-key). | | `LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error`. Overrides `log.level`. Anything else stops startup. | | `LOG_FORMAT` | `text` | `text` or `json`. Overrides `log.format`. Anything else stops startup. | | `BEACON_CPU_PROFILE_DIR`, `BEACON_CPU_PROFILE_UNTIL` | unset | Enable a bounded CPU capture in production. See [Profiling](profiling.md). | @@ -68,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. | 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. 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. | @@ -106,6 +107,58 @@ cannot honour. These are the ones people hit: A negative `websocket.max_connections_per_ip` is not caught at startup and rejects every connection instead, so leave it at a positive number. +## MeshMapper API key + +MeshMapper APIs require either a regional API key or a grouped-region API key covering +multiple regions. Your local MeshMapper regional or grouped-region admin can generate +these keys. Request a key for the APIs Beacon uses, covering your IATA or every member IATA +in your group, including members added by `meshmapper.zones.import_groups`. + +Set `MESHMAPPER_API_KEY` in the deployment's private `.env` or backend environment. Each +Beacon deployment can use a different key. It overrides `meshmapper.api_key` in +`config.yaml`, even when the environment value is empty. Both examples leave the key empty. +Do not use the mobile app's App key, `BEACON_API_KEY`, or a Coverage API key for this setting. +Never put it in a `VITE_*` value, browser configuration, logs, or git. + +After changing `.env`, recreate the backend container from the deployment folder +(`docker-deployment-type2/server` for the split deployment): + +```bash +docker compose up -d --force-recreate app +``` + +A plain `docker compose restart app` does not load changed container environment variables. +If you use the YAML setting instead, restarting Beacon is enough. Prefer the environment +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 +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. + +With no key, MeshMapper imports report unconfigured and make no requests. Beacon keeps +running and restores saved imports. Failed refreshes keep the last successful cached data +and its freshness timestamp; 401 reports authentication failure and 403 reports permission +failure, without falling back to anonymous requests. Existing backoff and longer +`Retry-After` delays on 429/503 remain in effect. See +[MeshMapper troubleshooting](troubleshooting.md#meshmapper-imports-are-unconfigured-or-stale). + +### Coordinate the MeshMapper rollout + +Before enforcement, confirm with the MeshMapper admin that Server supports `X-API-Key`, +that the key covers the required APIs and IATAs, and that these calls do not consume Coverage +quota. Header support must not be assumed deployed. The first four endpoints can require +keys before the app rollout; repeater enforcement waits for app 1.4.1 and its forced update. +Key issuance, permissions, quotas, and enforcement switches remain MeshMapper Server work, +tracked in [MeshMapper_Server#431](https://github.com/MeshMapper/MeshMapper_Server/issues/431). + +Existing `coverage.php` consumers keep their own keys, scopes, quotas, and supported +`?key=` authentication. Do not move them to header-only authentication without confirmed +server support; Beacon's importer does not call `coverage.php`. + ## Admin API key The `/api/v1/admin/*` endpoints need `Authorization: Bearer `. Everything else, including diff --git a/docs/security.md b/docs/security.md index 05c68fb..85b6bfc 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,7 +1,8 @@ # Securing a deployment Beacon is a read-mostly public service. The things worth protecting are the admin key, the API -listener, your broker credentials, and the database, which holds decrypted channel messages. +listener, your broker and MeshMapper credentials, and the database, which holds decrypted +channel messages. ## The admin key @@ -14,6 +15,14 @@ environment wins over the file) are in [Configuration](configuration.md#admin-ap If you do not need the admin endpoints, do not set a key. They answer `503` and nothing else changes. +## The MeshMapper API key + +Obtain a regional or grouped-region API key from your local MeshMapper admin and keep it +in `MESHMAPPER_API_KEY` in the backend environment. Never use the mobile App key, publish +the key through a `VITE_*` setting, or commit it. Beacon sends it only as `X-API-Key` to +the supported HTTPS MeshMapper API endpoints and refuses redirects. This is separate +from Beacon's admin authentication. See [MeshMapper API key](configuration.md#meshmapper-api-key). + ## Keep the API listener private beacon-server listens on `:8080` with no TLS. In the Docker deployments it is only reachable @@ -49,6 +58,6 @@ if you enable it, contains everything the database holds, so keep archives priva ## Secrets in files -`.env` holds the database password, broker credentials and the admin key. It is gitignored in +`.env` holds the database password, broker credentials, admin key, and MeshMapper API key. It is gitignored in the deployment folders; keep it that way, and keep its permissions tight on the host. `data/app/config.yaml` holds channel keys. Neither belongs in a public backup. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4d29677..f4840a5 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -93,6 +93,37 @@ The channel's key is not configured, or it was added without a restart. Add it u the stored messages for the new key and logs `config: backfilled N previously-undecrypted channel message(s)`. +## 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`, +`next_attempt`, and, for regional imports, `checked_at`. + +| Message | Fix | +|---|---| +| `unconfigured: MeshMapper API key is missing` | Set `MESHMAPPER_API_KEY` in the backend environment. An explicitly empty environment value overrides a YAML key. | +| `unconfigured: MeshMapper API key contains invalid characters` | Re-enter the key supplied by your local MeshMapper regional or grouped-region admin; remove embedded whitespace or control characters. | +| `authentication failed (HTTP 401)` | Check that you are using the correct regional or grouped-region API key and that MeshMapper Server supports `X-API-Key`. Do not use the mobile App key. | +| `permission denied (HTTP 403)` | Ask the MeshMapper admin to check access to the requested API and IATA. A group key needs every member IATA, including members added by `import_groups`. | +| `HTTP 429` or `HTTP 503` | Wait until `next_attempt`. Beacon honours longer `Retry-After` delays and its existing refresh limits. Do not shorten polling intervals or repeatedly restart to force a request. | + +After changing `.env`, run `docker compose up -d --force-recreate app` from the backend +deployment folder. A plain restart does not reload container environment variables. +Persisted backoff still applies after a restart. Never paste the key into logs, URLs, or +support messages. + +Beacon keeps the last successful cached data and freshness timestamp after a failed +refresh, including an authentication failure. It does not replace imports with empty +lists or retry anonymously. See [MeshMapper API key](configuration.md#meshmapper-api-key) +for setup and rollout requirements. + +## Backup export refuses a saved MeshMapper key + +Move the key from `meshmapper.api_key` to `MESHMAPPER_API_KEY` in the backend environment, +clear the saved YAML value, and recreate `app`. Backups include saved YAML verbatim, so +exports refuse a nonempty MeshMapper key there to keep it out of download responses. +See [Backup and export](backup-export.md#requirements-and-limits). + ## I changed a `VITE_*` value and nothing changed Run `docker compose up -d web` (`docker compose up -d beacon-web` in the split deployment). The web container writes the values to `/config.js` when it diff --git a/docs/upgrading.md b/docs/upgrading.md index 7b46061..3f4c5f9 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -93,6 +93,26 @@ 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`. +## MeshMapper API authentication rollout + +When upgrading to a Beacon build with MeshMapper API authentication, configure the key +before relying on imports. Ask your local MeshMapper regional or grouped-region admin +for a key covering the required APIs and your region or all member regions. Confirm +MeshMapper Server supports `X-API-Key` before enforcement; see +[MeshMapper API key](configuration.md#meshmapper-api-key) for the rollout details. + +Set `MESHMAPPER_API_KEY` in the backend `.env`, then recreate the `app` container: + +```bash +docker compose up -d --force-recreate app +``` + +Without a key, Beacon keeps running and retains saved imports, but MeshMapper refreshes +are unconfigured. Existing IP exemptions are not a substitute for a key. After configuring +one, check the MeshMapper logs for successful refreshes; existing backoff still applies. +If you use backups, leave `meshmapper.api_key` empty in the saved YAML and keep the key in +the environment. Exports refuse a nonempty saved YAML key. + ## Changes that need a restart Beacon reads `config.yaml` once at startup. After editing it, run @@ -101,7 +121,10 @@ Beacon reads `config.yaml` once at startup. After editing it, run - A newly added channel key decrypts the matching stored messages. Look for `config: backfilled N previously-undecrypted channel message(s)` in the log. - Border file and MeshMapper import changes take effect. -- A changed admin key is picked up. +- Changed admin or MeshMapper keys in the YAML file are picked up. + +For environment changes in `.env`, use `docker compose up -d --force-recreate app` instead. +A container restart reuses its old environment. The one runtime-only setting is CORS origins: `PUT /api/v1/admin/config` changes them without a restart, and nothing is written to the file, so a restart reloads whatever the file says. From 791417368b11ba9e4e3e6d7899b56bab45bf90a9 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Mon, 5 Oct 2026 06:34:00 -0400 Subject: [PATCH 4/8] docs(observers): document directory pagination (#15) --- docs/api-contract.md | 1 + docs/observer-directory.md | 118 +++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 docs/observer-directory.md diff --git a/docs/api-contract.md b/docs/api-contract.md index 32a0b0c..48243f3 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -277,6 +277,7 @@ edges spanning more than 180 degrees are rejected. | Endpoint | Notes | |---|---| | `GET /observers` | Page of observers. Params: location filters, `type` (e.g. `meshcoretomqtt`), `broker`, `status` (`online`/`offline`), `name`, `scope`, `cursor` (`lastSeen` epoch ms), `limit`. | +| `GET /observers/directory` | Traffic/name pagination, row counts and type facets. See [observer directory contract](observer-directory.md) for rollout status and exact semantics. | | `GET /observers/{observerId}` | Observer detail. | | `GET /observers/{observerId}/telemetry?range=24h&interval=1h` | Telemetry history. `interval` is `1h` (default), `6h` or `24h`. `afterId` returns only newer points. | | `GET /observers/{observerId}/activity?range=24h&interval=15m` | What the observer heard, bucketed. | diff --git a/docs/observer-directory.md b/docs/observer-directory.md new file mode 100644 index 0000000..550df25 --- /dev/null +++ b/docs/observer-directory.md @@ -0,0 +1,118 @@ +# Observer directory + +Status: pending [server PR #212](https://github.com/MeshCore-Beacon/beacon-server/pull/212), +not yet deployed. The existing `/observers` endpoint remains unchanged. + +## Requests + +`GET /api/v1/observers/directory?iata=YVR&sort=traffic&limit=50` + +| Parameter | Contract | +|---|---| +| `sort` | `traffic` (default) or `name`. | +| `limit` | Positive integer, default 50, capped at 200 per page. No directory-wide row cap. | +| `cursor` | Nonnegative integer offset, default 0. Pass the returned `nextCursor`. | +| `since`, `until` | Positive epoch milliseconds, inclusive start and exclusive end, rounded down to UTC hours. Window must be 1 hour through 31 days; `until` cannot be in the future. Required when cursor is nonzero. | +| `iata`, `iatas` | Case-insensitive IATA or comma-separated IATAs. Nonempty `iatas` takes precedence. | +| `regionId`, `region` | Expand region ID or slug and union with explicit IATAs. ID takes precedence. An empty region without explicit IATAs matches nothing. | +| `name` | Case-insensitive display-name substring using SQL ILIKE semantics (`%` and `_` are wildcards). | +| `type`, `broker`, `scope` | Exact observer type, broker membership or transport scope name. URL-encode `#` in scopes. | +| `status` | `online` or `offline`. | + +All filters apply before ordering and pagination. Type, broker, name and scope +are limited to 200 UTF-8 bytes each. At most 200 IATAs are accepted after region +expansion and before deduplication. Unknown/repeated parameters return 400. + +The first page defaults to `until = floorToUtcHour(now - 95 minutes) + 1 hour`, +and `since = until - 24 hours`. This excludes the latest 35 to 95 minutes; +these are hourly analytics counts, not live packet counters. + +For every continuation, repeat all filters and sort, pass the first response's +`windowStart` as `since` and `windowEnd` as `until`, and use `nextCursor`: + +```text +/api/v1/observers/directory?iata=YVR&sort=traffic&since=1767225600000&until=1767312000000&cursor=50&limit=50 +``` + +## Response + +Items contain the existing `ObserverSummary` fields plus required nullable +`observationCount`. Optional names, type, radio and empty scopes may be omitted. + +```json +{ + "items": [ + {"id": "00000000-0000-0000-0000-000000000001", "displayName": "North receiver", "iata": "YVR", "status": "online", "observationCount": 1200} + ], + "nextCursor": 1, + "hasMore": true, + "generatedAt": 1767314100000, + "windowStart": 1767225600000, + "windowEnd": 1767312000000, + "sort": "traffic", + "effectiveSort": "traffic", + "coverage": {"status": "complete", "expectedHours": 24, "completeHours": 24, "partialHours": 0, "missingHours": 0}, + "maxObservationCount": 1200, + "observerTypes": ["meshcore-ha", "meshcoretomqtt"] +} +``` + +Timestamps are epoch milliseconds. `items` and `observerTypes` are always arrays. +A terminal page has `hasMore: false` and no `nextCursor`. A cursor past the end +returns an empty terminal page. + +Counts sum `analytics_hourly_observer_identity` for the requested window. They +represent stored hearings per observer, deduplicated across brokers, not MQTT +messages or the observer detail's cumulative presence counter. No raw observation +scan is needed. Directory membership uses current observer IATA; counts include +only observations in selected IATAs, or all IATAs without a location filter. +Broker and scope filter membership, not the source of counted observations. + +When every requested hour is complete, missing observer analytics mean **0**. +If any hour is partial or missing, every count and `maxObservationCount` is +**null**, and `effectiveSort` is `name`. Coverage is `complete` when all hours are +complete, `partial` when some are complete or partial but not all complete, and +`unavailable` when all are missing. Absent rollup records count as missing. +Never render null as zero. + +Traffic order is count descending, then lowercase display name ascending using +PostgreSQL `C` collation, then UUID ascending. Name order uses the last two keys. +Missing names sort as empty strings. Online status means last status or last +seen within five minutes at request time. + +`maxObservationCount` covers the whole filtered result, including in name order. +It is 0 for an empty complete result. `observerTypes` includes all distinct +nonempty types matching every filter except the type filter itself, independent +of pagination. + +## Consistency and client behavior + +Each request reads current data. There is no stored snapshot, expiry, new table +or snapshot capacity limit. Keeping the window fixed avoids hourly window drift, +but analytics corrections and metadata changes can still move rows between +pages, causing repeats or skips. Counts, coverage, type choices and maximum can +also change. This endpoint is a browsing list, not a consistent export. + +Use one infinite query keyed by server, filters, window and sort. A query change +starts from page one and discards stale responses. Preserve server order, allow +one continuation in flight, deduplicate by observer UUID, and stop on +`hasMore=false` or a missing/repeated cursor. Fetch again near the scroll end, +including when the viewport is not filled. No per-row count requests or separate +top-observers request is needed. + +For stable bars while scrolling, retain the first page's maximum and clamp +count/max to [0,1]; reset on refresh. Render unavailable when count or maximum is +null, and zero width when the maximum is zero. If `effectiveSort` changes between +pages as coverage changes, restart from page one rather than mixing orderings. +Live status badges can update separately. Refresh the list to update membership +or order; selection and deep links continue to use UUIDs. + +Invalid parameters and unknown regions return 400. Database failures return 500; +retain loaded rows and offer retry. Successful pages and store errors send +`Cache-Control: no-store`; database reads have a five-second operation budget. + +Probe `/api/v1/observers/directory?limit=1` for compatibility. A supported server +returns the response above. Older servers may return 404, or the legacy detail +route's 400 with message `failed to parse observer UUID`. Other errors do not +prove lack of support. Use an explicitly limited legacy fallback or request a +server upgrade; do not assume the old `/observers` route honors new parameters. From ee088b5dfa391c0e0ecd6c2c650d327933327c43 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:19:37 -0400 Subject: [PATCH 5/8] docs(mobile): add BEACON Mobile privacy policy and support page (#16) --- docs/mobile/privacy.md | 64 ++++++++++++++++++++++++++++++++++++++++++ docs/mobile/support.md | 33 ++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 docs/mobile/privacy.md create mode 100644 docs/mobile/support.md diff --git a/docs/mobile/privacy.md b/docs/mobile/privacy.md new file mode 100644 index 0000000..c64500b --- /dev/null +++ b/docs/mobile/privacy.md @@ -0,0 +1,64 @@ +# BEACON Mobile Privacy Policy + +_Last updated: October 6, 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 +the app does and does not do with your information. + +## The short version + +- No account, no sign-in. +- No analytics, advertising, tracking or crash-reporting SDKs. +- The app does not access your location, contacts, photos, microphone or + camera. +- Your settings stay on your device. + +## What stays on your device + +The app stores its settings locally, using the platform's standard app storage: +the Beacon servers you have added, your selected region, theme, language and map +preferences. This data never leaves your device unless you back up your device +through Apple or Google. Deleting the app deletes it. + +## Network connections the app makes + +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 + 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. +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 + areas requested, as with any map. + +The app sends no personal information to these services beyond what any network +request carries, such as your IP address. + +## Mesh data shown in the app + +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). + +## Children + +The app is not directed at children and collects no personal information from +anyone. + +## Changes + +Updates to this policy will be published on this page with a new date. + +## Contact + +Questions about this policy: [MrAlders0n@smal.ca](mailto:MrAlders0n@smal.ca) +or [open an issue](https://github.com/MeshCore-Beacon/beacon-flutter-app/issues). diff --git a/docs/mobile/support.md b/docs/mobile/support.md new file mode 100644 index 0000000..49dc1ca --- /dev/null +++ b/docs/mobile/support.md @@ -0,0 +1,33 @@ +# BEACON Mobile Support + +BEACON Mobile is the iOS and Android client for Beacon, the real-time MeshCore +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. +Running your own server is covered in the +[deployment guide](https://github.com/MeshCore-Beacon/beacon-docs#readme). + +## Beta testing + +- **iPhone and iPad:** join through the public TestFlight link and send feedback + from the TestFlight app (take a screenshot in BEACON to attach it). +- **Android:** join the Google Play testing program from the opt-in link, then + leave feedback on the app's Play page. + +## Report a problem or request a feature + +[Open an issue on GitHub](https://github.com/MeshCore-Beacon/beacon-flutter-app/issues) +with your device model, OS version, the app version (Settings → About) and what +happened. + +## Contact + +Email [MrAlders0n@smal.ca](mailto:MrAlders0n@smal.ca). + +## Privacy + +See the [privacy policy](privacy.md). From 9d1dae3bf9892d58f8c0762c7c74724ec1e80299 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:47:26 -0400 Subject: [PATCH 6/8] docs: document GET /info and mobile.min_app_version (#18) * docs: document GET /info and mobile.min_app_version * docs: match /info serverVersion and admin field to the server --- app_config/config.yaml.example | 8 ++++++++ docs/api-contract.md | 28 +++++++++++++++++++++++++++- docs/configuration.md | 2 ++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app_config/config.yaml.example b/app_config/config.yaml.example index c30fe48..08bee2c 100644 --- a/app_config/config.yaml.example +++ b/app_config/config.yaml.example @@ -271,6 +271,14 @@ 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. +#mobile: +# min_app_version: "0.1.1" # default: "" (no requirement) + # CORS configuration. # Controls which origins, methods and headers are allowed for cross-origin requests. # Defaults to allowing all origins with GET/HEAD/OPTIONS if omitted, appropriate diff --git a/docs/api-contract.md b/docs/api-contract.md index 48243f3..4fd8498 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -591,13 +591,31 @@ A list item: lastHeardAt, rawPath, resolvedRoute }`. `rawPath` is `[{ hash, snr }]` as received; `resolvedRoute` uses the resolved hop shape from packet detail. +### Server info + +``` +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` 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 +[Mobile-specific concerns](#mobile-specific-concerns) for how the app uses it. + ### 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). No credentials, broker addresses, channel material or database settings. | +| `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. | | `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. | @@ -856,6 +874,14 @@ Flutter on iOS background suspension and Android battery saver will kill the Web The protocol doesn't need to know about backgrounding; the client just treats reconnection as the recovery mechanism. +**Minimum app version.** The app calls `GET /info` on launch, on return to foreground and when +the user switches servers. When the app's own version is lower than `minAppVersion` (compared +numerically, part by part), it blocks that server behind an "update required" screen until the +app is updated. Other servers stay usable. `minAppVersion: null` means no requirement. A `404` +comes from a server that predates the endpoint and also means no requirement. Network errors, +timeouts and other failures do not block: the app keeps using the server and checks again next +time. + --- ## Open questions diff --git a/docs/configuration.md b/docs/configuration.md index 24c0690..c679fe6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -83,6 +83,7 @@ which ones to read. | `websocket` | Connections per client, upgrade attempts per minute, and which other sites may open `/ws`. | The web app is served from a different host than the API. | | `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. | | `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. | @@ -102,6 +103,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). - 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 From 182010dbc64ddb31e32e425a86701bda2095d41e Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:05:49 -0400 Subject: [PATCH 7/8] docs: document web.min_web_version, batched route search and merge-commit releases (#20) --- app_config/config.yaml.example | 15 +++++++++------ docs/api-contract.md | 25 ++++++++++++++++--------- docs/configuration.md | 3 ++- docs/releases.md | 10 ++++++---- docs/upgrading.md | 4 ++++ 5 files changed, 37 insertions(+), 20 deletions(-) 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 From 80d6ebbd0e6aee0902d255acd440cc30702617f4 Mon Sep 17 00:00:00 2001 From: MrAlders0n <55921894+MrAlders0n@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:07:36 -0400 Subject: [PATCH 8/8] docs(mobile): drop the default server from the privacy and support pages (#22) BEACON Mobile no longer ships with a built-in server; users add one on first launch. Describe that, and stop naming a specific server. --- docs/mobile/privacy.md | 14 ++++++-------- docs/mobile/support.md | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) 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).