refactor(split): make the app self-hosting ready

Remove the transitional website auth, admin hooks, SEO and hosted analytics together, then align the app routes, runtime configuration, tests and dependencies. These changes form one atomic cleanup because the filtered app must switch its identity and runtime surfaces as a unit.
This commit is contained in:
itsrubberduck
2026-07-27 19:17:27 +02:00
parent 3ed3865ebb
commit c8c2365c4d
29 changed files with 123 additions and 2038 deletions

View File

@@ -1,104 +1,49 @@
# Runtime
NODE_ENV=development
MONGODB_URI=mongodb://127.0.0.1:27017/opensquawk
NUXT_PUBLIC_RADIO_BACKEND_URL=http://127.0.0.1:8000
# Authentication
# Generate strong, unique random values — e.g. `openssl rand -hex 32`.
# The server refuses to start in production if these are unset, look like a
# placeholder, or are shorter than 32 characters.
# Open mode is the self-hosted default: one persistent local identity, no login.
AUTH_MODE=open
# Generate strong, unique values, for example with `openssl rand -hex 32`.
JWT_SECRET=CHANGE_ME
JWT_REFRESH_SECRET=CHANGE_ME
# How this deployment obtains an identity.
# open — self-hosted: no login at all, every request is one local user.
# sso — identity handed over from NUXT_PUBLIC_AUTH_ISSUER via a one-time code.
# Leave this at `sso` for as long as the website surface (/api/admin/**,
# /api/editor/**) lives in this repo: `open` would serve those to everyone.
AUTH_MODE=sso
# Issuer for AUTH_MODE=sso. Empty means website and app share one origin and
# the local /login page is used.
APP_JWT_SECRET=CHANGE_ME
# Only needed when AUTH_MODE=sso.
NUXT_PUBLIC_AUTH_ISSUER=
# Secret for the app's own session cookie. Falls back to JWT_SECRET when unset;
# set it explicitly once the app runs on its own origin.
APP_JWT_SECRET=
# SSO issuer side (website). Comma-separated allowlist of origins an SSO code
# may be issued for. No default and no wildcard — empty disables the handoff.
# Without this the issuer would be an open redirector handing out identities.
SSO_REDIRECT_ORIGINS=
# OpenAI
OPENAI_API_KEY=sk-your-openai-key
OPENAI_PROJECT=
OPENAI_BASE_URL=
# Optional: specify a custom API endpoint, e.g. http://localhost:1234/v1
LLM_MODEL=gpt-5-nano
TTS_MODEL=tts-1
VOICE_ID=alloy
# Model used by the /api/decision/route LLM router (the Python backend calls
# this when regex routing misses a pilot transmission).
ROUTER_LLM_MODEL=gpt-5-mini
# Internal service-to-service auth. The Python decision backend calls
# /api/decision/route with this value in the `x-service-secret` header. Must
# match SERVICE_SECRET in the Python backend's env. If unset, the LLM router is
# disabled (the backend falls back to deterministic bad_next routing).
SERVICE_SECRET=CHANGE_ME
# Telemetry mirror (app → hosted service). Both this AND SERVICE_SECRET must be
# set for anything to be sent; leave empty and nothing ever leaves the instance.
# That is the self-host default, not a fallback.
# OpenAI-compatible services
OPENAI_API_KEY=
OPENAI_PROJECT=
OPENAI_BASE_URL=
OPENAIP_API_KEY=
LLM_MODEL=gpt-5-nano
ROUTER_LLM_MODEL=gpt-5-mini
TTS_MODEL=tts-1
VOICE_ID=alloy
# Optional telemetry mirror. Empty means nothing leaves this instance.
TELEMETRY_URL=
# Account-deletion webhook (website → app instance). Base URL of the app
# deployment; the website POSTs /api/service/user-deleted there when an account
# is deleted. Empty means website and app share one database and the website's
# own deletes already cover everything.
APP_WEBHOOK_URL=
# PM radio training
# Minimum word count for a voice (PTT) transmission to be used; shorter
# transcripts are treated as STT noise/hallucination and ignored. Set to 1 to
# disable. See the "STT MINIMUM-WORD GATE" in app/pages/pm.vue.
NUXT_PUBLIC_PTT_MIN_WORDS=2
# ATC audio generation
ATC_OUT_DIR=./storage/atc
FLIGHTLAB_TTS_CACHE_DIR=./.cache/flightlab-tts
USE_SPEACHES=false
USE_PIPER=false
PIPER_PORT=5001
SPEACHES_BASE_URL=
SPEECH_MODEL_ID=speaches-ai/piper-en_US-ryan-low
# Optional: external webhook for bridge dome-light telemetry. Leave empty to
# disable — there is deliberately no default, so no instance forwards cockpit
# telemetry anywhere unless its operator asks for it.
DOME_LIGHT_WEBHOOK_URL=
# Analytics (website). Without a Hotjar ID the module is not loaded at all.
HOTJAR_ID=
# Notifications
NOTIFY_RESEND_API_KEY=
NOTIFY_EMAIL_TO=
NOTIFY_EMAIL_FROM="OpenSquawk <info@opensquawk.de>"
# Bug reports stay in MongoDB unless both a recipient and SMTP are configured.
BUG_REPORT_NOTIFY_EMAIL=
NOTIFY_EMAIL_FROM="OpenSquawk <no-reply@localhost>"
NOTIFY_SMTP_HOST=
NOTIFY_SMTP_PORT=587
NOTIFY_SMTP_SECURE=false
NOTIFY_SMTP_USER=
NOTIFY_SMTP_PASS=
# Where bug reports are mailed. Unset means no mail is sent at all — reports
# then live only in this instance's own database.
BUG_REPORT_NOTIFY_EMAIL=
# Bootstrap invitations
BOOTSTRAP_INVITE_DEADLINE=2025-09-01T00:00:00Z
# Speech and audio
NUXT_PUBLIC_PTT_MIN_WORDS=2
ATC_OUT_DIR=./storage/atc
FLIGHTLAB_TTS_CACHE_DIR=./.cache/flightlab-tts
USE_SPEACHES=false
SPEACHES_BASE_URL=
SPEECH_MODEL_ID=speaches-ai/piper-en_US-ryan-low
USE_PIPER=false
PIPER_PORT=5001
# Manual invitation generator
MANUAL_INVITE_PASSWORD=CHANGE_ME
# Cron / scheduled tasks
# Required for the /api/service/cron/* endpoints (they send emails and mint
# invite codes). Without it those endpoints return 503. Pass it via the
# `x-cron-secret` header (preferred) or `?secret=` query param.
CRON_SECRET=CHANGE_ME
# Optional cockpit dome-light webhook. Empty disables forwarding.
DOME_LIGHT_WEBHOOK_URL=