# Beacon configuration file # Copy to config.yaml and adjust as needed. # Public reads stay available. No key means admin routes return 503. # Prefer BEACON_API_KEY in the service environment; never commit a real key. 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 iatas: YVR: name: Vancouver International lat: 49.1967 lng: -123.1815 # borderFile: borders/yvr.geojson # optional GeoJSON Feature (Polygon/MultiPolygon) # # 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 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. hashtags: - meshcore # 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" # Regional transport scopes for matching TRANSPORT_FLOOD packets. # Plain names have # prepended automatically (e.g. "bc" → "#bc"). scopes: - name: bc - name: "#west" telemetry: retention: 672h # 4 weeks 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 packets: retention: 720h # 30 days # 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 # 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; # # requires at least one valid border; no packet filtering # stale_threshold: 24h # default: 24h # delete_after: 720h # default: 30 days, same default as packets.retention # 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 materialized views refreshed hourly. 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 # default: 1h # reconfirm: 1h # default: 1h # cleanup: 1h # default: 1h