fix: google anti-bot flag + persistent/safe settings apply + TTS engine wiring

- Chrome: --disable-blink-features=AutomationControlled (+ ko-KR) so Google
  shows results, not the /sorry/ automation block.
- Settings persist to /data/jarvis-settings.json (survives recreate; entrypoint
  re-merges it) AND the runtime config; apply restarts via a DETACHED process so
  the HTTP response isn't dropped when the bridge restarts.
- Bridge reads tts_engine from the settings config so the TTS-engine choice
  actually applies.
This commit is contained in:
javis-bot
2026-06-15 13:13:11 +09:00
parent 84e435f916
commit 247edda3eb
4 changed files with 73 additions and 16 deletions

View File

@@ -90,7 +90,20 @@ STT_LANGUAGE = os.environ.get("STT_LANGUAGE", "ko").strip() or None
# TTS engine: "melo" (MeloTTS Korean speaker, the warm worker) is the primary
# voice; Piper is kept as a fallback if the worker is unreachable. Set
# TTS_ENGINE=piper to disable MeloTTS entirely.
TTS_ENGINE = os.environ.get("TTS_ENGINE", "melo").strip().lower()
def _tts_engine_setting() -> str:
"""TTS engine: settings-UI value (runtime config JSON) wins, else env, else
melo. Read at startup; the settings UI restarts the bridge on apply."""
try:
_cp = os.environ.get("JARVIS_CONFIG_PATH", "/app/config/jarvis.json")
_v = json.loads(open(_cp, encoding="utf-8").read()).get("tts_engine")
if _v:
return str(_v).strip().lower()
except Exception:
pass
return os.environ.get("TTS_ENGINE", "melo").strip().lower()
TTS_ENGINE = _tts_engine_setting()
MELO_WORKER_URL = os.environ.get("MELO_WORKER_URL", "http://127.0.0.1:8770")
MELO_TIMEOUT = float(os.environ.get("MELO_TIMEOUT", "30"))
# When MeloTTS is the engine, do NOT silently fall back to the English Piper

View File

@@ -37,6 +37,12 @@ def _config_path() -> Path:
return Path(p).expanduser() if p else (Path.home() / ".config" / "jarvis" / "config.json")
def _persist_path() -> Path:
"""Persistent overrides on the data volume — survive container recreate.
entrypoint.sh merges this back onto the env-rendered config at startup."""
return Path(os.environ.get("JARVIS_SETTINGS_PATH") or "/data/jarvis-settings.json")
def _read_config() -> Dict[str, Any]:
try:
return json.loads(_config_path().read_text("utf-8"))
@@ -67,8 +73,8 @@ def _ollama_models() -> list[str]:
return []
def _save(updates: Dict[str, Any]) -> None:
cfg = _read_config()
def _coerce(updates: Dict[str, Any]) -> Dict[str, Any]:
clean: Dict[str, Any] = {}
for k, v in updates.items():
if k not in _KEYS:
continue
@@ -84,22 +90,41 @@ def _save(updates: Dict[str, Any]) -> None:
continue
elif k == "llm_thinking_enabled":
v = str(v).lower() in ("1", "true", "on", "yes")
cfg[k] = v
p = _config_path()
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), "utf-8")
clean[k] = v
return clean
def _write_merge(path: Path, clean: Dict[str, Any]) -> None:
cur: Dict[str, Any] = {}
try:
cur = json.loads(path.read_text("utf-8"))
except Exception:
cur = {}
cur.update(clean)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(cur, ensure_ascii=False, indent=2), "utf-8")
def _save(updates: Dict[str, Any]) -> None:
clean = _coerce(updates)
# 1) persistent overrides (survive `docker compose up` recreate)
_write_merge(_persist_path(), clean)
# 2) runtime config so a bridge/worker restart picks it up immediately
_write_merge(_config_path(), clean)
def _apply() -> str:
# Restart the bridge (re-reads config) and the TTS worker (picks up speed).
out = []
for prog in ("melo-worker", "bridge"):
try:
r = subprocess.run(["supervisorctl", "restart", prog], capture_output=True, text=True, timeout=30)
out.append(f"{prog}: {(r.stdout or r.stderr).strip()}")
except Exception as e: # pragma: no cover
out.append(f"{prog}: {e}")
return " | ".join(out)
# Restart melo + bridge AFTER this response is sent. Detached (new session)
# so the bridge being killed mid-restart doesn't drop the restart itself,
# and the HTTP client still receives this response.
try:
subprocess.Popen(
["sh", "-c", "sleep 1; supervisorctl restart melo-worker bridge"],
start_new_session=True,
)
return "1초 후 브리지/TTS 워커가 재시작되어 반영됩니다."
except Exception as e: # pragma: no cover
return str(e)
_PAGE = """<!doctype html><html lang=ko><head><meta charset=utf-8>

View File

@@ -47,6 +47,22 @@ chmod 600 /root/.vnc/passwd
# --- Render jarvis brain config from template ---
envsubst < /app/docker/jarvis-config.template.json > /app/config/jarvis.json
export JARVIS_CONFIG_PATH=/app/config/jarvis.json
# Merge persistent settings from the settings UI (on the /data volume) on top of
# the env-rendered config, so changes survive container recreate.
if [ -f /data/jarvis-settings.json ]; then
python3 - <<'PY' || true
import json
try:
base = json.load(open("/app/config/jarvis.json"))
ov = json.load(open("/data/jarvis-settings.json"))
if isinstance(base, dict) and isinstance(ov, dict):
base.update(ov)
json.dump(base, open("/app/config/jarvis.json", "w"), ensure_ascii=False, indent=2)
print("[entrypoint] merged persistent settings overrides")
except Exception as e:
print(f"[entrypoint] settings merge skipped: {e}")
PY
fi
# --- Ensure the Piper voice exists (best effort) ---
bash /app/docker/download-piper.sh || echo "[entrypoint] piper download failed; TTS may be unavailable"

View File

@@ -14,6 +14,9 @@ export DISPLAY=:1
exec google-chrome \
--no-sandbox --no-first-run --disable-dev-shm-usage \
--test-type \
--disable-blink-features=AutomationControlled \
--disable-features=AutomationControlled \
--lang=ko-KR \
--disable-infobars \
--no-default-browser-check \
--disable-translate \