# ShadowBroker Backend — Environment Variables
# Copy this file to .env and fill in your keys:
#   cp .env.example .env

# ── Required Keys ──────────────────────────────────────────────
# Without these, the corresponding data layers will be empty.

OPENSKY_CLIENT_ID=        # https://opensky-network.org/ — free account, OAuth2 client ID
OPENSKY_CLIENT_SECRET=    # OAuth2 client secret from your OpenSky dashboard
AIS_API_KEY=              # https://aisstream.io/ — free tier WebSocket key

# ── Optional ───────────────────────────────────────────────────

# AISHub REST fallback. Used when stream.aisstream.io is unreachable
# (e.g. their cert expires or server goes offline). Free tier requires
# registration at https://www.aishub.net/api. Poll cadence defaults to
# 20 min to stay courteous; tunable via AISHUB_POLL_INTERVAL_MINUTES.
# AISHUB_USERNAME=
# AISHUB_POLL_INTERVAL_MINUTES=20

# Override allowed CORS origins (comma-separated). Defaults to localhost + LAN auto-detect.
# CORS_ORIGINS=http://192.168.1.50:3000,https://my-domain.com

# Admin key — protects sensitive endpoints (API key management, system update).
# If unset, loopback/localhost requests still work for local single-host dev.
# Remote/non-loopback admin access requires ADMIN_KEY, or ALLOW_INSECURE_ADMIN=true in debug-only setups.
# Set this in production and enter the same key in Settings → Admin Key.
# ADMIN_KEY=your-secret-admin-key-here

# Allow insecure admin access without ADMIN_KEY (local dev only, beyond loopback).
# Requires MESH_DEBUG_MODE=true; do not enable this for ordinary use.
# ALLOW_INSECURE_ADMIN=false

# Per-install operator handle. Round 7a: every outbound third-party API
# call (Wikipedia, Wikidata, Nominatim, GDELT, OpenMHz, Broadcastify,
# weather.gov, NUFORC, etc.) includes this handle in the User-Agent so
# upstreams can rate-limit / contact the specific install instead of
# treating every Shadowbroker user as one entity.
#
# Default empty -> a stable pseudonymous handle (e.g. "operator-7f3a92") is
# auto-generated on first run and persisted to backend/data/operator_handle.json.
# Operators who want a meaningful handle (real name, org, GitHub login) can
# set it here. Special characters are sanitized to dashes.
# OPERATOR_HANDLE=

# Default outbound User-Agent for all third-party HTTP fetchers. Operators
# who run a public relay and want a completely custom UA can set this; it
# bypasses the per-operator helper entirely. Most installs should leave it
# unset and use OPERATOR_HANDLE instead.
# SHADOWBROKER_USER_AGENT=

# Nominatim-specific User-Agent override (OSM usage policy). Leave unset to
# use the per-install handle (default) — set only if you have a registered
# Nominatim relay identity.
# NOMINATIM_USER_AGENT=

# ── Third-party fetcher opt-ins ────────────────────────────────
# These data sources phone home to politically/commercially sensitive
# upstreams. Disabled by default; set to "true" only if the operator
# explicitly wants the node's IP to contact these services.
#
# CrowdThreat — backend.crowdthreat.world (paid threat-intel aggregator).
# CROWDTHREAT_ENABLED=false
#
# EUvsDisinfo FIMI — euvsdisinfo.eu (EU disinformation tracker).
# FIMI_ENABLED=false
#
# Polymarket + Kalshi — US political/election prediction markets.
# PREDICTION_MARKETS_ENABLED=false
#
# Finnhub fallback / yfinance — financial market data.
# Set FINNHUB_API_KEY to enable Finnhub, or set FINANCIAL_ENABLED=true to allow
# the unauthenticated yfinance fallback to call Yahoo Finance.
# FINANCIAL_ENABLED=false
#
# NUFORC UAP sightings — huggingface.co dataset download.
# NUFORC_ENABLED=false
#
# News RSS aggregator — defaults ON. Set to "false" to disable all
# configured news feeds (kill switch for the news layer).
# NEWS_ENABLED=true

# LTA Singapore traffic cameras — leave blank to skip this data source.
# LTA_ACCOUNT_KEY=

# NASA FIRMS country-scoped fire data — enriches global CSV with conflict-zone hotspots.
# Free MAP_KEY from https://firms.modaps.eosdis.nasa.gov/map/#d:24hrs;@0.0,0.0,3.0z
# FIRMS_MAP_KEY=

# Ukraine air raid alerts from alerts.in.ua — free token from https://alerts.in.ua/
# ALERTS_IN_UA_TOKEN=

# Optional NUFORC UAP sighting map enrichment via Mapbox Tilequery.
# Leave blank to skip this optional enrichment.
# NUFORC_MAPBOX_TOKEN=

# Google Earth Engine service account for VIIRS change detection (optional).
# Download JSON key from https://console.cloud.google.com/iam-admin/serviceaccounts
# pip install earthengine-api
# GEE_SERVICE_ACCOUNT_KEY=

# ── Meshtastic MQTT Bridge ─────────────────────────────────────
# Disabled by default to respect the public Meshtastic broker.
# When enabled, subscribes to US region only. Add more regions via MESH_MQTT_EXTRA_ROOTS.
# MESH_MQTT_ENABLED=false
# MESH_MQTT_EXTRA_ROOTS=EU_868,ANZ    # comma-separated additional region roots
# MESH_MQTT_INCLUDE_DEFAULT_ROOTS=true
# MESH_MQTT_BROKER=mqtt.meshtastic.org
# MESH_MQTT_PORT=1883
# Leave user/pass blank for the public Meshtastic broker default.
# MESH_MQTT_USER=
# MESH_MQTT_PASS=

# Optional Meshtastic node ID (e.g. "!abcd1234"). When set, included in the
# User-Agent sent to meshtastic.liamcottle.net so the upstream service operator
# can identify per-install traffic instead of aggregated "ShadowBroker" hits.
# Leave blank to send a generic UA. If you set MESHTASTIC_OPERATOR_CALLSIGN,
# it is included in outbound headers to meshtastic.org by default so they
# can rate-limit per-operator. Set MESHTASTIC_SEND_CALLSIGN_HEADER=false to
# suppress the callsign while still using it locally (e.g. for APRS).
# MESHTASTIC_OPERATOR_CALLSIGN=
# MESHTASTIC_SEND_CALLSIGN_HEADER=true
# MESH_MQTT_PSK=                       # hex-encoded, empty = default LongFast key

# ── Mesh / Reticulum (RNS) ─────────────────────────────────────
# Full-node / participant-node posture for public Infonet sync.
# MESH_NODE_MODE=participant      # participant | relay | perimeter
# Legacy compatibility sunset toggles. Default posture is to block these.
# Legacy 16-hex node-id binding no longer has a boolean escape hatch; use a
# dated migration override only when you intentionally need older peers during
# migration before the hard removal target in v0.10.0 / 2026-06-01.
# MESH_BLOCK_LEGACY_NODE_ID_COMPAT=true
# MESH_ALLOW_LEGACY_NODE_ID_COMPAT_UNTIL=2026-05-15
# MESH_BLOCK_LEGACY_AGENT_ID_LOOKUP=true
# Temporary DM invite migration escape hatch. Default posture blocks importing
# legacy/compat v1/v2 DM invites; use a dated override only while retiring
# older exports and ask senders to re-export a current signed invite.
# MESH_ALLOW_COMPAT_DM_INVITE_IMPORT_UNTIL=2026-05-15
# Temporary legacy GET DM poll/count escape hatch. Default posture requires the
# signed mailbox-claim POST APIs; only use this dated override while retiring
# older clients that still call GET poll/count directly.
# MESH_ALLOW_LEGACY_DM_GET_UNTIL=2026-05-15
# Temporary raw dm1 compose/decrypt escape hatch. Default posture expects MLS
# DM bootstrap on supported peers; only use this dated override while retiring
# older clients that still need the raw dm1 helper path.
# MESH_ALLOW_LEGACY_DM1_UNTIL=2026-05-15
# Temporary legacy dm_message signature escape hatch. Default posture requires
# the full modern signed payload; only enable this with a dated migration
# override while older senders are being retired.
# MESH_ALLOW_LEGACY_DM_SIGNATURE_COMPAT_UNTIL=2026-05-15
# Rotate voter-blinding salts so new reputation events stop reusing one
# forever-stable blinded ID. Keep grace >= rotation cadence so older votes
# remain matchable while they age out of the ledger.
# MESH_VOTER_BLIND_SALT_ROTATE_DAYS=30
# MESH_VOTER_BLIND_SALT_GRACE_DAYS=30
# Deprecated legacy env vars kept only for backward config compatibility.
# Ordinary shipped gate flows keep MLS decrypt local; service-side decrypt is
# reserved for explicit recovery reads.
# MESH_GATE_BACKEND_DECRYPT_COMPAT=false
# MESH_GATE_BACKEND_DECRYPT_COMPAT_ACKNOWLEDGE=false
# Deprecated legacy env vars kept only for backward config compatibility.
# Ordinary shipped gate flows keep plaintext compose/post local and only submit
# encrypted envelopes to the backend for sign/post.
# MESH_GATE_BACKEND_PLAINTEXT_COMPAT=false
# MESH_GATE_BACKEND_PLAINTEXT_COMPAT_ACKNOWLEDGE=false
# Legacy runtime switches for recovery envelopes. Per-gate envelope_policy is
# the source of truth; leave these at the default unless testing old behavior.
# MESH_GATE_RECOVERY_ENVELOPE_ENABLE=true
# MESH_GATE_RECOVERY_ENVELOPE_ENABLE_ACKNOWLEDGE=true
# Optional operator-only recovery tradeoff. Leave off for the default posture:
# ordinary gate reads keep plaintext local/in-memory unless you explicitly use
# the recovery-envelope path.
# MESH_GATE_PLAINTEXT_PERSIST=false
# MESH_GATE_PLAINTEXT_PERSIST_ACKNOWLEDGE=false
# Legacy Phase-1 gate envelope fallback is now explicit and time-bounded per
# gate. This only controls the default expiry window when you deliberately
# re-enable that migration path for older stored envelopes.
# MESH_GATE_LEGACY_ENVELOPE_FALLBACK_MAX_DAYS=30
# Feature-flagged multiplexed gate session stream. Stream-first room ownership
# is implemented; keep off until you want that rollout enabled in your env.
# MESH_GATE_SESSION_STREAM_ENABLED=false
# MESH_GATE_SESSION_STREAM_HEARTBEAT_S=20
# MESH_GATE_SESSION_STREAM_BATCH_MS=1500
# MESH_GATE_SESSION_STREAM_MAX_GATES=16
# MESH_BOOTSTRAP_DISABLED=false
# MESH_BOOTSTRAP_MANIFEST_PATH=data/bootstrap_peers.json
# MESH_BOOTSTRAP_SIGNER_PUBLIC_KEY=
# Infonet/Wormhole fails closed to onion/RNS by default. Only enable clearnet
# sync for local relay development or an explicitly public testnet.
# MESH_INFONET_ALLOW_CLEARNET_SYNC=false
# MESH_BOOTSTRAP_SEED_PEERS=http://gqpbunqbgtkcqilvclm3xrkt3zowjyl3s62kkktvojgvxzizamvbrqid.onion:8000
# Add comma-separated http://*.onion peers as more private seed/relay nodes come online.
# MESH_DEFAULT_SYNC_PEERS=         # legacy alias; prefer MESH_BOOTSTRAP_SEED_PEERS
# MESH_RELAY_PEERS=               # comma-separated operator-trusted sync/push peers (empty by default)
# MESH_PEER_PUSH_SECRET=          # REQUIRED when relay/RNS peers are configured (min 16 chars, generate with: python -c "import secrets; print(secrets.token_urlsafe(32))")
# MESH_SYNC_INTERVAL_S=300
# MESH_SYNC_FAILURE_BACKOFF_S=60
#
# Enable Reticulum bridge for Infonet event gossip.
# MESH_RNS_ENABLED=false
# MESH_RNS_APP_NAME=shadowbroker
# MESH_RNS_ASPECT=infonet
# MESH_RNS_IDENTITY_PATH=
# MESH_RNS_PEERS=  # comma-separated destination hashes
# MESH_RNS_DANDELION_HOPS=2
# MESH_RNS_DANDELION_DELAY_MS=400
# MESH_RNS_CHURN_INTERVAL_S=300
# MESH_RNS_MAX_PEERS=32
# MESH_RNS_MAX_PAYLOAD=8192
# MESH_RNS_PEER_BUCKET_PREFIX=4
# MESH_RNS_MAX_PEERS_PER_BUCKET=4
# MESH_RNS_PEER_FAIL_THRESHOLD=3
# MESH_RNS_PEER_COOLDOWN_S=300
# MESH_RNS_SHARD_ENABLED=false
# MESH_RNS_SHARD_DATA_SHARDS=3
# MESH_RNS_SHARD_PARITY_SHARDS=1
# MESH_RNS_SHARD_TTL_S=30
# MESH_RNS_FEC_CODEC=xor
# MESH_RNS_BATCH_MS=200
# MESH_RNS_COVER_INTERVAL_S=0
# MESH_RNS_COVER_SIZE=64
# MESH_RNS_IBF_WINDOW=256
# MESH_RNS_IBF_TABLE_SIZE=64
# MESH_RNS_IBF_MINHASH_SIZE=16
# MESH_RNS_IBF_MINHASH_THRESHOLD=0.25
# MESH_RNS_IBF_WINDOW_JITTER=32
# MESH_RNS_IBF_INTERVAL_S=120
# MESH_RNS_IBF_SYNC_PEERS=3
# MESH_RNS_IBF_QUORUM_TIMEOUT_S=6
# MESH_RNS_IBF_MAX_REQUEST_IDS=64
# MESH_RNS_IBF_MAX_EVENTS=64
# MESH_RNS_SESSION_ROTATE_S=0
# MESH_RNS_IBF_FAIL_THRESHOLD=3
# MESH_RNS_IBF_COOLDOWN_S=120
# MESH_VERIFY_INTERVAL_S=600
# MESH_VERIFY_SIGNATURES=false

# ── Secure Storage (non-Windows) ───────────────────────────────
# Required on Linux/Docker to protect Wormhole key material at rest.
# Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))"
# Also supports Docker secrets via MESH_SECURE_STORAGE_SECRET_FILE.
# MESH_SECURE_STORAGE_SECRET=
#
# To rotate the storage secret, stop the backend and run:
#   1. Dry-run first (validates without writing):
#      MESH_OLD_STORAGE_SECRET=<current> MESH_NEW_STORAGE_SECRET=<new> \
#        python -m scripts.rotate_secure_storage_secret --dry-run
#   2. Rotate (creates .bak backups, then rewraps envelopes):
#      MESH_OLD_STORAGE_SECRET=<current> MESH_NEW_STORAGE_SECRET=<new> \
#        python -m scripts.rotate_secure_storage_secret
#   3. Update MESH_SECURE_STORAGE_SECRET to the new value and restart.
#
# If rotation is interrupted, .bak files preserve the old envelopes.
# To repair corrupted secure-json payloads (not key envelopes), use:
#   python -m scripts.repair_wormhole_secure_storage

# ── Mesh DM Relay ──────────────────────────────────────────────
# MESH_DM_TOKEN_PEPPER=change-me
# Keep DM relay metadata retention explicit and bounded.
# MESH_DM_KEY_TTL_DAYS=30
# MESH_DM_PREKEY_LOOKUP_ALIAS_TTL_DAYS=14
# MESH_DM_WITNESS_TTL_DAYS=14
# MESH_DM_BINDING_TTL_DAYS=3
# Optional operational bridge for externally sourced root witnesses / transparency.
# Relative paths resolve from the backend directory.
# MESH_DM_ROOT_EXTERNAL_WITNESS_IMPORT_PATH=data/root_witness_import.json
# Local single-host dev example after bootstrapping an external witness locally:
# MESH_DM_ROOT_EXTERNAL_WITNESS_IMPORT_PATH=../ops/root_witness_receipt_import.json
# Optional URI bridge for externally retrieved root witness packages.
# MESH_DM_ROOT_EXTERNAL_WITNESS_IMPORT_URI=file:///absolute/path/root_witness_import.json
# Maximum acceptable age for external witness packages before strong DM trust fails closed.
# MESH_DM_ROOT_EXTERNAL_WITNESS_MAX_AGE_S=3600
# Warning threshold for external witness packages before fail-closed max age.
# MESH_DM_ROOT_EXTERNAL_WITNESS_WARN_AGE_S=2700
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_EXPORT_PATH=data/root_transparency_ledger.json
# Local single-host dev example after publishing the transparency ledger locally:
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_EXPORT_PATH=../ops/root_transparency_ledger.json
# Optional URI used to read back and verify a published transparency ledger.
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_READBACK_URI=file:///absolute/path/root_transparency_ledger.json
# Local single-host dev readback example:
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_READBACK_URI=../ops/root_transparency_ledger.json
# Maximum acceptable age for external transparency ledgers before strong DM trust fails closed.
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_MAX_AGE_S=3600
# Warning threshold for external transparency ledgers before fail-closed max age.
# MESH_DM_ROOT_TRANSPARENCY_LEDGER_WARN_AGE_S=2700

# ── Self Update ────────────────────────────────────────────────
# MESH_UPDATE_SHA256=

# ── Wormhole (Local Agent) ─────────────────────────────────────
# WORMHOLE_HOST=127.0.0.1
# WORMHOLE_PORT=8787
# WORMHOLE_RELOAD=false
# WORMHOLE_TRANSPORT=direct
# WORMHOLE_SOCKS_PROXY=127.0.0.1:9050
# WORMHOLE_SOCKS_DNS=true
# Optional override for the loaded Rust privacy-core shared library. Leave
# unset for the default repo search order. When you override this, verify the
# authenticated wormhole status surfaces show the expected version, absolute
# library path, and SHA-256 for the loaded artifact before making stronger
# privacy claims about the deployment.
# PRIVACY_CORE_LIB=
# Minimum privacy-core version accepted when hidden/private carriers are
# enabled. Private-lane startup fails closed if the loaded artifact is
# missing, reports no parseable version, or falls below this minimum.
# PRIVACY_CORE_MIN_VERSION=0.1.0
# Comma-separated SHA-256 allowlist for the exact privacy-core artifact(s)
# your deployment is allowed to load. Required for Arti/RNS private-lane
# startup. Generate with:
#   PowerShell: Get-FileHash .\privacy-core\target\release\privacy_core.dll -Algorithm SHA256
#   macOS/Linux: sha256sum ./privacy-core/target/release/libprivacy_core.so
# PRIVACY_CORE_ALLOWED_SHA256=
# Optional structured release attestation artifact for the Sprint 8 release gate.
# Relative paths resolve from the backend directory. When set explicitly, a
# missing or unreadable file fails the DM relay security-suite criterion closed.
# CI/release tooling can generate this automatically via:
#   uv run python scripts/release_helper.py write-attestation ...
# MESH_RELEASE_ATTESTATION_PATH=data/release_attestation.json
# Operator-only Sprint 8 release attestation. Set this only when the DM relay
# security suite has been run and passed for the current release candidate.
# File-based release attestation takes precedence when present.
# MESH_RELEASE_DM_RELAY_SECURITY_SUITE_GREEN=false

# ── OpenClaw Agent ─────────────────────────────────────────────
# HMAC shared secret for remote OpenClaw agent authentication.
# Auto-generated via the Connect OpenClaw modal — do not set manually.
# OPENCLAW_HMAC_SECRET=
# Access tier: "restricted" (read-only) or "full" (read+write+inject)
# OPENCLAW_ACCESS_TIER=restricted

# ── SAR (Synthetic Aperture Radar) Layer ───────────────────────
# Mode A — Free catalog metadata from Alaska Satellite Facility (ASF Search).
# No account, no downloads.  Default-on.  Set to false to disable entirely.
# MESH_SAR_CATALOG_ENABLED=true
#
# Mode B — Free pre-processed ground-change anomalies (deformation, flood,
# damage assessments) from NASA OPERA, Copernicus EGMS, GFM, EMS, UNOSAT.
# Two-step opt-in: BOTH of the following must be set together.
#   1. MESH_SAR_PRODUCTS_FETCH=allow
#   2. MESH_SAR_PRODUCTS_FETCH_ACKNOWLEDGE=true
# Either flag alone keeps Mode B disabled.  You can also enable this from
# the Settings → SAR panel inside the app.
# MESH_SAR_PRODUCTS_FETCH=block
# MESH_SAR_PRODUCTS_FETCH_ACKNOWLEDGE=false
#
# NASA Earthdata Login (free, ~1 minute signup) — required for OPERA products.
#   Sign up: https://urs.earthdata.nasa.gov/users/new
#   Generate token: https://urs.earthdata.nasa.gov/profile  →  "Generate Token"
# MESH_SAR_EARTHDATA_USER=
# MESH_SAR_EARTHDATA_TOKEN=
#
# Copernicus Data Space (free, ~1 minute signup) — required for EGMS / EMS.
#   Sign up: https://dataspace.copernicus.eu/
# MESH_SAR_COPERNICUS_USER=
# MESH_SAR_COPERNICUS_TOKEN=
#
# Allow OpenClaw agents to read and act on the SAR layer (default true).
# MESH_SAR_OPENCLAW_ENABLED=true
#
# Require private-tier transport (Tor / RNS) before signing and broadcasting
# SAR anomalies to the mesh.  Default true — disable only for testnet/local use.
# MESH_SAR_REQUIRE_PRIVATE_TIER=true
