Prepare State Hub retirement baseline
This commit is contained in:
parent
2217bdd9f5
commit
5927591be8
46 changed files with 32583 additions and 62 deletions
|
|
@ -14,6 +14,7 @@ AUTHORIZE_SSH=0
|
|||
ALLOW_PLAINTEXT_STORE=0
|
||||
SKIP_GITEA=0
|
||||
SKIP_MCP=0
|
||||
SKIP_CODEX=0
|
||||
SSH_KEY="${SSH_KEY:-$HOME/.ssh/id_ed25519}"
|
||||
SSH_TARGETS=(
|
||||
"tegwick@92.205.62.239"
|
||||
|
|
@ -39,6 +40,7 @@ Options:
|
|||
--gitea-token TOKEN Gitea token; otherwise prompted when interactive.
|
||||
--skip-gitea Do not create or update ~/.railiance_gitea.conf.
|
||||
--skip-mcp Do not run make register-mcp.
|
||||
--skip-codex Do not configure Codex sandbox networking.
|
||||
-h, --help Show this help.
|
||||
USAGE
|
||||
}
|
||||
|
|
@ -118,6 +120,10 @@ while [ "$#" -gt 0 ]; do
|
|||
SKIP_MCP=1
|
||||
shift
|
||||
;;
|
||||
--skip-codex)
|
||||
SKIP_CODEX=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
|
|
@ -342,6 +348,19 @@ register_mcp() {
|
|||
fi
|
||||
}
|
||||
|
||||
configure_codex() {
|
||||
step "Configuring Codex State Hub access"
|
||||
if [ "$SKIP_CODEX" -eq 1 ]; then
|
||||
warn "Skipping Codex configuration by request."
|
||||
return
|
||||
fi
|
||||
local args=()
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
args+=(--dry-run)
|
||||
fi
|
||||
"$STATE_HUB_DIR/scripts/configure-codex.sh" "${args[@]}"
|
||||
}
|
||||
|
||||
health_check() {
|
||||
step "Checking State Hub reachability"
|
||||
if curl -fsS --max-time 2 "http://127.0.0.1:8000/state/health" >/dev/null 2>&1; then
|
||||
|
|
@ -362,6 +381,7 @@ main() {
|
|||
setup_ssh_key
|
||||
write_gitea_conf
|
||||
register_mcp
|
||||
configure_codex
|
||||
health_check
|
||||
ok "Bootstrap checks complete."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd /home/worsch/state-hub
|
||||
STATE_HUB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
STATE_HUB_PYTHON="${STATE_HUB_PYTHON:-$STATE_HUB_DIR/.venv/bin/python}"
|
||||
cd "$STATE_HUB_DIR"
|
||||
|
||||
API_BASE="${API_BASE:-http://127.0.0.1:8000}"
|
||||
HEALTH_URL="${API_BASE%/}/state/health"
|
||||
|
|
@ -24,6 +26,12 @@ if ! api_healthy; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$STATE_HUB_PYTHON" ]; then
|
||||
echo "state-hub Python is not executable at $STATE_HUB_PYTHON" >&2
|
||||
echo "install the repository environment before starting the Codex MCP server" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export API_BASE
|
||||
export MCP_TRANSPORT=stdio
|
||||
exec uv run python mcp_server/server.py
|
||||
exec "$STATE_HUB_PYTHON" mcp_server/codex_server.py
|
||||
|
|
|
|||
142
scripts/configure-codex.sh
Executable file
142
scripts/configure-codex.sh
Executable file
|
|
@ -0,0 +1,142 @@
|
|||
#!/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
|
||||
|
|
@ -13,8 +13,10 @@
|
|||
|
||||
## State Hub Integration
|
||||
|
||||
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
|
||||
there is no MCP server for Codex agents.
|
||||
The Custodian State Hub tracks work across all domains. Codex uses HTTP REST and
|
||||
the `statehub` CLI by default. MCP is opt-in because the current Codex MCP bridge
|
||||
adds severe call latency; the full administrative MCP surface remains available
|
||||
to clients that need it.
|
||||
|
||||
| Context | URL |
|
||||
|---------|-----|
|
||||
|
|
@ -27,6 +29,14 @@ Queueable writes return an explicit queued receipt if the central hub is
|
|||
unreachable. Treat that as pending local evidence, then ask the operator to run
|
||||
statehub outbox status/replay after connectivity returns.
|
||||
|
||||
Codex workspace-write sandboxes need network access enabled to reach the host's
|
||||
loopback listener. Bootstrap this once with `make -C ~/state-hub configure-codex`
|
||||
and restart Codex. The canonical REST health endpoint is `/state/health`, not
|
||||
`/health`. If a sandboxed loopback probe fails, retry it with escalated execution
|
||||
before declaring State Hub unavailable; a managed Codex permission profile may
|
||||
still enforce isolated networking. Experimental MCP can be enabled explicitly
|
||||
with `make -C ~/state-hub configure-codex WITH_MCP=1`.
|
||||
|
||||
### Orient at session start
|
||||
|
||||
```bash
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue