mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-10 05:12:23 +00:00
Main now ships an Iceland pack that loads the full live Vegagerðin list (about 500 views) with its CC BY 4.0 credit. The World catalog's 166 Vegagerðin rows point at the same frames under different ids, so every one of those cameras would be drawn twice. Drop them from the World export and its loader policy; World is now 2,230 rows and the default catalog limit 7,350 (4,000 shared slots plus 3,350 reserved). Register Data attribution entries for Ottawa, Québec, TripCheck and MLIT, as every other CCTV pack has. MLIT's terms ask for the source in the form "出典:国土交通省 川の防災情報" with the page link, and ODOT asks for "Camera courtesy of ODOT"; TripCheck partner cameras keep their owner's name in the label. The Houston guard test from main checked the old 4,000 default ceiling; check the 4,000 shared slots instead, like the other pack tests.
427 lines
19 KiB
Bash
Executable File
427 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SOURCE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
ROOT_DIR="$(cd "${GEV_PROJECT_ROOT:-$SOURCE_ROOT}" && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
PORT="${PORT:-4173}"
|
|
# Local-only by default: the dev server brokers configured API keys, so it
|
|
# should not be reachable from the network unless explicitly requested.
|
|
# Set HOST=0.0.0.0 to opt in to LAN exposure (a warning is printed).
|
|
HOST="${HOST:-localhost}"
|
|
# CCTV source packs (all keyless): Austin (~815 live upstream), Caltrans
|
|
# districts 4,7,11,3 = SF/LA/San Diego/Sacramento (~1,860 live upstream),
|
|
# TfL London JamCams (~870 live upstream), Ontario 511 (~944 live upstream,
|
|
# including Kitchener-area highways), and Fintraffic Finland weathercams
|
|
# (~2,260 live presets). Caps keep the densest cores per pack; override
|
|
# per-run for lighter/heavier loads. Kill switches: CCTV_CALTRANS_DISTRICTS='',
|
|
# CCTV_TFL_ENABLED=0, CCTV_ONTARIO_ENABLED=0, CCTV_FINTRAFFIC_ENABLED=0 and
|
|
# CCTV_CALGARY_ENABLED=0 (Open Calgary, ~215 live upstream).
|
|
CCTV_AUSTIN_MAX_SOURCES="${CCTV_AUSTIN_MAX_SOURCES:-250}"
|
|
# Use `-` not `:-` so an explicit empty string (the documented kill switch)
|
|
# is preserved rather than replaced by the default. Still set-u-safe when unset.
|
|
CCTV_CALTRANS_DISTRICTS="${CCTV_CALTRANS_DISTRICTS-4,7,11,3}"
|
|
CCTV_CALTRANS_MAX_SOURCES="${CCTV_CALTRANS_MAX_SOURCES:-300}"
|
|
CCTV_TFL_ENABLED="${CCTV_TFL_ENABLED:-1}"
|
|
CCTV_TFL_MAX_SOURCES="${CCTV_TFL_MAX_SOURCES:-250}"
|
|
CCTV_ONTARIO_ENABLED="${CCTV_ONTARIO_ENABLED:-1}"
|
|
CCTV_ONTARIO_MAX_SOURCES="${CCTV_ONTARIO_MAX_SOURCES:-1000}"
|
|
CCTV_FINTRAFFIC_ENABLED="${CCTV_FINTRAFFIC_ENABLED:-1}"
|
|
CCTV_FINTRAFFIC_MAX_SOURCES="${CCTV_FINTRAFFIC_MAX_SOURCES:-300}"
|
|
CCTV_DRIVEBC_ENABLED="${CCTV_DRIVEBC_ENABLED:-1}"
|
|
CCTV_DRIVEBC_MAX_SOURCES="${CCTV_DRIVEBC_MAX_SOURCES:-250}"
|
|
CCTV_TXDOT_ENABLED="${CCTV_TXDOT_ENABLED:-1}"
|
|
# Use `-` so an explicit empty string (the documented kill switch) is preserved.
|
|
CCTV_TXDOT_DISTRICTS="${CCTV_TXDOT_DISTRICTS-AUS,SAT}"
|
|
CCTV_TXDOT_MAX_SOURCES="${CCTV_TXDOT_MAX_SOURCES:-500}"
|
|
CCTV_TALLINN_ENABLED="${CCTV_TALLINN_ENABLED:-1}"
|
|
CCTV_TALLINN_MAX_SOURCES="${CCTV_TALLINN_MAX_SOURCES:-255}"
|
|
CCTV_TARKTEE_ENABLED="${CCTV_TARKTEE_ENABLED:-1}"
|
|
CCTV_TARKTEE_MAX_SOURCES="${CCTV_TARKTEE_MAX_SOURCES:-179}"
|
|
CCTV_WARENDORF_ENABLED="${CCTV_WARENDORF_ENABLED:-1}"
|
|
CCTV_NSW_ENABLED="${CCTV_NSW_ENABLED:-1}"
|
|
CCTV_NSW_MAX_SOURCES="${CCTV_NSW_MAX_SOURCES:-250}"
|
|
CCTV_CALGARY_ENABLED="${CCTV_CALGARY_ENABLED:-1}"
|
|
CCTV_CALGARY_MAX_SOURCES="${CCTV_CALGARY_MAX_SOURCES:-220}"
|
|
CCTV_MAX_SOURCES="${CCTV_MAX_SOURCES:-7350}"
|
|
|
|
# Capture which provider credentials genuinely came from the parent shell
|
|
# before this launcher resolves dotenv and Keychain fallbacks. Only names are
|
|
# passed to Vite; values never enter the provenance marker. This lets Provider
|
|
# Settings keep an exported credential read-only even when .env happens to hold
|
|
# the same value, without misclassifying values that dev-fresh loaded from .env.
|
|
KEY_SETUP_EXTERNAL_KEYS=()
|
|
[[ -n "${GOOGLE_MAPS_API_KEY:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(GOOGLE_MAPS_API_KEY)
|
|
[[ -n "${GOOGLE_MAPS_SERVER_API_KEY:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(GOOGLE_MAPS_SERVER_API_KEY)
|
|
[[ -n "${CESIUM_ION_TOKEN:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(CESIUM_ION_TOKEN)
|
|
[[ -n "${OPENAI_API_KEY:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(OPENAI_API_KEY)
|
|
[[ -n "${AISSTREAM_API_KEY:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(AISSTREAM_API_KEY)
|
|
[[ -n "${FIRMS_MAP_KEY:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(FIRMS_MAP_KEY)
|
|
[[ -n "${TOMTOM_API_KEY:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(TOMTOM_API_KEY)
|
|
[[ -n "${OPENSKY_CLIENT_ID:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(OPENSKY_CLIENT_ID)
|
|
[[ -n "${OPENSKY_CLIENT_SECRET:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(OPENSKY_CLIENT_SECRET)
|
|
[[ -n "${LL2_API_TOKEN:-}" ]] && KEY_SETUP_EXTERNAL_KEYS+=(LL2_API_TOKEN)
|
|
KEY_SETUP_EXTERNAL_KEYS_CSV="$(IFS=,; printf '%s' "${KEY_SETUP_EXTERNAL_KEYS[*]:-}")"
|
|
|
|
if command -v npm >/dev/null 2>&1; then
|
|
DEV_COMMAND=(npm run dev --)
|
|
elif command -v pnpm >/dev/null 2>&1; then
|
|
DEV_COMMAND=(pnpm run dev)
|
|
else
|
|
echo "error: neither npm nor pnpm found"
|
|
exit 1
|
|
fi
|
|
|
|
read_dotenv_value() {
|
|
local variable_name="$1"
|
|
if ! command -v node >/dev/null 2>&1; then
|
|
echo "warning: node not found; cannot parse dotenv files" >&2
|
|
return
|
|
fi
|
|
node "$SOURCE_ROOT/scripts/read-dotenv-value.mjs" "${variable_name}"
|
|
}
|
|
|
|
# Vite loads .env for browser build-time configuration, but this launcher needs
|
|
# the Maps key before Vite starts. Preserve a shell-provided value; otherwise
|
|
# read Vite's project-local dotenv ladder without executing it as shell code.
|
|
GOOGLE_MAPS_API_KEY_ENV="${GOOGLE_MAPS_API_KEY:-}"
|
|
GOOGLE_MAPS_API_KEY_ENV_SOURCE="env"
|
|
if [[ -z "${GOOGLE_MAPS_API_KEY_ENV}" ]]; then
|
|
GOOGLE_MAPS_API_KEY_ENV="$(read_dotenv_value "GOOGLE_MAPS_API_KEY")"
|
|
GOOGLE_MAPS_API_KEY_ENV_SOURCE="dotenv"
|
|
fi
|
|
GOOGLE_MAPS_API_KEY_KEYCHAIN=""
|
|
GOOGLE_MAPS_API_KEY_SOURCE=""
|
|
if command -v security >/dev/null 2>&1; then
|
|
for acct in "api-key" "default" "key"; do
|
|
GOOGLE_MAPS_API_KEY_KEYCHAIN="$(security find-generic-password -s "google-maps-api" -a "${acct}" -w 2>/dev/null || true)"
|
|
if [[ -n "${GOOGLE_MAPS_API_KEY_KEYCHAIN}" ]]; then
|
|
GOOGLE_MAPS_API_KEY_SOURCE="keychain:${acct}"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -n "${GOOGLE_MAPS_API_KEY_ENV}" ]]; then
|
|
GOOGLE_MAPS_API_KEY="${GOOGLE_MAPS_API_KEY_ENV}"
|
|
GOOGLE_MAPS_API_KEY_SOURCE="${GOOGLE_MAPS_API_KEY_ENV_SOURCE}"
|
|
elif [[ -n "${GOOGLE_MAPS_API_KEY_KEYCHAIN}" ]]; then
|
|
GOOGLE_MAPS_API_KEY="${GOOGLE_MAPS_API_KEY_KEYCHAIN}"
|
|
else
|
|
GOOGLE_MAPS_API_KEY=""
|
|
fi
|
|
if [[ -z "${GOOGLE_MAPS_API_KEY}" ]]; then
|
|
GOOGLE_MAPS_API_KEY_SOURCE="not configured"
|
|
fi
|
|
|
|
read_keychain_secret() {
|
|
local service="$1"
|
|
local account="$2"
|
|
security find-generic-password -s "$service" -a "$account" -w 2>/dev/null || true
|
|
}
|
|
|
|
load_opensky_oauth_from_file() {
|
|
local file_path="$1"
|
|
if [[ -z "${file_path}" || ! -f "${file_path}" ]]; then
|
|
return
|
|
fi
|
|
if ! command -v node >/dev/null 2>&1; then
|
|
echo "warning: node not found; cannot parse OPENSKY_CREDENTIALS_FILE"
|
|
return
|
|
fi
|
|
|
|
local parsed
|
|
parsed="$(node -e "const fs=require('fs');const p=process.argv[1];try{const raw=JSON.parse(fs.readFileSync(p,'utf8'));const id=String(raw.clientId??raw.client_id??'').trim();const secret=String(raw.clientSecret??raw.client_secret??'').trim();process.stdout.write(id+'\\t'+secret);}catch{process.exit(1)}" -- "${file_path}" 2>/dev/null || true)"
|
|
if [[ "${parsed}" != *$'\t'* ]]; then
|
|
return
|
|
fi
|
|
local parsed_client_id="${parsed%%$'\t'*}"
|
|
local parsed_client_secret="${parsed#*$'\t'}"
|
|
|
|
if [[ -z "${OPENSKY_CLIENT_ID}" && -n "${parsed_client_id}" ]]; then
|
|
OPENSKY_CLIENT_ID="${parsed_client_id}"
|
|
fi
|
|
if [[ -z "${OPENSKY_CLIENT_SECRET}" && -n "${parsed_client_secret}" ]]; then
|
|
OPENSKY_CLIENT_SECRET="${parsed_client_secret}"
|
|
fi
|
|
}
|
|
|
|
resolve_opensky_credentials() {
|
|
OPENSKY_AUTH_MODE="${OPENSKY_AUTH_MODE:-$(read_dotenv_value "OPENSKY_AUTH_MODE")}"
|
|
OPENSKY_AUTH_MODE="$(printf '%s' "${OPENSKY_AUTH_MODE:-oauth}" | tr '[:upper:]' '[:lower:]')"
|
|
OPENSKY_CREDENTIALS_FILE="${OPENSKY_CREDENTIALS_FILE:-$(read_dotenv_value "OPENSKY_CREDENTIALS_FILE")}"
|
|
case "${OPENSKY_AUTH_MODE}" in
|
|
basic|oauth|auto|anon) ;;
|
|
*)
|
|
echo "warning: invalid OPENSKY_AUTH_MODE='${OPENSKY_AUTH_MODE}', defaulting to 'oauth'"
|
|
OPENSKY_AUTH_MODE="oauth"
|
|
;;
|
|
esac
|
|
|
|
# Explicit env wins, then .env, then the credentials file / Keychain below.
|
|
OPENSKY_CLIENT_ID="${OPENSKY_CLIENT_ID:-$(read_dotenv_value "OPENSKY_CLIENT_ID")}"
|
|
OPENSKY_CLIENT_SECRET="${OPENSKY_CLIENT_SECRET:-$(read_dotenv_value "OPENSKY_CLIENT_SECRET")}"
|
|
OPENSKY_USERNAME="${OPENSKY_USERNAME:-$(read_dotenv_value "OPENSKY_USERNAME")}"
|
|
OPENSKY_PASSWORD="${OPENSKY_PASSWORD:-$(read_dotenv_value "OPENSKY_PASSWORD")}"
|
|
|
|
if [[ "${OPENSKY_AUTH_MODE}" == "basic" || "${OPENSKY_AUTH_MODE}" == "auto" ]] && [[ -z "${OPENSKY_USERNAME}" ]]; then
|
|
for svc in "opensky-network" "opensky"; do
|
|
for acct in "username" "user" "login" "email" "default"; do
|
|
OPENSKY_USERNAME="$(read_keychain_secret "${svc}" "${acct}")"
|
|
if [[ -n "${OPENSKY_USERNAME}" ]]; then
|
|
break 2
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
if [[ "${OPENSKY_AUTH_MODE}" == "basic" || "${OPENSKY_AUTH_MODE}" == "auto" ]] && [[ -z "${OPENSKY_PASSWORD}" ]]; then
|
|
for svc in "opensky-network" "opensky"; do
|
|
for acct in "password" "pass" "token" "secret" "default"; do
|
|
OPENSKY_PASSWORD="$(read_keychain_secret "${svc}" "${acct}")"
|
|
if [[ -n "${OPENSKY_PASSWORD}" ]]; then
|
|
break 2
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
if [[ "${OPENSKY_AUTH_MODE}" == "oauth" || "${OPENSKY_AUTH_MODE}" == "auto" ]] && [[ -n "${OPENSKY_CREDENTIALS_FILE}" ]]; then
|
|
load_opensky_oauth_from_file "${OPENSKY_CREDENTIALS_FILE}"
|
|
fi
|
|
if [[ "${OPENSKY_AUTH_MODE}" == "oauth" || "${OPENSKY_AUTH_MODE}" == "auto" ]] && [[ -z "${OPENSKY_CLIENT_ID}" ]]; then
|
|
for svc in "opensky-network" "opensky"; do
|
|
for acct in "client_id" "client-id" "client" "api-key"; do
|
|
OPENSKY_CLIENT_ID="$(read_keychain_secret "${svc}" "${acct}")"
|
|
if [[ -n "${OPENSKY_CLIENT_ID}" ]]; then
|
|
break 2
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
if [[ "${OPENSKY_AUTH_MODE}" == "oauth" || "${OPENSKY_AUTH_MODE}" == "auto" ]] && [[ -z "${OPENSKY_CLIENT_SECRET}" ]]; then
|
|
for svc in "opensky-network" "opensky"; do
|
|
for acct in "client_secret" "client-secret" "secret"; do
|
|
OPENSKY_CLIENT_SECRET="$(read_keychain_secret "${svc}" "${acct}")"
|
|
if [[ -n "${OPENSKY_CLIENT_SECRET}" ]]; then
|
|
break 2
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
|
|
case "${OPENSKY_AUTH_MODE}" in
|
|
basic)
|
|
OPENSKY_CLIENT_ID=""
|
|
OPENSKY_CLIENT_SECRET=""
|
|
;;
|
|
oauth)
|
|
OPENSKY_USERNAME=""
|
|
OPENSKY_PASSWORD=""
|
|
;;
|
|
anon)
|
|
OPENSKY_CLIENT_ID=""
|
|
OPENSKY_CLIENT_SECRET=""
|
|
OPENSKY_USERNAME=""
|
|
OPENSKY_PASSWORD=""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
resolve_opensky_credentials
|
|
|
|
# Optional keys: explicit env wins, followed by .env and Keychain fallback.
|
|
# Add to Keychain with e.g.:
|
|
# security add-generic-password -U -s "openai-api" -a "api-key" -w
|
|
OPENAI_API_KEY="${OPENAI_API_KEY:-$(read_dotenv_value "OPENAI_API_KEY")}"
|
|
AISSTREAM_API_KEY="${AISSTREAM_API_KEY:-$(read_dotenv_value "AISSTREAM_API_KEY")}"
|
|
CESIUM_ION_TOKEN="${CESIUM_ION_TOKEN:-$(read_dotenv_value "CESIUM_ION_TOKEN")}"
|
|
LL2_API_TOKEN="${LL2_API_TOKEN:-$(read_dotenv_value "LL2_API_TOKEN")}"
|
|
TOMTOM_API_KEY="${TOMTOM_API_KEY:-$(read_dotenv_value "TOMTOM_API_KEY")}"
|
|
FIRMS_MAP_KEY="${FIRMS_MAP_KEY:-$(read_dotenv_value "FIRMS_MAP_KEY")}"
|
|
OPENAI_API_KEY="${OPENAI_API_KEY:-$(read_keychain_secret "openai-api" "api-key")}"
|
|
AISSTREAM_API_KEY="${AISSTREAM_API_KEY:-$(read_keychain_secret "aisstream-api" "api-key")}"
|
|
CESIUM_ION_TOKEN="${CESIUM_ION_TOKEN:-$(read_keychain_secret "cesium-ion" "token")}"
|
|
TOMTOM_API_KEY="${TOMTOM_API_KEY:-$(read_keychain_secret "tomtom-api" "api-key")}"
|
|
FIRMS_MAP_KEY="${FIRMS_MAP_KEY:-$(read_keychain_secret "firms-map" "map-key")}"
|
|
|
|
if [[ ! -f "$SOURCE_ROOT/src/data/cctv.js" ]]; then
|
|
echo "error: expected CCTV layer file missing: src/data/cctv.js"
|
|
exit 1
|
|
fi
|
|
|
|
if ! grep -q "return createApplicationCatalog(" "$SOURCE_ROOT/src/standalone/catalog.js" || \
|
|
! grep -q "^[[:space:]]*createApplicationCctv({" "$SOURCE_ROOT/src/app/constructCatalog.js"; then
|
|
echo "error: CCTV layer not wired in src/standalone/catalog.js"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Stopping all existing God's Eye View dev servers..."
|
|
pkill -f "${ROOT_DIR}/node_modules/.bin/vite" >/dev/null 2>&1 || true
|
|
pkill -f "${ROOT_DIR}/node_modules/vite/bin/vite.js" >/dev/null 2>&1 || true
|
|
|
|
# Also clear the requested port in case it is held by a stale wrapper or a
|
|
# server started through a different package-manager command.
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
PIDS="$(lsof -tiTCP:${PORT} -sTCP:LISTEN 2>/dev/null || true)"
|
|
if [[ -n "${PIDS}" ]]; then
|
|
echo "${PIDS}" | xargs kill -9 >/dev/null 2>&1 || true
|
|
fi
|
|
fi
|
|
|
|
echo "Clearing Vite cache..."
|
|
rm -rf node_modules/.vite
|
|
|
|
echo "Starting fresh God's Eye View dev server..."
|
|
case "${HOST}" in
|
|
localhost|127.0.0.1|::1)
|
|
echo "Local-only mode: reachable at http://localhost:${PORT}/ (set HOST=0.0.0.0 for LAN)"
|
|
;;
|
|
*)
|
|
LAN_IP=""
|
|
if command -v ipconfig >/dev/null 2>&1; then
|
|
# macOS: first active interface wins
|
|
for iface in en0 en1; do
|
|
LAN_IP="$(ipconfig getifaddr "${iface}" 2>/dev/null || true)"
|
|
[[ -n "${LAN_IP}" ]] && break
|
|
done
|
|
elif command -v hostname >/dev/null 2>&1; then
|
|
# Linux: hostname -I lists addresses; take the first
|
|
LAN_IP="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
|
|
fi
|
|
echo ""
|
|
echo "!! =============================================================="
|
|
echo "!! WARNING: HOST=${HOST} — network-exposed mode."
|
|
echo "!! This dev server brokers your configured API keys (OpenAI,"
|
|
echo "!! OpenSky, AISStream, TomTom, FIRMS, LL2, Google) to ANYONE who can"
|
|
echo "!! reach it on the network. Use only on networks you trust."
|
|
echo "!! Consider the opt-in per-IP throttles GEV_RATELIMIT_OPENAI_PER_MIN"
|
|
echo "!! and GEV_RATELIMIT_GOOGLE_PER_MIN (see .env.example) — and note"
|
|
echo "!! they are NOT billing caps; set provider-side budget alerts too."
|
|
if [[ -n "${LAN_IP}" ]]; then
|
|
echo "!! LAN URL: http://${LAN_IP}:${PORT}/"
|
|
else
|
|
echo "!! LAN URL: http://<this-machine-ip>:${PORT}/"
|
|
fi
|
|
echo "!! =============================================================="
|
|
echo ""
|
|
echo "URL (this machine): http://localhost:${PORT}/"
|
|
;;
|
|
esac
|
|
echo "Google Maps key source: ${GOOGLE_MAPS_API_KEY_SOURCE}"
|
|
echo "Tip: after server starts, hard refresh browser (Cmd+Shift+R)."
|
|
echo "The CCTV panel starts collapsed; open it from its header, or in browser console:"
|
|
echo "localStorage.setItem('godsEyeView.v6.panelCollapsed.cctv-panel', '0'); location.reload();"
|
|
echo "OpenSky auth mode: ${OPENSKY_AUTH_MODE}"
|
|
if [[ -n "${OPENSKY_CREDENTIALS_FILE}" ]]; then
|
|
if [[ -f "${OPENSKY_CREDENTIALS_FILE}" ]]; then
|
|
echo "OpenSky credentials file: ${OPENSKY_CREDENTIALS_FILE}"
|
|
else
|
|
echo "OpenSky credentials file: ${OPENSKY_CREDENTIALS_FILE} (missing)"
|
|
fi
|
|
fi
|
|
case "${OPENSKY_AUTH_MODE}" in
|
|
basic)
|
|
if [[ -n "${OPENSKY_USERNAME}" && -n "${OPENSKY_PASSWORD}" ]]; then
|
|
echo "OpenSky basic auth: configured"
|
|
else
|
|
echo "OpenSky basic auth: missing credentials"
|
|
fi
|
|
;;
|
|
oauth)
|
|
if [[ -n "${OPENSKY_CLIENT_ID}" && -n "${OPENSKY_CLIENT_SECRET}" ]]; then
|
|
echo "OpenSky OAuth: configured"
|
|
else
|
|
echo "OpenSky OAuth: missing client credentials"
|
|
fi
|
|
;;
|
|
auto)
|
|
if [[ -n "${OPENSKY_CLIENT_ID}" && -n "${OPENSKY_CLIENT_SECRET}" ]]; then
|
|
echo "OpenSky auto auth: OAuth configured"
|
|
elif [[ -n "${OPENSKY_USERNAME}" && -n "${OPENSKY_PASSWORD}" ]]; then
|
|
echo "OpenSky auto auth: basic configured"
|
|
else
|
|
echo "OpenSky auto auth: no credentials found"
|
|
fi
|
|
;;
|
|
anon)
|
|
echo "OpenSky auth: disabled (anonymous mode)"
|
|
;;
|
|
esac
|
|
[[ -n "${OPENAI_API_KEY}" ]] && echo "OpenAI key (voice + HUD summary): configured" || echo "OpenAI key (voice + HUD summary): not set — GEV MIC off unless a Codex ChatGPT sign-in is selected in Provider Settings (experimental)"
|
|
[[ -n "${AISSTREAM_API_KEY}" ]] && echo "AISStream key (live vessels): configured" || echo "AISStream key (live vessels): not set — ships layer empty"
|
|
if [[ -n "${GOOGLE_MAPS_API_KEY}" ]]; then
|
|
echo "Startup map: Google Photorealistic 3D Tiles (direct)"
|
|
elif [[ -n "${CESIUM_ION_TOKEN}" ]]; then
|
|
echo "Startup map: Google Photorealistic 3D Tiles (Cesium ion)"
|
|
else
|
|
echo "Startup map: Esri World Imagery with keyless terrain (OpenStreetMap fallback)"
|
|
fi
|
|
[[ -n "${CESIUM_ION_TOKEN}" ]] && echo "Cesium ion token: configured — Google 3D, Bing, and world-terrain stacks available" || echo "Cesium ion token: not set"
|
|
[[ -n "${TOMTOM_API_KEY}" ]] && echo "TomTom key (live traffic flow): configured" || echo "TomTom key (live traffic flow): not set — simulated traffic"
|
|
[[ -n "${FIRMS_MAP_KEY}" ]] && echo "NASA FIRMS key (live fires): configured" || echo "NASA FIRMS key (live fires): not set — fires layer requires a key"
|
|
[[ -n "${LL2_API_TOKEN}" ]] && echo "Launch Library 2 token: configured" || echo "Launch Library 2 token: not set — using public access"
|
|
|
|
# Build the dev server environment explicitly. A value that resolved to
|
|
# nothing is left UNSET instead of being exported empty: Vite backfills its
|
|
# own .env values only for variables it finds undefined, so an empty export
|
|
# would shadow a key the user did configure in .env.
|
|
DEV_ENV=()
|
|
DEV_UNSET=()
|
|
put_env() {
|
|
DEV_ENV+=("$1=$2")
|
|
}
|
|
put_env_if_set() {
|
|
if [[ -n "$2" ]]; then
|
|
DEV_ENV+=("$1=$2")
|
|
else
|
|
# Leaving it out is not enough: the child inherits this shell's
|
|
# environment, so an empty export made in the PARENT would pass straight
|
|
# through and shadow .env just the same. Remove it from the child outright.
|
|
DEV_UNSET+=(-u "$1")
|
|
fi
|
|
}
|
|
|
|
put_env_if_set GOOGLE_MAPS_API_KEY "${GOOGLE_MAPS_API_KEY}"
|
|
put_env CCTV_AUSTIN_MAX_SOURCES "${CCTV_AUSTIN_MAX_SOURCES}"
|
|
# Empty is the documented Caltrans kill switch, so this one is passed as-is.
|
|
put_env CCTV_CALTRANS_DISTRICTS "${CCTV_CALTRANS_DISTRICTS}"
|
|
put_env CCTV_CALTRANS_MAX_SOURCES "${CCTV_CALTRANS_MAX_SOURCES}"
|
|
put_env CCTV_TFL_ENABLED "${CCTV_TFL_ENABLED}"
|
|
put_env CCTV_TFL_MAX_SOURCES "${CCTV_TFL_MAX_SOURCES}"
|
|
put_env CCTV_ONTARIO_ENABLED "${CCTV_ONTARIO_ENABLED}"
|
|
put_env CCTV_ONTARIO_MAX_SOURCES "${CCTV_ONTARIO_MAX_SOURCES}"
|
|
put_env_if_set TFL_APP_KEY "${TFL_APP_KEY:-}"
|
|
put_env CCTV_FINTRAFFIC_ENABLED "${CCTV_FINTRAFFIC_ENABLED}"
|
|
put_env CCTV_FINTRAFFIC_MAX_SOURCES "${CCTV_FINTRAFFIC_MAX_SOURCES}"
|
|
put_env CCTV_DRIVEBC_ENABLED "${CCTV_DRIVEBC_ENABLED}"
|
|
put_env CCTV_DRIVEBC_MAX_SOURCES "${CCTV_DRIVEBC_MAX_SOURCES}"
|
|
put_env CCTV_TXDOT_ENABLED "${CCTV_TXDOT_ENABLED}"
|
|
put_env CCTV_TXDOT_DISTRICTS "${CCTV_TXDOT_DISTRICTS}"
|
|
put_env CCTV_TXDOT_MAX_SOURCES "${CCTV_TXDOT_MAX_SOURCES}"
|
|
put_env CCTV_TALLINN_ENABLED "${CCTV_TALLINN_ENABLED}"
|
|
put_env CCTV_TALLINN_MAX_SOURCES "${CCTV_TALLINN_MAX_SOURCES}"
|
|
put_env CCTV_TARKTEE_ENABLED "${CCTV_TARKTEE_ENABLED}"
|
|
put_env CCTV_TARKTEE_MAX_SOURCES "${CCTV_TARKTEE_MAX_SOURCES}"
|
|
put_env CCTV_WARENDORF_ENABLED "${CCTV_WARENDORF_ENABLED}"
|
|
put_env CCTV_NSW_ENABLED "${CCTV_NSW_ENABLED}"
|
|
put_env CCTV_NSW_MAX_SOURCES "${CCTV_NSW_MAX_SOURCES}"
|
|
put_env CCTV_CALGARY_ENABLED "${CCTV_CALGARY_ENABLED}"
|
|
put_env CCTV_CALGARY_MAX_SOURCES "${CCTV_CALGARY_MAX_SOURCES}"
|
|
put_env CCTV_MAX_SOURCES "${CCTV_MAX_SOURCES}"
|
|
put_env OPENSKY_AUTH_MODE "${OPENSKY_AUTH_MODE}"
|
|
put_env_if_set OPENSKY_CREDENTIALS_FILE "${OPENSKY_CREDENTIALS_FILE}"
|
|
put_env_if_set OPENSKY_CLIENT_ID "${OPENSKY_CLIENT_ID}"
|
|
put_env_if_set OPENSKY_CLIENT_SECRET "${OPENSKY_CLIENT_SECRET}"
|
|
put_env_if_set OPENSKY_USERNAME "${OPENSKY_USERNAME}"
|
|
put_env_if_set OPENSKY_PASSWORD "${OPENSKY_PASSWORD}"
|
|
put_env_if_set OPENAI_API_KEY "${OPENAI_API_KEY}"
|
|
put_env_if_set AISSTREAM_API_KEY "${AISSTREAM_API_KEY}"
|
|
put_env_if_set CESIUM_ION_TOKEN "${CESIUM_ION_TOKEN}"
|
|
put_env_if_set TOMTOM_API_KEY "${TOMTOM_API_KEY}"
|
|
put_env_if_set FIRMS_MAP_KEY "${FIRMS_MAP_KEY}"
|
|
put_env_if_set LL2_API_TOKEN "${LL2_API_TOKEN}"
|
|
put_env GEV_LAUNCHER "dev-fresh"
|
|
put_env GEV_KEY_SETUP_EXTERNAL_KEYS "${KEY_SETUP_EXTERNAL_KEYS_CSV}"
|
|
|
|
env ${DEV_UNSET[@]+"${DEV_UNSET[@]}"} "${DEV_ENV[@]}" "${DEV_COMMAND[@]}" --host "${HOST}" --port "${PORT}" --force
|