Skip to main content

Configuration

The API uses a layered TOML configuration system. Settings are loaded in this order (later values override earlier):

  1. config/default.toml — base configuration
  2. config/{APP_ENV}.toml — environment-specific overrides (optional)
  3. config/local.toml — local overrides, gitignored (optional)
  4. Environment variables with APOLLON__ prefix (double underscore)

Config File Structure

[server]
host = "0.0.0.0"
port = 3000
trust_proxy = false

[database]
url = "postgresql://apollon:apollon@localhost:5432/apollon"

[redis]
url = "redis://:apollon@localhost:6379" # password carried in the URL
pool_size = 10 # reserved (ConnectionManager is multiplexed)
timeout = 5 # connect timeout (seconds)
ttl_secs = 86400 # AIS static-enrichment cache TTL (24h)
vessel_snapshot_ttl_secs = 900 # AIS vessels cold-start snapshot TTL (15 min)

[timescale]
url = "postgresql://apollon:apollon@localhost:5433/apollon_tsdb"
max_connections = 5
min_connections = 1
connect_timeout = 30
idle_timeout = 600
max_lifetime = 1800
compression_after_hours = 168

[peplink]
router_ip = "192.168.50.1"
username = "admin"
password = "admin"

[websocket]
path = "/v1/ws"
update_interval_ms = 1000
tokens = []

[forwarding]
# url = "https://external-service.example.com"
# token = "bearer-token" # sent as Authorization: Bearer header
# device_id = "" # sent as X-Device-ID header (optional)
# health_url = "https://external-service.example.com/health"
max_queue_size = 100
max_retries = 3
retry_delay_ms = 5000
timeout_ms = 10000

[cors]
allowed_origins = ["*"]

[logging]
level = "info"
format = "pretty"

[auth]
api_keys = []

[docs]
swagger_ui = true
openapi_json = true
graphiql = true

# [rate_limit]
# window_ms = 900000
# max_requests = 100

# [sentry]
# dsn = ""

[cleanup]
retention_days = 30
interval_hours = 24
enabled = true

[mock]
use_mock_data = false

[ais]
enabled = false # master switch for all AIS vessel tracking
local_priority_ttl_secs = 120 # dual-mode handoff: keep tracking an MMSI via local for this long after its last local message before aisstream may resume it
ignore_mmsi = [] # applies to BOTH sources
stale_timeout_secs = 1800 # seconds without any AIS message before a vessel is dropped from the map (both sources)

[ais.aisstream]
enabled = true # AISstream.io WebSocket source
api_key = "" # AISstream.io API key (required when this source is enabled)
radius_km = 10 # aisstream subscription bounding-box radius

[ais.local]
enabled = false # local NMEA/AIS receiver (e.g. Quark-elec A027+)
host = "192.168.1.69"
port = 2000
# reception_range_km = 40 # optional; map bbox circle only, never filters received vessels

[ais.forward]
enabled = false # forward local-source vessels to Heimdall over a WebSocket client connection
# url = "wss://heimdall.example.com/v1/ws" # Heimdall forward-AIS WebSocket ingest endpoint
# token = "bearer-token" # sent as Authorization: Bearer on the upgrade request
# device_id = "" # sent as X-Device-ID header (optional)

[pegelonline]
base_url = "https://pegelonline.wsv.de/webservices/rest-api/v2"
enabled = false
radius_km = 30
stations = [] # Station whitelist (PegelOnline API shortnames). Empty = radius-based search.

[storage]
path = "./data/storage"
max_file_size_mb = 10

[watchlist]
enabled = false
AIS sources — independent toggles with local priority

[ais] enabled is the master switch. Each source is enabled on its own: [ais.aisstream] enabled and [ais.local] enabled. Any combination works:

  • aisstream only — cloud AIS via AISstream.io.
  • local only — a physical NMEA/AIS receiver (e.g. Quark-elec A027+) over TCP.
  • both (dual mode) — the local receiver takes priority for vessels in antenna range; aisstream fills in everything beyond it. A vessel heard live via local is not double-tracked from aisstream. Once that vessel goes silent on the local feed for longer than local_priority_ttl_secs (default 120 s) — i.e. it has left antenna range — aisstream resumes tracking it (handoff). Local always wins for in-range vessels.

The old [ais] source = "aisstream" | "local" enum has been removed — replace it with the per-source enabled flags. api_key and radius_km now live under [ais.aisstream] (not [ais]).

AIS forwarding to Heimdall ([ais.forward])

When enabled, Apollon forwards only its local-source vessels (from the A027+ receiver) to Heimdall over a persistent outbound WebSocket, reusing the vessels delta protocol. aisstream vessels are not forwarded (Heimdall runs its own aisstream client) — Apollon's near-range local feed supplements Heimdall's global map. enabled defaults to false; set url (Heimdall's forward-AIS WebSocket endpoint), token (Bearer), and optionally device_id (X-Device-ID). See the WebSocket API for the wire contract.

Redis is optional

The [redis] section powers the AIS static-enrichment cache and the AIS vessels cold-start snapshot (vessel_snapshot_ttl_secs). Redis is attempted at startup within timeout seconds; if it is unreachable the API logs a warning and falls back to Postgres-only enrichment (slower, but complete) and an empty vessels map on cold start. There is no enabled flag — an absent or unreachable Redis simply degrades gracefully. Configs written before this feature (no [redis] block) keep working via defaults.

Local Overrides

Create config/local.toml for machine-specific settings (this file is gitignored):

[peplink]
router_ip = "10.0.0.1"
username = "myuser"
password = "mypassword"

[ais]
enabled = true
ignore_mmsi = [211581120]

[ais.aisstream]
enabled = true
api_key = "your-aisstream-api-key"

# Add the local A027+ receiver alongside aisstream for dual mode (local priority + aisstream secondary):
# [ais.local]
# enabled = true
# host = "192.168.1.69"
# port = 2000

[pegelonline]
enabled = true
# With stations whitelist: searches within 200km, filters by whitelist, radius_km is ignored.
# Without (empty): searches by GPS proximity within radius_km.
stations = ["OTTENHEIM", "IFFEZHEIM", "MAXAU", "MAINZ", "KOBLENZ", "KÖLN"]

[storage]
path = "./data/storage"
max_file_size_mb = 10

[watchlist]
enabled = true

[mock]
use_mock_data = true

Environment-Specific Config

Set APP_ENV to load an additional config file:

APP_ENV=production # loads config/production.toml
APP_ENV=staging # loads config/staging.toml

See Environment Variables for the full list of APOLLON__ variables.