"""Turn apiGateway back off on the live Application to restore the chat. ## Why Enabling apiGateway makes the Helm chart set `API_GATEWAY_URL` on the web pod (infra/helm/medical-chatbot/templates/web.yaml). Every one of web's BFF routes prefers that variable over `AI_SERVICE_URL`: apps/web/app/api/chat/route.ts:7 apps/web/app/api/suggest/route.ts:5 apps/web/app/api/history/route.ts:5 apps/web/app/api/sections/route.ts:5 apps/web/app/api/section-text/route.ts:5 apps/web/app/api/feedback/route.ts:5 process.env.API_GATEWAY_URL ?? process.env.AI_SERVICE_URL ?? ... but the gateway only proxies `/auth/*` (apps/api-gateway/src/proxy/ has a single AuthProxyController). So all six start posting to a service with no such route, and the UI shows "Dịch vụ AI Service đang khởi động hoặc gặp sự cố tạm thời." The bug shipped in PR #27 yesterday and sat dormant: the configuration that triggers it was never actually rendered until the 2026-08-19 repair of the folded-scalar corruption deployed it for the first time. ## What this does Flips `enabled` to false under the `apiGateway` key only -- `authService`'s own `enabled: true` is left alone -- so `API_GATEWAY_URL` stops being set and all six routes fall back to `AI_SERVICE_URL`, exactly the configuration that served traffic before today. Cost: login stops working again (apps/web/app/api/auth/{login,me}/route.ts read API_GATEWAY_URL with no fallback). That is the deliberate trade -- chat is the product and affects every visitor; login is a day-old addition that was not reachable before today anyway. The real fix is to stop routing RAG through the gateway at all, which needs an image rebuild. Refuses to act unless it finds `apiGateway:` followed by `enabled: true`, so it cannot silently do something else. Required env: ARGOCD_PRACTICE_URL, ARGOCD_PRACTICE_PASSWORD. """ from __future__ import annotations import json import os import sys import time import urllib.error import urllib.request APP_NAME = "medical-chatbot-app" POLL_SECONDS = 15 POLL_ROUNDS = 8 def call(base: str, method: str, path: str, token: str | None = None, body=None): req = urllib.request.Request( f"{base}{path}", data=json.dumps(body).encode() if body is not None else None, method=method, headers={"Content-Type": "application/json"}, ) if token: req.add_header("Authorization", f"Bearer {token}") with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read() return json.loads(raw) if raw else {} def disable_api_gateway(values: str) -> str: """Set `enabled: false` under the apiGateway key, and nowhere else.""" lines = values.splitlines() out: list[str] = [] in_gateway = False changed = 0 for line in lines: stripped = line.strip() # A non-indented key ends whatever block we were in. if line and not line[0].isspace(): in_gateway = stripped == "apiGateway:" elif in_gateway and stripped == "enabled: true": indent = line[: len(line) - len(line.lstrip())] line = f"{indent}enabled: false" changed += 1 out.append(line) if changed != 1: raise SystemExit( f"Expected exactly one `enabled: true` under apiGateway, changed {changed}. " "Refusing to write." ) return "\n".join(out) + "\n" def main() -> int: base = os.environ["ARGOCD_PRACTICE_URL"].rstrip("/") password = os.environ["ARGOCD_PRACTICE_PASSWORD"] session = call(base, "POST", "/api/v1/session", body={"username": "admin", "password": password}) token = session["token"] app = call(base, "GET", f"/api/v1/applications/{APP_NAME}", token=token) values = app["spec"]["source"]["helm"].get("values", "") repaired = disable_api_gateway(values) print("apiGateway.enabled -> false (authService untouched)") app["spec"]["source"]["helm"]["values"] = repaired call(base, "PUT", f"/api/v1/applications/{APP_NAME}", token=token, body=app) print("PUT accepted.") try: call(base, "POST", f"/api/v1/applications/{APP_NAME}/sync", token=token, body={}) print("Sync triggered.") except urllib.error.HTTPError as exc: if exc.code == 400: print("Explicit sync raced with autosync -- continuing.") else: raise for i in range(POLL_ROUNDS): time.sleep(POLL_SECONDS) app = call(base, "GET", f"/api/v1/applications/{APP_NAME}", token=token) status = app.get("status", {}) print( f"poll {i + 1}/{POLL_ROUNDS}: sync={status.get('sync', {}).get('status')} " f"health={status.get('health', {}).get('status')}" ) return 0 if __name__ == "__main__": raise SystemExit(main())