From 9e70b78d7cbd51cc323c14845471c05dd576318b Mon Sep 17 00:00:00 2001 From: KansaiGaijin <83641841+KansaiGaijin@users.noreply.github.com> Date: Tue, 21 Jul 2026 18:30:08 +1200 Subject: [PATCH] fix: env var overrides for campaign settings (hf_token, ollama_host etc) --- backend/app/config.py | 31 +++++++++++++++++++++++++++++++ backend/app/diagnostics.py | 5 +++-- backend/app/routers/campaigns.py | 12 +++++------- backend/app/routers/models.py | 3 ++- backend/app/routers/notes.py | 3 ++- backend/app/routers/sessions.py | 3 ++- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/backend/app/config.py b/backend/app/config.py index 7e0aa7a..7d80ff6 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -64,3 +64,34 @@ CAMPAIGN_SETTINGS = { "player_recap_style": "story", "player_recap_custom_prompt": "", } + +# Env var names that can override CAMPAIGN_SETTINGS at read time. +# Set these in docker-compose.yml to prefill the setup wizard. +CAMPAIGN_ENV_OVERRIDES = { + "hf_token": "NAT20_HF_TOKEN", + "whisper_model": "NAT20_WHISPER_MODEL", + "whisper_compute_type": "NAT20_WHISPER_COMPUTE_TYPE", + "ollama_host": "NAT20_OLLAMA_HOST", + "ollama_model": "NAT20_OLLAMA_MODEL", + "api_base_url": "NAT20_API_BASE_URL", + "api_key": "NAT20_API_KEY", + "api_model": "NAT20_API_MODEL", + "chunk_word_target": "NAT20_CHUNK_WORD_TARGET", + "world_context": "NAT20_WORLD_CONTEXT", + "world_context_path": "NAT20_WORLD_CONTEXT_PATH", + "player_recap_style": "NAT20_PLAYER_RECAP_STYLE", + "player_recap_custom_prompt": "NAT20_PLAYER_RECAP_CUSTOM_PROMPT", +} + + +def merge_campaign_settings_with_env(raw: dict) -> dict: + """Priority: env var > DB value > CAMPAIGN_SETTINGS default.""" + merged = {**CAMPAIGN_SETTINGS} + for key, val in raw.items(): + if val and val.strip(): + merged[key] = val.strip() + for key, env_name in CAMPAIGN_ENV_OVERRIDES.items(): + val = os.environ.get(env_name) + if val is not None: + merged[key] = val + return merged diff --git a/backend/app/diagnostics.py b/backend/app/diagnostics.py index bcf8774..317f492 100644 --- a/backend/app/diagnostics.py +++ b/backend/app/diagnostics.py @@ -4,6 +4,7 @@ import subprocess import requests from . import database as db, config +from .config import merge_campaign_settings_with_env def _check_ffmpeg() -> dict: @@ -27,7 +28,7 @@ def _check_gpu() -> dict: def _check_hf_token(campaign_id: str = "default") -> dict: - settings = db.get_campaign_settings(campaign_id) + settings = merge_campaign_settings_with_env(db.get_campaign_settings(campaign_id)) token = settings.get("hf_token", "") if not token: return {"name": "huggingface_token", "ok": False, "message": "No HF token set. Diarization (speaker separation) will fail without one. Add it in Settings."} @@ -35,7 +36,7 @@ def _check_hf_token(campaign_id: str = "default") -> dict: def _check_llm_backend(campaign_id: str = "default") -> dict: - settings = db.get_campaign_settings(campaign_id) + settings = merge_campaign_settings_with_env(db.get_campaign_settings(campaign_id)) if settings.get("llm_mode") == "api": if not settings.get("api_key"): return {"name": "llm_backend", "ok": False, "message": "Hosted API selected but no API key set."} diff --git a/backend/app/routers/campaigns.py b/backend/app/routers/campaigns.py index 1ecdc62..3f498b4 100644 --- a/backend/app/routers/campaigns.py +++ b/backend/app/routers/campaigns.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, HTTPException -from .. import database as db, config +from .. import database as db +from ..config import CAMPAIGN_SETTINGS, merge_campaign_settings_with_env router = APIRouter(prefix="/api/campaigns", tags=["campaigns"]) @@ -52,17 +53,14 @@ def get_campaign_settings(campaign_id: str): if not db.get_campaign(campaign_id): raise HTTPException(404, "Campaign not found") raw = db.get_campaign_settings(campaign_id) - # return defaults for any missing keys - merged = {**config.CAMPAIGN_SETTINGS, **raw} - return merged + return merge_campaign_settings_with_env(raw) @router.post("/{campaign_id}/settings") def update_campaign_settings(campaign_id: str, body: dict): if not db.get_campaign(campaign_id): raise HTTPException(404, "Campaign not found") - clean = {k: str(v) for k, v in body.items() if k in config.CAMPAIGN_SETTINGS} + clean = {k: str(v) for k, v in body.items() if k in CAMPAIGN_SETTINGS} db.update_campaign_settings(campaign_id, clean) raw = db.get_campaign_settings(campaign_id) - merged = {**config.CAMPAIGN_SETTINGS, **raw} - return merged + return merge_campaign_settings_with_env(raw) diff --git a/backend/app/routers/models.py b/backend/app/routers/models.py index 7c419ea..e6a3257 100644 --- a/backend/app/routers/models.py +++ b/backend/app/routers/models.py @@ -2,6 +2,7 @@ import requests from fastapi import APIRouter, Query from .. import database as db +from ..config import merge_campaign_settings_with_env router = APIRouter(prefix="/api/models", tags=["models"]) @@ -10,7 +11,7 @@ router = APIRouter(prefix="/api/models", tags=["models"]) def list_ollama_models(campaign_id: str = Query("default")): """List models already pulled on the configured Ollama host, so the setup wizard can offer a dropdown instead of asking the user to type a tag blind.""" - settings = db.get_campaign_settings(campaign_id) + settings = merge_campaign_settings_with_env(db.get_campaign_settings(campaign_id)) host = settings.get("ollama_host", "http://host.docker.internal:11434") try: resp = requests.get(f"{host.rstrip('/')}/api/tags", timeout=5) diff --git a/backend/app/routers/notes.py b/backend/app/routers/notes.py index 9b3e487..4b64de6 100644 --- a/backend/app/routers/notes.py +++ b/backend/app/routers/notes.py @@ -3,6 +3,7 @@ from pathlib import Path from fastapi import APIRouter, HTTPException, Request from .. import database as db, jobs +from ..config import merge_campaign_settings_with_env from ..pipeline.turns import load_turns from ..pipeline.summarize import summarize_session @@ -19,7 +20,7 @@ async def generate_notes(session_id: str, request: Request): raise HTTPException(400, "Session hasn't been transcribed yet") cid = session["campaign_id"] or "default" - settings = db.get_campaign_settings(cid) + settings = merge_campaign_settings_with_env(db.get_campaign_settings(cid)) speaker_map = {r["raw_label"]: r["display_name"] for r in db.get_conn().execute("SELECT raw_label, display_name FROM speakers WHERE session_id = ?", (session_id,)) if r["display_name"]} diff --git a/backend/app/routers/sessions.py b/backend/app/routers/sessions.py index cfb33c4..e7f8ddc 100644 --- a/backend/app/routers/sessions.py +++ b/backend/app/routers/sessions.py @@ -6,6 +6,7 @@ from pathlib import Path from fastapi import APIRouter, UploadFile, File, Form, HTTPException from .. import database as db, config, jobs +from ..config import merge_campaign_settings_with_env from ..errors import PipelineError from ..pipeline.audio import extract_audio from ..pipeline.transcribe import transcribe_and_diarize @@ -86,7 +87,7 @@ def get_session(session_id: str): def _run_transcription(session_id: str, video_path: Path, audio_path: Path, transcript_path: Path, campaign_id: str | None = None): cid = campaign_id or "default" - settings = db.get_campaign_settings(cid) + settings = merge_campaign_settings_with_env(db.get_campaign_settings(cid)) def run(progress_cb): db.get_conn() # ensure thread-local connection exists in this worker thread