fix: env var overrides for campaign settings (hf_token, ollama_host etc)
Some checks failed
Build and Push / build (push) Has been cancelled

This commit is contained in:
KansaiGaijin
2026-07-21 18:30:08 +12:00
parent ca13989ca0
commit 9e70b78d7c
6 changed files with 45 additions and 12 deletions

View File

@@ -64,3 +64,34 @@ CAMPAIGN_SETTINGS = {
"player_recap_style": "story", "player_recap_style": "story",
"player_recap_custom_prompt": "", "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

View File

@@ -4,6 +4,7 @@ import subprocess
import requests import requests
from . import database as db, config from . import database as db, config
from .config import merge_campaign_settings_with_env
def _check_ffmpeg() -> dict: def _check_ffmpeg() -> dict:
@@ -27,7 +28,7 @@ def _check_gpu() -> dict:
def _check_hf_token(campaign_id: str = "default") -> 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", "") token = settings.get("hf_token", "")
if not 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."} 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: 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 settings.get("llm_mode") == "api":
if not settings.get("api_key"): if not settings.get("api_key"):
return {"name": "llm_backend", "ok": False, "message": "Hosted API selected but no API key set."} return {"name": "llm_backend", "ok": False, "message": "Hosted API selected but no API key set."}

View File

@@ -1,6 +1,7 @@
from fastapi import APIRouter, HTTPException 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"]) 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): if not db.get_campaign(campaign_id):
raise HTTPException(404, "Campaign not found") raise HTTPException(404, "Campaign not found")
raw = db.get_campaign_settings(campaign_id) raw = db.get_campaign_settings(campaign_id)
# return defaults for any missing keys return merge_campaign_settings_with_env(raw)
merged = {**config.CAMPAIGN_SETTINGS, **raw}
return merged
@router.post("/{campaign_id}/settings") @router.post("/{campaign_id}/settings")
def update_campaign_settings(campaign_id: str, body: dict): def update_campaign_settings(campaign_id: str, body: dict):
if not db.get_campaign(campaign_id): if not db.get_campaign(campaign_id):
raise HTTPException(404, "Campaign not found") 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) db.update_campaign_settings(campaign_id, clean)
raw = db.get_campaign_settings(campaign_id) raw = db.get_campaign_settings(campaign_id)
merged = {**config.CAMPAIGN_SETTINGS, **raw} return merge_campaign_settings_with_env(raw)
return merged

View File

@@ -2,6 +2,7 @@ import requests
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from .. import database as db from .. import database as db
from ..config import merge_campaign_settings_with_env
router = APIRouter(prefix="/api/models", tags=["models"]) 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")): def list_ollama_models(campaign_id: str = Query("default")):
"""List models already pulled on the configured Ollama host, so the setup """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.""" 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") host = settings.get("ollama_host", "http://host.docker.internal:11434")
try: try:
resp = requests.get(f"{host.rstrip('/')}/api/tags", timeout=5) resp = requests.get(f"{host.rstrip('/')}/api/tags", timeout=5)

View File

@@ -3,6 +3,7 @@ from pathlib import Path
from fastapi import APIRouter, HTTPException, Request from fastapi import APIRouter, HTTPException, Request
from .. import database as db, jobs from .. import database as db, jobs
from ..config import merge_campaign_settings_with_env
from ..pipeline.turns import load_turns from ..pipeline.turns import load_turns
from ..pipeline.summarize import summarize_session 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") raise HTTPException(400, "Session hasn't been transcribed yet")
cid = session["campaign_id"] or "default" 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 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,)) db.get_conn().execute("SELECT raw_label, display_name FROM speakers WHERE session_id = ?", (session_id,))
if r["display_name"]} if r["display_name"]}

View File

@@ -6,6 +6,7 @@ from pathlib import Path
from fastapi import APIRouter, UploadFile, File, Form, HTTPException from fastapi import APIRouter, UploadFile, File, Form, HTTPException
from .. import database as db, config, jobs from .. import database as db, config, jobs
from ..config import merge_campaign_settings_with_env
from ..errors import PipelineError from ..errors import PipelineError
from ..pipeline.audio import extract_audio from ..pipeline.audio import extract_audio
from ..pipeline.transcribe import transcribe_and_diarize 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): 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" 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): def run(progress_cb):
db.get_conn() # ensure thread-local connection exists in this worker thread db.get_conn() # ensure thread-local connection exists in this worker thread