2026-07-07 17:50:51 -04:00
|
|
|
"""OSINT Dashboard — centralized configuration (12-factor, env-driven).
|
|
|
|
|
|
|
|
|
|
All infrastructure endpoints are read from the environment with
|
|
|
|
|
container-friendly defaults. No secrets, hostnames, or cluster-specific
|
|
|
|
|
addresses are hardcoded anywhere in the codebase.
|
|
|
|
|
|
|
|
|
|
Override per environment via environment variables (docker-compose,
|
|
|
|
|
k8s ConfigMap/Secret, or systemd):
|
|
|
|
|
|
|
|
|
|
DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME
|
|
|
|
|
NATS_URL
|
|
|
|
|
MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_SECURE
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
from urllib.parse import quote_plus
|
|
|
|
|
|
|
|
|
|
# ── PostgreSQL ────────────────────────────────────────────────────────────
|
|
|
|
|
# Defaults assume docker-compose/k8s service names. Override for your stack.
|
|
|
|
|
DB_USER = os.getenv("DB_USER", "osint")
|
|
|
|
|
DB_PASSWORD = os.getenv("DB_PASSWORD", "")
|
|
|
|
|
DB_HOST = os.getenv("DB_HOST", "postgres")
|
|
|
|
|
DB_PORT = os.getenv("DB_PORT", "5432")
|
|
|
|
|
DB_NAME = os.getenv("DB_NAME", "osint_data")
|
|
|
|
|
|
|
|
|
|
# Async SQLAlchemy URL. Password is URL-encoded so special chars are safe.
|
|
|
|
|
DATABASE_URL = (
|
|
|
|
|
f"postgresql+asyncpg://"
|
|
|
|
|
f"{DB_USER}:{quote_plus(DB_PASSWORD)}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# ── NATS JetStream ─────────────────────────────────────────────────────────
|
|
|
|
|
NATS_URL = os.getenv("NATS_URL", "nats://nats:4222")
|
|
|
|
|
|
|
|
|
|
# ── MinIO (document storage) ────────────────────────────────────────────────
|
|
|
|
|
MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "minio:9000")
|
|
|
|
|
MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "")
|
|
|
|
|
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "")
|
|
|
|
|
# "false" / "0" / "no" (case-insensitive) → plain HTTP (e.g. local compose).
|
|
|
|
|
MINIO_SECURE = os.getenv("MINIO_SECURE", "false").lower() not in ("false", "0", "no")
|
Add NASA FIRMS active-fire ingest + /api/fires; API keys management page
Coherent merge of two coordinated features on the shared working tree:
FIRMS fire heatmap (backend, t_6e404c14):
- app/fire_sources.py: fetch FIRMS VIIRS area CSV (free MAP_KEY) -> NATS events.fire
- fires hypertable (TimescaleDB, 1-day chunks) with natural-key PK
(latitude, longitude, acq_time, satellite); idempotent ON CONFLICT DO NOTHING
- alembic/versions/002_fires.py; GET /api/fires?bbox=&since= (JSON only)
- POST /api/ingest/fires; ~15 min poll loop (FIRMS_INTERVAL=900) in ingester
- env-driven config (FIRMS_MAP_KEY/DATASET/BBOX/INTERVAL); docs/firms.md covers
the zero-cost GIBS VIIRS_SNPP_Thermal_Anomalies_375m_All tile alternative
- 18 tests (parser, mapping, idempotency, API contract) verified vs real
TimescaleDB+PostGIS (localhost/osint-dashboard-pg image)
API keys page (frontend, t_4433cff2):
- app/keystore.py: api_keys table (self-creating), FIRMS/GEMINI/TELEGRAM
registry with format validation, ****last4 masking, get_api_key()
- GET/POST/DELETE /api/keys (never returns full values); Keys tab in index.html
DB_NULL_POOL env switch in app/database.py enables a NullPool for tests /
short-lived processes that open a fresh event loop per unit.
2026-08-24 15:37:42 -04:00
|
|
|
|
|
|
|
|
# ── NASA FIRMS (active fire / hotspot ingest) ───────────────────────────────
|
|
|
|
|
# MAP_KEY is free; obtain one at https://firms.modaps.eosdis.nasa.gov/api/map_key_info/
|
|
|
|
|
# and set FIRMS_MAP_KEY in .env. Until it is set, the fire ingestor logs a
|
|
|
|
|
# warning and stays idle (no crash).
|
|
|
|
|
FIRMS_MAP_KEY = os.getenv("FIRMS_MAP_KEY", "")
|
|
|
|
|
# NRT VIIRS S-NPP active fire/hotspot detection (375m).
|
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
|
|
|
# NASA stops Suomi NPP product delivery on 2026-11-01 — default to NOAA-20.
|
|
|
|
|
FIRMS_DATASET = os.getenv("FIRMS_DATASET", "VIIRS_NOAA20_NRT")
|
Add NASA FIRMS active-fire ingest + /api/fires; API keys management page
Coherent merge of two coordinated features on the shared working tree:
FIRMS fire heatmap (backend, t_6e404c14):
- app/fire_sources.py: fetch FIRMS VIIRS area CSV (free MAP_KEY) -> NATS events.fire
- fires hypertable (TimescaleDB, 1-day chunks) with natural-key PK
(latitude, longitude, acq_time, satellite); idempotent ON CONFLICT DO NOTHING
- alembic/versions/002_fires.py; GET /api/fires?bbox=&since= (JSON only)
- POST /api/ingest/fires; ~15 min poll loop (FIRMS_INTERVAL=900) in ingester
- env-driven config (FIRMS_MAP_KEY/DATASET/BBOX/INTERVAL); docs/firms.md covers
the zero-cost GIBS VIIRS_SNPP_Thermal_Anomalies_375m_All tile alternative
- 18 tests (parser, mapping, idempotency, API contract) verified vs real
TimescaleDB+PostGIS (localhost/osint-dashboard-pg image)
API keys page (frontend, t_4433cff2):
- app/keystore.py: api_keys table (self-creating), FIRMS/GEMINI/TELEGRAM
registry with format validation, ****last4 masking, get_api_key()
- GET/POST/DELETE /api/keys (never returns full values); Keys tab in index.html
DB_NULL_POOL env switch in app/database.py enables a NullPool for tests /
short-lived processes that open a fresh event loop per unit.
2026-08-24 15:37:42 -04:00
|
|
|
# Area bounding box as "minlon,minlat,maxlon,maxlat". Default covers most of
|
|
|
|
|
# the inhabited globe; narrow it (e.g. CONUS "-125,24,-66,50") to shrink
|
|
|
|
|
# payloads and the Postgres write volume.
|
|
|
|
|
FIRMS_BBOX = os.getenv("FIRMS_BBOX", "-180,-60,180,75")
|
|
|
|
|
# Poll cadence in seconds. FIRMS NRT updates every ~5-10 min; 900 = 15 min.
|
|
|
|
|
FIRMS_INTERVAL = int(os.getenv("FIRMS_INTERVAL", "900"))
|
2026-08-24 21:32:55 -04:00
|
|
|
# Day range for the area-CSV request. FIRMS accepts [1..5] days of NRT
|
2026-08-24 22:07:51 -04:00
|
|
|
# detections; the API 400s when this parameter is omitted. Keep >=2: at
|
|
|
|
|
# some hours "1" returns zero detections due to NRT data latency.
|
|
|
|
|
FIRMS_DAYS = int(os.getenv("FIRMS_DAYS", "2"))
|
Add NASA FIRMS active-fire ingest + /api/fires; API keys management page
Coherent merge of two coordinated features on the shared working tree:
FIRMS fire heatmap (backend, t_6e404c14):
- app/fire_sources.py: fetch FIRMS VIIRS area CSV (free MAP_KEY) -> NATS events.fire
- fires hypertable (TimescaleDB, 1-day chunks) with natural-key PK
(latitude, longitude, acq_time, satellite); idempotent ON CONFLICT DO NOTHING
- alembic/versions/002_fires.py; GET /api/fires?bbox=&since= (JSON only)
- POST /api/ingest/fires; ~15 min poll loop (FIRMS_INTERVAL=900) in ingester
- env-driven config (FIRMS_MAP_KEY/DATASET/BBOX/INTERVAL); docs/firms.md covers
the zero-cost GIBS VIIRS_SNPP_Thermal_Anomalies_375m_All tile alternative
- 18 tests (parser, mapping, idempotency, API contract) verified vs real
TimescaleDB+PostGIS (localhost/osint-dashboard-pg image)
API keys page (frontend, t_4433cff2):
- app/keystore.py: api_keys table (self-creating), FIRMS/GEMINI/TELEGRAM
registry with format validation, ****last4 masking, get_api_key()
- GET/POST/DELETE /api/keys (never returns full values); Keys tab in index.html
DB_NULL_POOL env switch in app/database.py enables a NullPool for tests /
short-lived processes that open a fresh event loop per unit.
2026-08-24 15:37:42 -04:00
|
|
|
# Outbound HTTP timeout for the FIRMS CSV download.
|
|
|
|
|
FIRMS_TIMEOUT = float(os.getenv("FIRMS_TIMEOUT", "60"))
|
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
|
|
|
# Comma-separated FIRMS products to dual-write. S-NPP delivery ends 2026-11-01;
|
|
|
|
|
# default to NOAA-20 + NOAA-21 NRT. FIRMS_DATASET is still honored when
|
|
|
|
|
# FIRMS_DATASETS is unset (empty string means "use FIRMS_DATASET only").
|
|
|
|
|
_FIRMS_DATASETS_RAW = os.getenv("FIRMS_DATASETS", "VIIRS_NOAA20_NRT,VIIRS_NOAA21_NRT")
|
|
|
|
|
FIRMS_DATASETS = [d.strip() for d in _FIRMS_DATASETS_RAW.split(",") if d.strip()] or [FIRMS_DATASET]
|
|
|
|
|
|
|
|
|
|
# Identifying User-Agent for NWS / Amtraker / Nominatim (mandatory on some APIs).
|
|
|
|
|
OSINT_USER_AGENT = os.getenv(
|
|
|
|
|
"OSINT_USER_AGENT", "osint-dashboard/1.0 (self-hosted; lancewalters94@gmail.com)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# AISStream (server-side WebSocket only). Idle when unset.
|
|
|
|
|
AISSTREAM_API_KEY = os.getenv("AISSTREAM_API_KEY", "")
|
|
|
|
|
# Bounding box(es) as minlat,minlon,maxlat,maxlon — note lat/lon order (AISStream).
|
|
|
|
|
# Default: CONUS coasts + Great Lakes, not the world.
|
|
|
|
|
AISSTREAM_BBOX = os.getenv("AISSTREAM_BBOX", "24,-125,50,-66")
|
|
|
|
|
# Run the AIS worker inside the dashboard process (default on so vessels work
|
|
|
|
|
# without the ingest profile). Set 0 if the ingester owns the only connection.
|
|
|
|
|
AISSTREAM_IN_APP = os.getenv("AISSTREAM_IN_APP", "1").lower() in ("1", "true", "yes")
|
|
|
|
|
AISSTREAM_IN_INGEST = os.getenv("AISSTREAM_IN_INGEST", "0").lower() in ("1", "true", "yes")
|