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:
@@ -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
|
# 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
|
# voice; Piper is kept as a fallback if the worker is unreachable. Set
|
||||||
# TTS_ENGINE=piper to disable MeloTTS entirely.
|
# 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_WORKER_URL = os.environ.get("MELO_WORKER_URL", "http://127.0.0.1:8770")
|
||||||
MELO_TIMEOUT = float(os.environ.get("MELO_TIMEOUT", "30"))
|
MELO_TIMEOUT = float(os.environ.get("MELO_TIMEOUT", "30"))
|
||||||
# When MeloTTS is the engine, do NOT silently fall back to the English Piper
|
# When MeloTTS is the engine, do NOT silently fall back to the English Piper
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ def _config_path() -> Path:
|
|||||||
return Path(p).expanduser() if p else (Path.home() / ".config" / "jarvis" / "config.json")
|
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]:
|
def _read_config() -> Dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
return json.loads(_config_path().read_text("utf-8"))
|
return json.loads(_config_path().read_text("utf-8"))
|
||||||
@@ -67,8 +73,8 @@ def _ollama_models() -> list[str]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def _save(updates: Dict[str, Any]) -> None:
|
def _coerce(updates: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
cfg = _read_config()
|
clean: Dict[str, Any] = {}
|
||||||
for k, v in updates.items():
|
for k, v in updates.items():
|
||||||
if k not in _KEYS:
|
if k not in _KEYS:
|
||||||
continue
|
continue
|
||||||
@@ -84,22 +90,41 @@ def _save(updates: Dict[str, Any]) -> None:
|
|||||||
continue
|
continue
|
||||||
elif k == "llm_thinking_enabled":
|
elif k == "llm_thinking_enabled":
|
||||||
v = str(v).lower() in ("1", "true", "on", "yes")
|
v = str(v).lower() in ("1", "true", "on", "yes")
|
||||||
cfg[k] = v
|
clean[k] = v
|
||||||
p = _config_path()
|
return clean
|
||||||
p.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
p.write_text(json.dumps(cfg, ensure_ascii=False, indent=2), "utf-8")
|
|
||||||
|
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:
|
def _apply() -> str:
|
||||||
# Restart the bridge (re-reads config) and the TTS worker (picks up speed).
|
# Restart melo + bridge AFTER this response is sent. Detached (new session)
|
||||||
out = []
|
# so the bridge being killed mid-restart doesn't drop the restart itself,
|
||||||
for prog in ("melo-worker", "bridge"):
|
# and the HTTP client still receives this response.
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(["supervisorctl", "restart", prog], capture_output=True, text=True, timeout=30)
|
subprocess.Popen(
|
||||||
out.append(f"{prog}: {(r.stdout or r.stderr).strip()}")
|
["sh", "-c", "sleep 1; supervisorctl restart melo-worker bridge"],
|
||||||
|
start_new_session=True,
|
||||||
|
)
|
||||||
|
return "1초 후 브리지/TTS 워커가 재시작되어 반영됩니다."
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
out.append(f"{prog}: {e}")
|
return str(e)
|
||||||
return " | ".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
_PAGE = """<!doctype html><html lang=ko><head><meta charset=utf-8>
|
_PAGE = """<!doctype html><html lang=ko><head><meta charset=utf-8>
|
||||||
|
|||||||
@@ -47,6 +47,22 @@ chmod 600 /root/.vnc/passwd
|
|||||||
# --- Render jarvis brain config from template ---
|
# --- Render jarvis brain config from template ---
|
||||||
envsubst < /app/docker/jarvis-config.template.json > /app/config/jarvis.json
|
envsubst < /app/docker/jarvis-config.template.json > /app/config/jarvis.json
|
||||||
export JARVIS_CONFIG_PATH=/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) ---
|
# --- Ensure the Piper voice exists (best effort) ---
|
||||||
bash /app/docker/download-piper.sh || echo "[entrypoint] piper download failed; TTS may be unavailable"
|
bash /app/docker/download-piper.sh || echo "[entrypoint] piper download failed; TTS may be unavailable"
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export DISPLAY=:1
|
|||||||
exec google-chrome \
|
exec google-chrome \
|
||||||
--no-sandbox --no-first-run --disable-dev-shm-usage \
|
--no-sandbox --no-first-run --disable-dev-shm-usage \
|
||||||
--test-type \
|
--test-type \
|
||||||
|
--disable-blink-features=AutomationControlled \
|
||||||
|
--disable-features=AutomationControlled \
|
||||||
|
--lang=ko-KR \
|
||||||
--disable-infobars \
|
--disable-infobars \
|
||||||
--no-default-browser-check \
|
--no-default-browser-check \
|
||||||
--disable-translate \
|
--disable-translate \
|
||||||
|
|||||||
Reference in New Issue
Block a user