Files
beacon-server/config.yaml.example
T
MrAlders0n 9bbf8efee5 release: v2.0.4 (#217)
* perf(routes): batch cross-IATA and hash route search (#216)

One search fired a /routes/cross request per IATA pair (138k on 2026-10-08). Route search now takes an optional iatas list and runs a fixed number of index-backed queries. /info advertises built-in minimum client versions.

* chore: bump version to 2.0.4
2026-10-09 22:17:23 -04:00

331 lines
15 KiB
Plaintext

# Beacon configuration file: local development starter. Copy to config.yaml and adjust.
# The fully annotated reference, with every key and its default, is
# https://github.com/MeshCore-Beacon/beacon-docs/blob/main/app_config/config.yaml.example
# Durations are Go strings (30s, 1h, 168h); negative values prevent startup
# (observers.delete_after excepted, where nonpositive disables it).
# Public reads stay available. No key means admin routes return 503.
# Prefer BEACON_API_KEY in the service environment; never commit a real key.
# At least 16 characters with no inner whitespace (surrounding whitespace is
# trimmed); a shorter key prevents startup.
auth:
api_key: ""
log:
# Environment LOG_LEVEL / LOG_FORMAT override these values.
level: info # debug, info, warn, error
format: text # text or json
server:
# Only these direct proxy peers may set the client IP through X-Real-IP.
# The proxy must overwrite that header, not pass through client input.
# Leave empty for direct access (logs a startup warning). Invalid/empty CIDR
# entries prevent startup. A single host needs /32 for IPv4 or /128 for IPv6;
# bare IP addresses are not accepted.
# For Docker, list the actual proxy/compose network CIDR (e.g. 172.20.0.0/24).
# For a local host proxy, use ["127.0.0.1/32", "::1/128"].
# Direct-client proxy examples; X-Forwarded-For alone is not used:
# Apache 2.4.10+ (mod_headers, in the proxy virtual host):
# RequestHeader set X-Real-IP "expr=%{REMOTE_ADDR}"
# nginx (in the proxy location):
# proxy_set_header X-Real-IP $remote_addr;
# Caddy (inside the reverse_proxy block):
# header_up X-Real-IP {http.request.remote.host}
# With additional proxy hops, first configure their trust at the edge proxy
# and pass its validated client address rather than a client-supplied header.
trusted_proxies: []
# Applies to /api/v1/* only. Both counters are local to this Beacon instance.
# Set trusted_proxies correctly before using a reverse proxy, or clients share
# the proxy's budget. IPv6 clients share a /64 budget to prevent address rotation.
ratelimit:
enabled: true
requests_per_minute: 300
# Maximum requests in a one-second sliding window, not token-bucket capacity.
# Omitted/zero defaults to requests_per_minute. Negative limits prevent startup.
# A 429 returns error.code=rate_limited and Retry-After (1 or 60 seconds).
burst: 300
# 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.
# 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.
#
# Scope-name discovery. Manual scopes remain authoritative.
# Every known IATA, including ones auto-created from traffic, is looked up in its
# country's zone list (shared with zones below) and its site's get_scopes.php
# catalogue is imported; IATAs MeshMapper doesn't list are skipped. No sources
# to configure; a legacy sources: list is ignored with a warning.
# Catalogues covering more than one IATA are rejected.
# 64 names per catalogue, 64 KiB per response; no page scraping.
# Catalogues and ETags persist in PostgreSQL. Failed refreshes retain known names.
# Refresh success/failure/freshness is logged under component=meshmapper.scopes.
# Turning scopes off deactivates imported matching keys on restart;
# historical scope identities and recorded evidence are retained.
# 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: 24h # 1h-24h (MeshMapper allows one get_scopes.php per region per 55m); one source per 15s tick
#
# Region boundaries (Zones API). Every known IATA, including ones auto-created
# from traffic, is looked up in its country's zone list, then its get_geojson.php
# outline is imported; new IATAs are picked up while running. An imported boundary
# overrides that IATA's borderFile on the border map and for nodes.mark_foreign.
# A missing or failed boundary keeps the last good import, or the borderFile when
# there is none. Disabling drops every import on restart. Logged under
# component=meshmapper.zones.
# Each country's zone list is fetched at most once a day (MeshMapper allows one
# get_zones.php per country per 23.5h) and saved, so restarts don't refetch it.
# Failed or rate-limited requests also wait out the limit, or Retry-After if longer.
# Listed IATAs also take their display name and approximate location from the
# zone list, unless iatas: below sets a name or lat/lng for them.
# import_groups turns MeshMapper zone groups (e.g. ONQC: YOW, YQB, YUL) into
# regions, from the zone lists already fetched. A group is imported when at least
# one member is heard by an observer or configured here, with its full member list (members Beacon hasn't
# heard yet are added as IATAs); slug = lowercased group code, centered on the
# members' average location, listed after the regions below. A region below with
# the same slug wins.
# Imported regions are removed when the group disappears or stops qualifying,
# and all of them when import_groups is turned off. Requires zones.enabled.
# zones:
# enabled: false
# import_groups: false
# refresh_interval: 24h # 24h-168h (one get_geojson.php per region per 23.5h); one request per 15s tick, ETag-conditional
#
# Public hashtag channels (get_channels.php). Every known IATA that MeshMapper
# lists is looked up on its site, the same way as scopes. Each key and hash is
# checked against the name before use; a list with any mismatch is rejected and
# the last good one kept. Imported channels decrypt like configured ones (history
# is backfilled when a key first appears) and are listed under that IATA when
# /channels is filtered by region. Saved in PostgreSQL across restarts; failures
# wait out the 23.5h limit. Turning it off removes the region listing on restart.
# Logged under component=meshmapper.channels.
# channels:
# enabled: false
# refresh_interval: 24h # 24h-168h (one get_channels.php per region per 23.5h)
iatas:
YVR:
name: Vancouver International
lat: 49.1967
lng: -123.1815
# borderFile: borders/yvr.geojson # optional GeoJSON Feature (Polygon/MultiPolygon)
# # (or use meshmapper.zones above)
# # for the region border map; path is relative to
# # this file's directory; validated at boot.
YYJ:
name: Victoria International
lat: 48.6469
lng: -123.4260
YYC:
name: Calgary International
lat: 51.1139
lng: -114.0200
YEG:
name: Edmonton International
lat: 53.3097
lng: -113.5797
YOW:
name: Ottawa International
lat: 45.3225
lng: -75.6692
YYZ:
name: Toronto Pearson
lat: 43.6777
lng: -79.6248
# Hand-written regions; optional with meshmapper.zones.import_groups. Seeding
# sets each region's members to exactly this list.
regions:
- slug: western-canada
name: Western Canada
display_order: 1
center_lat: 51.0
center_lng: -114.0
zoom_level: 5
iatas: [YVR, YYJ, YYC, YEG]
- slug: eastern-canada
name: Eastern Canada
display_order: 2
center_lat: 45.0
center_lng: -75.0
zoom_level: 5
iatas: [YOW, YYZ]
channel_keys:
# Hashtag channels: Beacon derives the PSK automatically from the tag name.
# secret = SHA256("#tag")[:16], channel_hash = SHA256(secret)[0]
# Hashtag names should be provided without the # prefix.
# Beacon prepends # automatically before deriving the key.
# A plain name is listed under every region; add region: <slug> (a configured
# region) to list it only there when /channels is filtered by region.
hashtags:
- meshcore
# - name: ottawa-mesh
# region: eastern-canada
# Explicit keys: provide the channel hash (hex) and key (hex) directly.
# An optional name can be set for display purposes.
keys:
"11":
key: "8b3387e9c5cdea6ac9e5edbaa115cd72"
name: "Public"
# region: eastern-canada # optional, as for hashtags
# Regional transport scopes for matching TRANSPORT_FLOOD packets.
# Plain names have # prepended automatically (e.g. "bc" → "#bc").
# region (required) is a configured region slug; region-filtered scope lists
# and scope stats show the scope under that region's IATAs. Matching stays global.
# Optional when meshmapper.scopes covers your regions.
scopes:
- name: bc
region: western-canada
- name: "#west"
region: western-canada
telemetry:
retention: 744h # 31 days
resolution: 1h # store one snapshot per observer per hour
# Optional authenticated database/config downloads. Disabled unless explicitly enabled.
# Requires auth.api_key, a supported PostgreSQL URL and a compatible pg_dump client.
# A failed prerequisite leaves this feature unavailable; ingest and other APIs continue.
#backup:
# enabled: false
# Also covers observations and channel messages.
packets:
retention: 168h # 7 days
# Hourly analytics rollups (stats endpoints). Rollups outlive raw packets; this is also the
# longest window /stats/series and the other historical stats endpoints accept.
analytics:
rollup_retention: 2160h # 90 days
# Presence write coalescing: observer last_seen and packet last_heard_at bumps are
# batched in memory and flushed on an interval. An unclean shutdown loses at most
# one interval of presence freshness.
#presence:
# flush_interval: 30s # default: 30s
# packet_ttl: 30s # how long a quiet packet stays coalesced (default: 30s)
# Known-route retention. Routes are distilled packet history (the path a packet
# took, hop by hop), so they may outlive packets.retention; there is no required
# relationship between the two.
routes:
# How long a route is kept after it was last observed. Every new observation
# of the same path resets this clock, so routes the mesh still uses never
# expire -- only paths nothing has traveled for this long age out.
retention: 336h # 14 days
# Shorter window for rarely-seen routes: one observed fewer than
# min_observations times in total is dropped once it goes unobserved for
# this long. Must be shorter than retention, or it never applies.
grace: 168h # 7 days
# Lifetime observation count a route must reach to earn the full retention
# window. Below it a route is treated as flood noise -- a path recorded once
# or twice and never confirmed -- and only kept for the grace window. The
# count never resets, so once a route crosses this bar it stays in the
# retention tier for good.
min_observations: 3
websocket:
max_connections_per_ip: 5 # default: 5
# Upgrade attempts, including failed handshakes; zero/omitted defaults to 10.
# IPv6 clients share a /64 attempt budget. Negative values prevent startup.
# Rate exhaustion rejects the handshake with 429 and Retry-After: 60.
# Within this rate budget, a full concurrent cap accepts then closes with 1013
# before hello, allowing browsers to back off without evicting existing clients.
max_connects_per_minute: 10
# Other sites allowed to open /ws. Exact scheme://host[:port], or
# scheme://*.domain[:port] for every subdomain of domain at any depth. The
# wildcard never matches the apex itself, and scheme and port stay exact.
#allowed_origins:
# - https://example.com
# - https://*.example.com
# Node staleness, deletion, and clock-drift thresholds.
#nodes:
# mark_foreign: false # opt in to possiblyForeign on repeater nodes
# # uses the union of configured iatas.*.borderFile polygons,
# # with meshmapper.zones boundaries replacing them; requires
# # a valid border file or meshmapper.zones; no packet filtering
# stale_threshold: 24h # default: 24h
# delete_after: 720h # default: 30 days
# clock_drift_threshold: 5m # default: 5m
# Optional observer age-out. Omitted or nonpositive leaves observers intact.
# Enable only with one Beacon ingest process for this database.
# At most 1000 old observers are deleted per background.cleanup interval;
# retained observations, telemetry and ownership records prevent deletion.
#observers:
# delete_after: 720h # opt in to 30 days unseen
# 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
# for a public read-only API. Restrict allowed_origins if you expose write endpoints.
#cors:
# allowed_origins:
# - "https://app.example.com"
# allowed_methods: [GET, HEAD, OPTIONS, POST, PUT, DELETE] # admin UI writes
# allowed_headers: [Accept, Authorization, Content-Type]
# allow_credentials: false
# max_age: 300
# Redis caching layer.
# If REDIS_ADDR is not set, caching is disabled and all reads go directly to PostgreSQL.
# TTLs are specified as duration strings e.g. "30m", "1h", "2h".
# Per-category TTLs override the global ttl. Any category left unset inherits ttl.
cache:
# Global default TTL for all cached responses. Defaults to 1h if not set.
ttl: "1h"
ttls:
# Aggregated stats endpoints (overview, observations, breakdowns, top nodes/observers,
# radio presets, scope stats). Backed by hourly rollups; keys change when a new hour rolls.
stats: "1h"
# Mostly-static reference data (IATAs, regions, scopes).
# Only changes when new observers arrive or config is reseeded.
reference: "1h"
# Individual node detail. Also explicitly invalidated on upsert.
nodes: "1h"
# Individual observer detail. Also explicitly invalidated on upsert.
observers: "1h"
# Geographic ingest filter (optional).
# Drop packets from observers outside the specified area at ingest time.
# Country codes are ISO 3166-1 alpha-2 (e.g. CA, US, GB).
# Continent codes: AF (Africa), AN (Antarctica), AS (Asia),
# EU (Europe), NA (North America), OC (Oceania), SA (South America).
# If both are set an IATA passes if it matches either (OR semantics).
# Omit this section entirely to accept packets from all IATAs (default).
#ingest:
# allow_countries: [CA, US]
# allow_continents: [NA]
# Background task intervals.
# Shorter intervals are useful during initial deployment to confirm data is
# flowing. Back off to 1h or more once stable.
#background:
# view_refresh: 1h # rebuilds radio presets; default: 1h
# reconfirm: 1h # default: 1h
# cleanup: 1h # default: 1h
# Minimum client versions, served at GET /api/v1/info. Older clients show
# "update required" for this server. Each release has a built-in floor; these
# can raise it, never lower it. Strict X.Y.Z (no v, no suffix); anything else
# prevents startup. Default: empty, the built-in floor only.
#mobile:
# min_app_version: "0.1.1" # BEACON Mobile
#web:
# min_web_version: "2.0.3" # Beacon Web