mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 15:02:20 +02:00
89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
"""Ithaca anchor — local-instance readiness / integrity self-check.
|
|
|
|
Beyond ``/api/health``'s liveness ping, this confirms the self-hosted instance is
|
|
whole and at home: the database is reachable, the data directory is present and
|
|
writable, and storage is local-first. Served by ``GET /api/ready`` and suitable
|
|
for an orchestrator readiness probe (200 only when every critical check passes).
|
|
"""
|
|
|
|
import os
|
|
import uuid
|
|
from datetime import datetime
|
|
from typing import Dict
|
|
|
|
|
|
def check_readiness() -> Dict[str, object]:
|
|
"""Run the readiness checks and return a JSON-serialisable report.
|
|
|
|
``ready`` is True only when every critical check (database, data_dir) passes.
|
|
Harness deployments can also require semantic tool selection by setting
|
|
``ODYSSEUS_REQUIRE_TOOL_INDEX_READY=1``.
|
|
``local_first`` is informational — a remote database is a valid deployment, so
|
|
it never fails readiness, it only reports whether storage stays on this host.
|
|
"""
|
|
from core.constants import APP_VERSION, DATA_DIR
|
|
from core.database import DATABASE_URL, engine
|
|
from sqlalchemy import text as sql_text
|
|
|
|
checks: Dict[str, Dict[str, object]] = {}
|
|
|
|
# Database reachable — the simplest honest probe that the engine is live.
|
|
try:
|
|
with engine.connect() as conn:
|
|
conn.execute(sql_text("SELECT 1"))
|
|
checks["database"] = {"ok": True}
|
|
except Exception as e:
|
|
checks["database"] = {"ok": False, "error": str(e)}
|
|
|
|
# Data directory present and writable — home must be able to hold its own data.
|
|
try:
|
|
os.makedirs(DATA_DIR, exist_ok=True)
|
|
probe = os.path.join(DATA_DIR, f".ready_probe_{uuid.uuid4().hex}")
|
|
with open(probe, "w", encoding="utf-8") as fh:
|
|
fh.write("ok")
|
|
os.remove(probe)
|
|
checks["data_dir"] = {"ok": True, "path": DATA_DIR}
|
|
except Exception as e:
|
|
checks["data_dir"] = {"ok": False, "error": str(e)}
|
|
|
|
# Local-first: storage stays on the home machine (informational, never fatal).
|
|
local_first = (
|
|
DATABASE_URL.startswith("sqlite")
|
|
or "localhost" in DATABASE_URL
|
|
or "127.0.0.1" in DATABASE_URL
|
|
)
|
|
checks["local_first"] = {"ok": True, "local": local_first}
|
|
|
|
# ToolIndex is visible on every readiness response but only gates startup
|
|
# when the deployment requires the semantic agent surface. Product installs
|
|
# can remain available with deterministic tool-selection fallback.
|
|
require_tool_index = str(
|
|
os.environ.get("ODYSSEUS_REQUIRE_TOOL_INDEX_READY", "")
|
|
).strip().lower() in {"1", "true", "yes", "on"}
|
|
try:
|
|
from src.tool_index import get_tool_index_status, tool_index_prewarm_enabled
|
|
|
|
tool_index = get_tool_index_status()
|
|
tool_index["prewarm_enabled"] = tool_index_prewarm_enabled()
|
|
except Exception as e:
|
|
tool_index = {
|
|
"state": "unavailable",
|
|
"ready": False,
|
|
"error_type": type(e).__name__,
|
|
"prewarm_enabled": False,
|
|
}
|
|
tool_index["ok"] = bool(tool_index.get("ready"))
|
|
tool_index["critical"] = require_tool_index
|
|
checks["tool_index"] = tool_index
|
|
|
|
critical_names = ["database", "data_dir"]
|
|
if require_tool_index:
|
|
critical_names.append("tool_index")
|
|
ready = all(bool(checks[name].get("ok")) for name in critical_names)
|
|
return {
|
|
"ready": ready,
|
|
"version": APP_VERSION,
|
|
"checks": checks,
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
}
|