#!/usr/bin/env bash set -euo pipefail CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}" CONFIG_PATH="${CODEX_CONFIG_PATH:-$CODEX_HOME_DIR/config.toml}" API_BASE="${API_BASE:-http://127.0.0.1:8000}" DRY_RUN=0 SKIP_VERIFY=0 MCP_MODE="remove" MCP_NAME="${STATE_HUB_MCP_NAME:-dev-hub}" MCP_COMMAND="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/codex-state-hub-mcp.sh" usage() { cat <<'USAGE' Usage: scripts/configure-codex.sh [options] Enable direct network access for Codex workspace-write sandboxes and verify that the sandbox can reach the local State Hub. Options: --codex-home DIR Codex home containing config.toml. Default: $CODEX_HOME or ~/.codex. --api-base URL State Hub API base. Default: http://127.0.0.1:8000. --dry-run Show the configuration change without writing it. --skip-verify Do not run the sandboxed State Hub health check. --with-mcp Opt in to the experimental slim State Hub MCP server. --skip-mcp Do not add or remove a State Hub MCP registration. -h, --help Show this help. USAGE } while [ "$#" -gt 0 ]; do case "$1" in --codex-home) test -n "${2:-}" || { echo "ERROR: --codex-home requires a directory" >&2; exit 2; } CODEX_HOME_DIR="$2" CONFIG_PATH="$CODEX_HOME_DIR/config.toml" shift 2 ;; --api-base) test -n "${2:-}" || { echo "ERROR: --api-base requires a URL" >&2; exit 2; } API_BASE="$2" shift 2 ;; --dry-run) DRY_RUN=1; shift ;; --skip-verify) SKIP_VERIFY=1; shift ;; --with-mcp) MCP_MODE="add"; shift ;; --skip-mcp) MCP_MODE="skip"; shift ;; -h|--help) usage; exit 0 ;; *) echo "ERROR: unknown argument: $1" >&2; usage >&2; exit 2 ;; esac done python3 - "$CONFIG_PATH" "$DRY_RUN" <<'PY' import re import sys import tomllib from pathlib import Path path = Path(sys.argv[1]) dry_run = sys.argv[2] == "1" original = path.read_text(encoding="utf-8") if path.exists() else "" if original: tomllib.loads(original) header = "[sandbox_workspace_write]" lines = original.splitlines() start = next((i for i, line in enumerate(lines) if line.strip() == header), None) if start is None: if lines and lines[-1].strip(): lines.append("") lines.extend([header, "network_access = true"]) else: end = next( (i for i in range(start + 1, len(lines)) if re.match(r"^\s*\[", lines[i])), len(lines), ) setting = next( (i for i in range(start + 1, end) if re.match(r"^\s*network_access\s*=", lines[i])), None, ) if setting is None: lines.insert(end, "network_access = true") else: lines[setting] = "network_access = true" updated = "\n".join(lines).rstrip() + "\n" tomllib.loads(updated) if updated == original: print(f"OK: {path} already enables sandbox workspace-write network access") elif dry_run: print(f"DRY-RUN: would enable sandbox_workspace_write.network_access in {path}") else: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_suffix(path.suffix + ".tmp") temporary.write_text(updated, encoding="utf-8") temporary.chmod(0o600) temporary.replace(path) print(f"OK: enabled sandbox_workspace_write.network_access in {path}") PY if [ "$MCP_MODE" = "add" ]; then if [ "$DRY_RUN" -eq 1 ]; then echo "DRY-RUN: would register Codex MCP server $MCP_NAME -> $MCP_COMMAND" elif ! command -v codex >/dev/null 2>&1; then echo "WARN: codex is not on PATH; State Hub MCP registration was skipped." >&2 elif CODEX_HOME="$CODEX_HOME_DIR" codex mcp get "$MCP_NAME" >/dev/null 2>&1; then echo "OK: Codex MCP server $MCP_NAME is already registered" else CODEX_HOME="$CODEX_HOME_DIR" codex mcp add "$MCP_NAME" -- "$MCP_COMMAND" echo "OK: registered Codex MCP server $MCP_NAME -> $MCP_COMMAND" fi elif [ "$MCP_MODE" = "remove" ]; then if [ "$DRY_RUN" -eq 1 ]; then echo "DRY-RUN: would remove Codex MCP server $MCP_NAME if registered" elif command -v codex >/dev/null 2>&1 \ && CODEX_HOME="$CODEX_HOME_DIR" codex mcp get "$MCP_NAME" >/dev/null 2>&1; then CODEX_HOME="$CODEX_HOME_DIR" codex mcp remove "$MCP_NAME" echo "OK: removed Codex MCP server $MCP_NAME; use REST/statehub CLI by default" else echo "OK: Codex MCP server $MCP_NAME is not registered" fi fi if [ "$DRY_RUN" -eq 1 ] || [ "$SKIP_VERIFY" -eq 1 ]; then exit 0 fi if ! command -v codex >/dev/null 2>&1; then echo "WARN: codex is not on PATH; configuration was written but not verified." >&2 exit 0 fi HEALTH_URL="${API_BASE%/}/state/health" if timeout 10 codex sandbox -- curl -fsS --max-time 3 "$HEALTH_URL" >/dev/null 2>&1; then echo "OK: Codex sandbox can reach State Hub at $HEALTH_URL" else echo "WARN: Codex sandbox still cannot reach $HEALTH_URL." >&2 echo "WARN: A managed permission profile may enforce restricted networking/--unshare-net." >&2 echo "WARN: Retry State Hub REST/CLI commands with escalated execution or change the managed profile." >&2 fi