Configuration
The API uses a layered TOML configuration system. Settings are loaded in this order (later values override earlier):
config/default.toml— base configurationconfig/{APP_ENV}.toml— environment-specific overrides (optional)config/local.toml— local overrides, gitignored (optional)- 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] 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
localis not double-tracked from aisstream. Once that vessel goes silent on the local feed for longer thanlocal_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.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.
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.