File size: 11,112 Bytes
116b4cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#!/usr/bin/env bash
set -euo pipefail

BASE_IMAGE="${BASE_IMAGE:-omniroute-local-base}"
CLI_IMAGE="${CLI_IMAGE:-omniroute-local-cli}"

BASE_CONTAINER="${BASE_CONTAINER:-omniroute-cli-runtime-base}"
CLI_CONTAINER="${CLI_CONTAINER:-omniroute-cli-runtime-cli}"
HOST_CONTAINER="${HOST_CONTAINER:-omniroute-cli-runtime-host}"
WRITE_BLOCK_CONTAINER="${WRITE_BLOCK_CONTAINER:-omniroute-cli-runtime-write-block}"
WRITE_ALLOW_CONTAINER="${WRITE_ALLOW_CONTAINER:-omniroute-cli-runtime-write-allow}"
REGRESSION_CONTAINER="${REGRESSION_CONTAINER:-omniroute-cli-runtime-regression}"

BASE_PORT="${BASE_PORT:-20140}"
CLI_PORT="${CLI_PORT:-20141}"
HOST_PORT="${HOST_PORT:-20142}"
WRITE_BLOCK_PORT="${WRITE_BLOCK_PORT:-20143}"
WRITE_ALLOW_PORT="${WRITE_ALLOW_PORT:-20144}"
REGRESSION_PORT="${REGRESSION_PORT:-20145}"

WORKDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ENV_FILE="${WORKDIR}/.env"
FAILURES=0

cleanup() {
  docker rm -f \
    "${BASE_CONTAINER}" \
    "${CLI_CONTAINER}" \
    "${HOST_CONTAINER}" \
    "${WRITE_BLOCK_CONTAINER}" \
    "${WRITE_ALLOW_CONTAINER}" \
    "${REGRESSION_CONTAINER}" >/dev/null 2>&1 || true
  [[ -n "${TMP_HOST_BIN_DIR:-}" ]] && rm -rf "${TMP_HOST_BIN_DIR}" || true
  [[ -n "${TMP_WRITE_HOME:-}" ]] && rm -rf "${TMP_WRITE_HOME}" || true
  [[ -n "${TMP_BAD_BIN_DIR:-}" ]] && rm -rf "${TMP_BAD_BIN_DIR}" || true
}
trap cleanup EXIT

assert_equals() {
  local label="$1"
  local expected="$2"
  local actual="$3"
  if [[ "${expected}" == "${actual}" ]]; then
    echo "      PASS: ${label} -> ${actual}"
  else
    echo "      FAIL: ${label} -> expected=${expected}, got=${actual}"
    FAILURES=$((FAILURES + 1))
  fi
}

wait_ready() {
  local port="$1"
  for _ in $(seq 1 45); do
    if curl -fsS "http://127.0.0.1:${port}/api/settings" >/dev/null 2>&1; then
      return 0
    fi
    sleep 1
  done
  return 1
}

read_json_field() {
  local port="$1"
  local endpoint="$2"
  local jq_filter="$3"
  curl -sS "http://127.0.0.1:${port}${endpoint}" | jq -r "${jq_filter}"
}

echo "[1/8] Building Docker images (runner-base + runner-cli)"
docker build --target runner-base -t "${BASE_IMAGE}" "${WORKDIR}" >/tmp/omniroute_cli_runtime_build_base.log
docker build --target runner-cli -t "${CLI_IMAGE}" "${WORKDIR}" >/tmp/omniroute_cli_runtime_build_cli.log
echo "      Build done."

echo "[2/8] Validating runner-base (no CLIs)"
docker rm -f "${BASE_CONTAINER}" >/dev/null 2>&1 || true
docker run -d --name "${BASE_CONTAINER}" -p "${BASE_PORT}:20128" --env-file "${ENV_FILE}" "${BASE_IMAGE}" >/tmp/omniroute_cli_runtime_base.cid
wait_ready "${BASE_PORT}" || { echo "      FAIL: base container did not become ready"; exit 1; }

for tool in codex claude droid openclaw; do
  INSTALLED="$(read_json_field "${BASE_PORT}" "/api/cli-tools/${tool}-settings" '.installed')"
  RUNNABLE="$(read_json_field "${BASE_PORT}" "/api/cli-tools/${tool}-settings" '.runnable')"
  assert_equals "runner-base ${tool} installed" "false" "${INSTALLED}"
  assert_equals "runner-base ${tool} runnable" "false" "${RUNNABLE}"
done

# Cover guide/runtime-only tools too (cursor, cline, roo, continue)
CURSOR_INSTALLED_BASE="$(read_json_field "${BASE_PORT}" "/api/cli-tools/runtime/cursor" '.installed')"
CURSOR_RUNNABLE_BASE="$(read_json_field "${BASE_PORT}" "/api/cli-tools/runtime/cursor" '.runnable')"
assert_equals "runner-base cursor installed" "false" "${CURSOR_INSTALLED_BASE}"
assert_equals "runner-base cursor runnable" "false" "${CURSOR_RUNNABLE_BASE}"

for tool in cline roo continue; do
  INSTALLED="$(read_json_field "${BASE_PORT}" "/api/cli-tools/runtime/${tool}" '.installed')"
  RUNNABLE="$(read_json_field "${BASE_PORT}" "/api/cli-tools/runtime/${tool}" '.runnable')"
  REASON="$(read_json_field "${BASE_PORT}" "/api/cli-tools/runtime/${tool}" '.reason')"
  assert_equals "runner-base ${tool} installed" "true" "${INSTALLED}"
  assert_equals "runner-base ${tool} runnable" "true" "${RUNNABLE}"
  assert_equals "runner-base ${tool} reason" "not_required" "${REASON}"
done

echo "[3/8] Validating runner-cli (codex/claude/droid/openclaw preinstalled)"
docker rm -f "${CLI_CONTAINER}" >/dev/null 2>&1 || true
docker run -d --name "${CLI_CONTAINER}" -p "${CLI_PORT}:20128" --env-file "${ENV_FILE}" "${CLI_IMAGE}" >/tmp/omniroute_cli_runtime_cli.cid
wait_ready "${CLI_PORT}" || { echo "      FAIL: cli container did not become ready"; exit 1; }

for tool in codex claude droid; do
  INSTALLED="$(read_json_field "${CLI_PORT}" "/api/cli-tools/${tool}-settings" '.installed')"
  RUNNABLE="$(read_json_field "${CLI_PORT}" "/api/cli-tools/${tool}-settings" '.runnable')"
  assert_equals "runner-cli ${tool} installed" "true" "${INSTALLED}"
  assert_equals "runner-cli ${tool} runnable" "true" "${RUNNABLE}"
done
OPENCLAW_INSTALLED="$(read_json_field "${CLI_PORT}" "/api/cli-tools/openclaw-settings" '.installed')"
OPENCLAW_RUNNABLE="$(read_json_field "${CLI_PORT}" "/api/cli-tools/openclaw-settings" '.runnable')"
assert_equals "runner-cli openclaw installed" "true" "${OPENCLAW_INSTALLED}"
assert_equals "runner-cli openclaw runnable" "true" "${OPENCLAW_RUNNABLE}"

CURSOR_INSTALLED_CLI="$(read_json_field "${CLI_PORT}" "/api/cli-tools/runtime/cursor" '.installed')"
CURSOR_RUNNABLE_CLI="$(read_json_field "${CLI_PORT}" "/api/cli-tools/runtime/cursor" '.runnable')"
assert_equals "runner-cli cursor installed" "false" "${CURSOR_INSTALLED_CLI}"
assert_equals "runner-cli cursor runnable" "false" "${CURSOR_RUNNABLE_CLI}"

for tool in cline roo continue; do
  INSTALLED="$(read_json_field "${CLI_PORT}" "/api/cli-tools/runtime/${tool}" '.installed')"
  RUNNABLE="$(read_json_field "${CLI_PORT}" "/api/cli-tools/runtime/${tool}" '.runnable')"
  REASON="$(read_json_field "${CLI_PORT}" "/api/cli-tools/runtime/${tool}" '.reason')"
  assert_equals "runner-cli ${tool} installed" "true" "${INSTALLED}"
  assert_equals "runner-cli ${tool} runnable" "true" "${RUNNABLE}"
  assert_equals "runner-cli ${tool} reason" "not_required" "${REASON}"
done

echo "[4/8] Validating host-style mount detection via CLI_EXTRA_PATHS"
TMP_HOST_BIN_DIR="$(mktemp -d)"
cat >"${TMP_HOST_BIN_DIR}/codex" <<'EOF'
#!/usr/bin/env sh
if [ "${1:-}" = "--version" ] || [ "${1:-}" = "-v" ]; then
  echo "codex-cli host-mount-test"
  exit 0
fi
echo "host-mounted codex stub"
exit 0
EOF
chmod +x "${TMP_HOST_BIN_DIR}/codex"

docker rm -f "${HOST_CONTAINER}" >/dev/null 2>&1 || true
docker run -d --name "${HOST_CONTAINER}" -p "${HOST_PORT}:20128" \
  --env-file "${ENV_FILE}" \
  -e CLI_MODE=host \
  -e CLI_EXTRA_PATHS=/host-cli/bin \
  -v "${TMP_HOST_BIN_DIR}:/host-cli/bin:ro" \
  "${BASE_IMAGE}" >/tmp/omniroute_cli_runtime_host.cid
wait_ready "${HOST_PORT}" || { echo "      FAIL: host-mode container did not become ready"; exit 1; }

HOST_INSTALLED="$(read_json_field "${HOST_PORT}" "/api/cli-tools/codex-settings" '.installed')"
HOST_RUNNABLE="$(read_json_field "${HOST_PORT}" "/api/cli-tools/codex-settings" '.runnable')"
HOST_RUNTIME_MODE="$(read_json_field "${HOST_PORT}" "/api/cli-tools/codex-settings" '.runtimeMode')"
assert_equals "host-mount codex installed" "true" "${HOST_INSTALLED}"
assert_equals "host-mount codex runnable" "true" "${HOST_RUNNABLE}"
assert_equals "host-mount runtimeMode" "host" "${HOST_RUNTIME_MODE}"

echo "[5/8] Validating write policy blocking (CLI_ALLOW_CONFIG_WRITES=false)"
docker rm -f "${WRITE_BLOCK_CONTAINER}" >/dev/null 2>&1 || true
docker run -d --name "${WRITE_BLOCK_CONTAINER}" -p "${WRITE_BLOCK_PORT}:20128" \
  --env-file "${ENV_FILE}" \
  -e CLI_ALLOW_CONFIG_WRITES=false \
  "${CLI_IMAGE}" >/tmp/omniroute_cli_runtime_write_block.cid
wait_ready "${WRITE_BLOCK_PORT}" || { echo "      FAIL: write-block container did not become ready"; exit 1; }

WRITE_BLOCK_POST_CODE="$(
  curl -sS -o /tmp/omniroute_cli_runtime_write_block_post.json -w '%{http_code}' \
    -X POST "http://127.0.0.1:${WRITE_BLOCK_PORT}/api/cli-tools/codex-settings" \
    -H 'Content-Type: application/json' \
    --data '{"baseUrl":"http://localhost:20128","apiKey":"sk_test_key","model":"cc/claude-opus-4-6"}'
)"
WRITE_BLOCK_DELETE_CODE="$(
  curl -sS -o /tmp/omniroute_cli_runtime_write_block_delete.json -w '%{http_code}' \
    -X DELETE "http://127.0.0.1:${WRITE_BLOCK_PORT}/api/cli-tools/codex-settings"
)"
assert_equals "write-block POST codex-settings" "403" "${WRITE_BLOCK_POST_CODE}"
assert_equals "write-block DELETE codex-settings" "403" "${WRITE_BLOCK_DELETE_CODE}"

echo "[6/8] Validating write policy allow + CLI_CONFIG_HOME mount"
TMP_WRITE_HOME="$(mktemp -d)"
docker rm -f "${WRITE_ALLOW_CONTAINER}" >/dev/null 2>&1 || true
docker run -d --name "${WRITE_ALLOW_CONTAINER}" -p "${WRITE_ALLOW_PORT}:20128" \
  --env-file "${ENV_FILE}" \
  -e CLI_ALLOW_CONFIG_WRITES=true \
  -e CLI_CONFIG_HOME=/host-home \
  -v "${TMP_WRITE_HOME}:/host-home" \
  "${CLI_IMAGE}" >/tmp/omniroute_cli_runtime_write_allow.cid
wait_ready "${WRITE_ALLOW_PORT}" || { echo "      FAIL: write-allow container did not become ready"; exit 1; }

WRITE_ALLOW_POST_CODE="$(
  curl -sS -o /tmp/omniroute_cli_runtime_write_allow_post.json -w '%{http_code}' \
    -X POST "http://127.0.0.1:${WRITE_ALLOW_PORT}/api/cli-tools/codex-settings" \
    -H 'Content-Type: application/json' \
    --data '{"baseUrl":"http://localhost:20128","apiKey":"sk_test_key","model":"cc/claude-opus-4-6"}'
)"
assert_equals "write-allow POST codex-settings" "200" "${WRITE_ALLOW_POST_CODE}"

if [[ -f "${TMP_WRITE_HOME}/.codex/config.toml" && -f "${TMP_WRITE_HOME}/.codex/auth.json" ]]; then
  echo "      PASS: codex config/auth written under mounted CLI_CONFIG_HOME"
else
  echo "      FAIL: codex config/auth not written under mounted CLI_CONFIG_HOME"
  FAILURES=$((FAILURES + 1))
fi

WRITE_ALLOW_DELETE_CODE="$(
  curl -sS -o /tmp/omniroute_cli_runtime_write_allow_delete.json -w '%{http_code}' \
    -X DELETE "http://127.0.0.1:${WRITE_ALLOW_PORT}/api/cli-tools/codex-settings"
)"
assert_equals "write-allow DELETE codex-settings" "200" "${WRITE_ALLOW_DELETE_CODE}"

echo "[7/8] Regression: non-executable command must not be runnable=true"
TMP_BAD_BIN_DIR="$(mktemp -d)"
cat >"${TMP_BAD_BIN_DIR}/codex" <<'EOF'
#!/usr/bin/env sh
echo "this should never execute"
EOF
chmod 644 "${TMP_BAD_BIN_DIR}/codex"

docker rm -f "${REGRESSION_CONTAINER}" >/dev/null 2>&1 || true
docker run -d --name "${REGRESSION_CONTAINER}" -p "${REGRESSION_PORT}:20128" \
  --env-file "${ENV_FILE}" \
  -e CLI_CODEX_BIN=/host-bad/codex \
  -v "${TMP_BAD_BIN_DIR}:/host-bad:ro" \
  "${BASE_IMAGE}" >/tmp/omniroute_cli_runtime_regression.cid
wait_ready "${REGRESSION_PORT}" || { echo "      FAIL: regression container did not become ready"; exit 1; }

REGRESSION_RUNNABLE="$(read_json_field "${REGRESSION_PORT}" "/api/cli-tools/codex-settings" '.runnable')"
REGRESSION_REASON="$(read_json_field "${REGRESSION_PORT}" "/api/cli-tools/codex-settings" '.reason')"
assert_equals "regression codex runnable" "false" "${REGRESSION_RUNNABLE}"
assert_equals "regression codex reason" "not_executable" "${REGRESSION_REASON}"

echo "[8/8] Final result"
if [[ "${FAILURES}" -gt 0 ]]; then
  echo "Result: FAILED (${FAILURES} checks failed)"
  exit 1
fi

echo "Result: PASSED (all checks)"