| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| |
| APP_ROOT="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" |
| export APP_ROOT |
| python3 "${APP_ROOT}/scripts/preflight.py" |
|
|
| |
| |
| |
|
|
| : "${MARINE_API_URL:?Missing MARINE_API_URL variable}" |
|
|
| |
| |
| |
| |
| |
| |
|
|
| CODEX_PROVIDER="${CODEX_PROVIDER:-zai}" |
| case "${CODEX_PROVIDER,,}" in |
| auto|glm|zhipu|zai) |
| CODEX_PROVIDER="zai" |
| ;; |
| *) |
| echo "[startup] ERROR: this deployment only supports CODEX_PROVIDER=zai" |
| exit 1 |
| ;; |
| esac |
|
|
| MODEL_API_KEY="${ZAI_API_KEY:-${GLM_API_KEY:-${ZHIPU_API_KEY:-}}}" |
| export OPENAI_BASE_URL="${OPENAI_BASE_URL:-https://open.bigmodel.cn/api/v1}" |
| export CODEX_MODEL="${CODEX_MODEL:-glm-5.3}" |
| export CODEX_PROVIDER_ID="ZAI" |
| export CODEX_PROVIDER_NAME="ZAI" |
|
|
| echo "[startup] Codex provider=${CODEX_PROVIDER_NAME}; model=${CODEX_MODEL}; base_url=${OPENAI_BASE_URL}" |
|
|
| if [ -z "${MODEL_API_KEY}" ]; then |
| echo "[startup] ERROR: no ${CODEX_PROVIDER_NAME} API key was injected." |
| echo "[startup] Expected secret: ZAI_API_KEY (or GLM_API_KEY/ZHIPU_API_KEY)" |
| echo "[startup] Available environment variable names containing KEY/API:" |
| env | cut -d= -f1 | grep -E 'KEY|API' | sort || true |
| exit 1 |
| fi |
|
|
| |
| |
| |
|
|
| export OPENAI_API_KEY="${MODEL_API_KEY}" |
| |
| |
| export CODEX_HARNESS_RUNTIME="${CODEX_HARNESS_RUNTIME:-native}" |
| export REQUIRE_CODEX_HARNESS="${REQUIRE_CODEX_HARNESS:-1}" |
| if [ "${CODEX_HARNESS_RUNTIME}" != "native" ]; then |
| echo "[startup] ERROR: CODEX_HARNESS_RUNTIME must be native" |
| exit 1 |
| fi |
| |
| |
| |
| export CODEX_DISABLE_WEBSOCKETS="${CODEX_DISABLE_WEBSOCKETS:-1}" |
| |
| |
| case "${CODEX_ALLOW_MCP_BYPASS:-0}" in |
| 0|false|FALSE|no|NO|off|OFF|"") ;; |
| *) |
| echo "[startup] ERROR: CODEX_ALLOW_MCP_BYPASS is forbidden for this deployment" |
| exit 1 |
| ;; |
| esac |
| export CODEX_ALLOW_MCP_BYPASS=0 |
| export MARINE_EXPORT_RANGE_MAX_DAYS="${MARINE_EXPORT_RANGE_MAX_DAYS:-31}" |
|
|
| |
| |
| |
|
|
| export PORT="${PORT:-7860}" |
| export FISHERIES_EXPORT_ROOT="${FISHERIES_EXPORT_ROOT:-/tmp/squid_fisheries_exports}" |
| export HF_FISHERIES_CACHE_ROOT="${HF_FISHERIES_CACHE_ROOT:-/tmp/squid_hf_fisheries_cache}" |
| if [ -z "${PUBLIC_BASE_URL:-}" ] && [ -n "${SPACE_HOST:-}" ]; then |
| export PUBLIC_BASE_URL="https://${SPACE_HOST}" |
| fi |
|
|
| |
| |
| |
|
|
| RUNTIME_MCP_CONFIG="/tmp/codex-marine-mcp.json" |
| export RUNTIME_MCP_CONFIG |
|
|
| python3 - <<'PY_MCP_CONFIG' |
| import json |
| import os |
| from pathlib import Path |
|
|
| env = { |
| "MARINE_API_URL": os.environ["MARINE_API_URL"], |
| "HF_SQUID_DATASET_REPO": ( |
| os.environ.get("HF_SQUID_DATASET_REPO") |
| or os.environ.get("HF_DATASET_REPO") |
| or "globalsquiddatabase/squid_dataset" |
| ), |
| "HF_TUNA_DATASET_REPO": ( |
| os.environ.get("HF_TUNA_DATASET_REPO") |
| or "globalsquiddatabase/Tuna-Fisheries-Dataset" |
| ), |
| "FISHERIES_EXPORT_ROOT": os.environ.get( |
| "FISHERIES_EXPORT_ROOT", "/tmp/squid_fisheries_exports" |
| ), |
| "HF_FISHERIES_CACHE_ROOT": os.environ.get( |
| "HF_FISHERIES_CACHE_ROOT", "/tmp/squid_hf_fisheries_cache" |
| ), |
| "HF_SQUID_DATASET_REVISION": ( |
| os.environ.get("HF_SQUID_DATASET_REVISION") |
| or os.environ.get("HF_DATASET_REVISION", "") |
| ), |
| "HF_TUNA_DATASET_REVISION": os.environ.get("HF_TUNA_DATASET_REVISION", ""), |
| "PUBLIC_BASE_URL": os.environ.get("PUBLIC_BASE_URL", ""), |
| "MARINE_PUBLIC_BASE_URL": os.environ.get("MARINE_PUBLIC_BASE_URL", ""), |
| "MARINE_EXPORT_RANGE_MAX_DAYS": os.environ.get("MARINE_EXPORT_RANGE_MAX_DAYS", "31"), |
| } |
|
|
| hf_token = os.environ.get("HF_TOKEN", "").strip() |
| if hf_token: |
| env["HF_TOKEN"] = hf_token |
| for name in ("LOCAL_SQUID_DATA_ROOT", "LOCAL_TUNA_DATA_ROOT"): |
| if os.environ.get(name, "").strip(): |
| env[name] = os.environ[name] |
|
|
| cfg = { |
| "timeouts": { |
| "connect_timeout": 15, |
| "execute_timeout": 900, |
| "read_timeout": 900, |
| }, |
| "servers": { |
| "marine": { |
| "command": "python3", |
| "args": [os.path.join(os.environ["APP_ROOT"], "marine_mcp.py")], |
| "disabled": False, |
| "required": False, |
| "env": env, |
| } |
| }, |
| } |
|
|
| p = Path(os.environ["RUNTIME_MCP_CONFIG"]) |
| p.write_text( |
| json.dumps(cfg, ensure_ascii=False, indent=2) + "\n", |
| encoding="utf-8", |
| ) |
| p.chmod(0o600) |
|
|
| print("[startup] private Marine MCP runtime config ready") |
| PY_MCP_CONFIG |
|
|
| |
| |
| export CODEX_HOME="${CODEX_HOME:-${HOME:-/tmp}/.codex-home}" |
| mkdir -p "${CODEX_HOME}" |
| python3 - <<'PY_CODEX_CONFIG' |
| import json |
| import os |
| from pathlib import Path |
|
|
| def q(value): |
| return json.dumps(str(value), ensure_ascii=False) |
|
|
| home = Path(os.environ["CODEX_HOME"]) |
| home.mkdir(parents=True, exist_ok=True) |
| marine_env = { |
| "MARINE_API_URL": os.environ["MARINE_API_URL"], |
| "HF_SQUID_DATASET_REPO": os.environ.get("HF_SQUID_DATASET_REPO", "globalsquiddatabase/squid_dataset"), |
| "HF_TUNA_DATASET_REPO": os.environ.get("HF_TUNA_DATASET_REPO", "globalsquiddatabase/Tuna-Fisheries-Dataset"), |
| "FISHERIES_EXPORT_ROOT": os.environ.get("FISHERIES_EXPORT_ROOT", "/tmp/squid_fisheries_exports"), |
| "HF_FISHERIES_CACHE_ROOT": os.environ.get("HF_FISHERIES_CACHE_ROOT", "/tmp/squid_hf_fisheries_cache"), |
| "PUBLIC_BASE_URL": os.environ.get("PUBLIC_BASE_URL", ""), |
| "MARINE_PUBLIC_BASE_URL": os.environ.get("MARINE_PUBLIC_BASE_URL", ""), |
| "MARINE_EXPORT_RANGE_MAX_DAYS": os.environ.get("MARINE_EXPORT_RANGE_MAX_DAYS", "31"), |
| } |
| for name in ("HF_TOKEN", "HF_SQUID_DATASET_REVISION", "HF_TUNA_DATASET_REVISION"): |
| if os.environ.get(name, "").strip(): |
| marine_env[name] = os.environ[name] |
| for name in ("LOCAL_SQUID_DATA_ROOT", "LOCAL_TUNA_DATA_ROOT"): |
| if os.environ.get(name, "").strip(): |
| marine_env[name] = os.environ[name] |
|
|
| auth_line = ( |
| "experimental_bearer_token = " + q(os.environ["OPENAI_API_KEY"]) |
| if os.environ.get("CODEX_PROVIDER") == "zai" |
| else "env_key = \"OPENAI_API_KEY\"" |
| ) |
|
|
| lines = [ |
| "model_provider = " + q(os.environ["CODEX_PROVIDER_ID"]), |
| "model = " + q(os.environ.get("CODEX_MODEL", "glm-5.3")), |
| "model_reasoning_effort = " + q(os.environ.get("CODEX_REASONING_EFFORT", "max")), |
| "cli_auth_credentials_store = \"file\"", |
| "", |
| "[model_providers." + os.environ["CODEX_PROVIDER_ID"] + "]", |
| "name = " + q(os.environ["CODEX_PROVIDER_NAME"]), |
| "base_url = " + q(os.environ.get("OPENAI_BASE_URL", "https://open.bigmodel.cn/api/v1")), |
| auth_line, |
| "requires_openai_auth = false", |
| "wire_api = \"responses\"", |
| "supports_websockets = false", |
| "", |
| "[mcp_servers.marine]", |
| "command = \"python3\"", |
| "args = [" + q(os.path.join(os.environ["APP_ROOT"], "marine_mcp.py")) + "]", |
| "required = false", |
| |
| |
| |
| |
| "default_tools_approval_mode = \"approve\"", |
| "startup_timeout_sec = 30", |
| "tool_timeout_sec = 900", |
| "env = { " + ", ".join(f"{k} = {q(v)}" for k, v in marine_env.items()) + " }", |
| ] |
| |
| if os.environ.get("CODEX_MODEL", "").strip(): |
| catalog = { |
| "models": [{ |
| "slug": os.environ.get("CODEX_MODEL", "glm-5.3"), |
| "display_name": os.environ.get("CODEX_MODEL", "glm-5.3"), |
| "description": ( |
| os.environ.get("CODEX_PROVIDER_NAME", "OpenAI-compatible") |
| + " model" |
| ), |
| "base_instructions": "You are a helpful coding assistant. Use the configured Marine MCP tools when the user asks for marine data.", |
| "model_messages": { |
| "instructions_template": "You are a helpful coding assistant. Follow the user's request and use available tools when needed." |
| }, |
| "default_reasoning_level": os.environ.get("CODEX_REASONING_EFFORT", "max"), |
| "supported_reasoning_levels": [ |
| {"effort": "low", "description": "Light reasoning"}, |
| {"effort": "medium", "description": "Balanced reasoning"}, |
| {"effort": "high", "description": "Deep reasoning"}, |
| {"effort": "max", "description": "Maximum reasoning"}, |
| ], |
| |
| |
| |
| "shell_type": "shell_command", |
| "visibility": "list", |
| "supported_in_api": True, |
| "priority": 1, |
| "truncation_policy": {"mode": "tokens", "limit": 204800}, |
| "supports_reasoning_summaries": True, |
| "default_reasoning_summary": "none", |
| "support_verbosity": False, |
| "default_verbosity": None, |
| "context_window": 204800, |
| "max_context_window": 204800, |
| "effective_context_window_percent": 95, |
| "supports_parallel_tool_calls": True, |
| "experimental_supported_tools": [], |
| "input_modalities": ["text"], |
| }] |
| } |
| (home / "models.json").write_text( |
| json.dumps(catalog, ensure_ascii=False, indent=2) + "\n", |
| encoding="utf-8", |
| ) |
| lines.insert(3, "model_catalog_json = " + q(str(home / "models.json"))) |
| path = home / "config.toml" |
| path.write_text("\n".join(lines) + "\n", encoding="utf-8") |
| path.chmod(0o600) |
| print(f"[startup] official Codex config ready: {path}") |
| PY_CODEX_CONFIG |
|
|
| |
| |
| |
|
|
| if ! curl -fsS --max-time 12 "${MARINE_API_URL%/}/health"; then |
| echo "[startup] WARNING: Marine API health check failed. Chat remains available; Ocean tools will be unavailable until MARINE_API_URL is corrected." |
| fi |
| echo |
|
|
| |
| |
| |
|
|
| exec uvicorn ui_server:app \ |
| --host 0.0.0.0 \ |
| --port "${PORT}" \ |
| --proxy-headers |
|
|