Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Default owners for everything
* @446564
# Every change to main needs a review from the repo owner
* @MrAlders0n
24 changes: 24 additions & 0 deletions .github/workflows/main-source-guard.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions app_config/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 17 additions & 1 deletion app_config/config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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
Expand Down Expand Up @@ -263,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
Expand Down
10 changes: 9 additions & 1 deletion docker-deployment-type1/data/app/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions docker-deployment-type2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 9 additions & 1 deletion docker-deployment-type2/server/data/app/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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
Expand Down
29 changes: 28 additions & 1 deletion docs/api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -590,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. |
Expand Down Expand Up @@ -855,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
Expand Down
4 changes: 4 additions & 0 deletions docs/backup-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
57 changes: 56 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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). |
Expand Down Expand Up @@ -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. |
Expand All @@ -82,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. |
Expand All @@ -101,11 +103,64 @@ 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
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 <key>`. Everything else, including
Expand Down
Loading
Loading