fix: split MEALIE_API_URL (internal) from MEALIE_PUBLIC_URL (UI link)

Cauldron's container can't resolve 'recipes.sulkta.com' from inside the
sulkta+sulkta-db-net bridges (Lucy's split-horizon doesn't propagate to
container DNS). Symptom: 500 on /connect-mealie POST when validating the
pasted token.

Fix: take the LAN-internal HTTP path direct to mealie. Mealie shares
OpenVPN-rack2's netns, listening on 9000 inside that netns. Both cauldron
and OpenVPN-rack2 are on sulkta-db-net (172.30.1.0/24), so cauldron talks
to 'http://OpenVPN-rack2:9000' via Docker's internal DNS — bypasses
Apache/HTTPS termination on Rackham entirely.

The public URL stays in the UI (so the connect-mealie page deep-link to
mint a token still goes to https://recipes.sulkta.com via the user's
browser, which DOES resolve it).

Also tightened Mealie._get/_put/_post to wrap requests.RequestException
into MealieError so connection failures don't 500 callers.
This commit is contained in:
Kayos 2026-04-28 20:26:25 -07:00
parent 6588f148e6
commit a329784063
3 changed files with 24 additions and 9 deletions

View file

@ -9,7 +9,8 @@ class Config:
bind_host: str
bind_port: int
mealie_base_url: str
mealie_api_url: str # internal URL cauldron uses for HTTP calls (LAN-internal)
mealie_public_url: str # external URL shown to users for token-mint UI
mealie_api_token: str # system token (Cobb's "Cauldron" token, used for admin batch ops)
clawdforge_url: str
@ -41,7 +42,12 @@ def load() -> Config:
secret_key=os.environ["SECRET_KEY"],
bind_host=os.environ.get("BIND_HOST", "0.0.0.0"),
bind_port=int(os.environ.get("BIND_PORT", "7790")),
mealie_base_url=os.environ["MEALIE_BASE_URL"].rstrip("/"),
mealie_api_url=os.environ.get(
"MEALIE_API_URL", os.environ["MEALIE_BASE_URL"]
).rstrip("/"),
mealie_public_url=os.environ.get(
"MEALIE_PUBLIC_URL", os.environ["MEALIE_BASE_URL"]
).rstrip("/"),
mealie_api_token=os.environ["MEALIE_API_TOKEN"],
clawdforge_url=os.environ["CLAWDFORGE_URL"].rstrip("/"),
clawdforge_token=os.environ["CLAWDFORGE_TOKEN"],

View file

@ -35,19 +35,28 @@ class Mealie:
# --- low-level ----------------------------------------------------------
def _get(self, path: str, **params) -> dict:
r = self.session.get(f"{self.base_url}{path}", params=params, timeout=30)
try:
r = self.session.get(f"{self.base_url}{path}", params=params, timeout=30)
except requests.RequestException as e:
raise MealieError(f"GET {path} transport: {e}") from e
if r.status_code >= 400:
raise MealieError(f"GET {path} -> {r.status_code}: {r.text[:300]}")
return r.json()
def _put(self, path: str, body: dict) -> dict:
r = self.session.put(f"{self.base_url}{path}", json=body, timeout=30)
try:
r = self.session.put(f"{self.base_url}{path}", json=body, timeout=30)
except requests.RequestException as e:
raise MealieError(f"PUT {path} transport: {e}") from e
if r.status_code >= 400:
raise MealieError(f"PUT {path} -> {r.status_code}: {r.text[:300]}")
return r.json()
def _post(self, path: str, body: dict) -> dict:
r = self.session.post(f"{self.base_url}{path}", json=body, timeout=30)
try:
r = self.session.post(f"{self.base_url}{path}", json=body, timeout=30)
except requests.RequestException as e:
raise MealieError(f"POST {path} transport: {e}") from e
if r.status_code >= 400:
raise MealieError(f"POST {path} -> {r.status_code}: {r.text[:300]}")
return r.json()

View file

@ -44,7 +44,7 @@ forge = Forge(
default_timeout=cfg.default_timeout_secs,
)
# System-tier Mealie client (Cobb's "Cauldron" token; admin batch ops only)
system_mealie = Mealie(base_url=cfg.mealie_base_url, api_token=cfg.mealie_api_token)
system_mealie = Mealie(base_url=cfg.mealie_api_url, api_token=cfg.mealie_api_token)
system_sterilizer = Sterilizer(mealie=system_mealie, forge=forge, model=cfg.default_model)
@ -103,7 +103,7 @@ def create_app() -> Flask:
tok = crypto.decrypt(blob)
except Exception:
return None
return Mealie(base_url=cfg.mealie_base_url, api_token=tok)
return Mealie(base_url=cfg.mealie_api_url, api_token=tok)
# ---------- public ---------------------------------------------------
@ -188,7 +188,7 @@ def create_app() -> Flask:
return render_template_string(
CONNECT_TEMPLATE,
user=u,
mealie_url=cfg.mealie_base_url,
mealie_url=cfg.mealie_public_url,
css=_PALETTE_CSS,
)
@ -201,7 +201,7 @@ def create_app() -> Flask:
return ("empty token", 400)
# Validate against Mealie before storing — don't persist a bad token
test = Mealie(base_url=cfg.mealie_base_url, api_token=token)
test = Mealie(base_url=cfg.mealie_api_url, api_token=token)
try:
test.who_am_i()
except MealieError as e: