chore: import upstream snapshot with attribution
This commit is contained in:
Executable
+240
@@ -0,0 +1,240 @@
|
||||
#!/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)"
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Defaults requested
|
||||
BASE_URL="${BASE_URL:-https://omniroute.com/v1}"
|
||||
MODEL="${MODEL:-kr/claude-sonnet-4.5}"
|
||||
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-45}"
|
||||
API_KEY="${API_KEY:-${CLOUD_API_KEY:-${OPENAI_API_KEY:-}}}"
|
||||
|
||||
ENDPOINT="${BASE_URL%/}/chat/completions"
|
||||
|
||||
if [[ -z "${API_KEY}" ]]; then
|
||||
echo "[cloud-test] FAIL: API key not configured."
|
||||
echo "[cloud-test] Set one of: API_KEY, CLOUD_API_KEY, OPENAI_API_KEY"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "[cloud-test] Endpoint: ${ENDPOINT}"
|
||||
echo "[cloud-test] Model: ${MODEL}"
|
||||
echo "[cloud-test] API key: ${API_KEY:0:8}...${API_KEY: -6}"
|
||||
|
||||
PAYLOAD=$(cat <<JSON
|
||||
{
|
||||
"model": "${MODEL}",
|
||||
"stream": false,
|
||||
"messages": [
|
||||
{ "role": "user", "content": "Reply with exactly: CLOUD_OK" }
|
||||
],
|
||||
"max_tokens": 64
|
||||
}
|
||||
JSON
|
||||
)
|
||||
|
||||
HTTP_CODE=$(curl -sS -m "${TIMEOUT_SECONDS}" \
|
||||
-o /tmp/omniroute_cloud_test_response.json \
|
||||
-w "%{http_code}" \
|
||||
-X POST "${ENDPOINT}" \
|
||||
-H "Authorization: Bearer ${API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${PAYLOAD}" || true)
|
||||
|
||||
echo "[cloud-test] HTTP status: ${HTTP_CODE}"
|
||||
echo "[cloud-test] Response body:"
|
||||
cat /tmp/omniroute_cloud_test_response.json || true
|
||||
echo
|
||||
|
||||
if [[ "${HTTP_CODE}" =~ ^2 ]]; then
|
||||
echo "[cloud-test] PASS"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[cloud-test] FAIL"
|
||||
exit 1
|
||||
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
LOCAL_BASE_URL="${LOCAL_BASE_URL:-http://127.0.0.1:20128}"
|
||||
CLOUD_BASE_URL="${CLOUD_BASE_URL:-https://omniroute.com/v1}"
|
||||
MODEL="${MODEL:-kr/claude-sonnet-4.5}"
|
||||
STREAM_MODE="${STREAM_MODE:-false}"
|
||||
MAX_RETRIES="${MAX_RETRIES:-6}"
|
||||
RETRY_DELAY_SECONDS="${RETRY_DELAY_SECONDS:-3}"
|
||||
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-45}"
|
||||
|
||||
LOCAL_KEYS_URL="${LOCAL_BASE_URL%/}/api/keys"
|
||||
LOCAL_SYNC_URL="${LOCAL_BASE_URL%/}/api/sync/cloud"
|
||||
CLOUD_CHAT_URL="${CLOUD_BASE_URL%/}/chat/completions"
|
||||
|
||||
echo "[1/5] Creating local API key"
|
||||
KEY_NAME="cloud-e2e-$(date +%s)"
|
||||
CREATE_RESP="$(curl -sS -m "${TIMEOUT_SECONDS}" -X POST "${LOCAL_KEYS_URL}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "{\"name\":\"${KEY_NAME}\"}")"
|
||||
|
||||
API_KEY="$(echo "${CREATE_RESP}" | jq -r '.key // empty')"
|
||||
if [[ -z "${API_KEY}" || "${API_KEY}" == "null" ]]; then
|
||||
echo "FAIL: could not create local API key"
|
||||
echo "Response: ${CREATE_RESP}"
|
||||
exit 1
|
||||
fi
|
||||
echo " Key created: ${API_KEY:0:12}...${API_KEY: -6}"
|
||||
|
||||
echo "[2/5] Enabling cloud mode"
|
||||
ENABLE_CODE="$(curl -sS -m "${TIMEOUT_SECONDS}" -o /tmp/cloud_enable_e2e.json -w '%{http_code}' \
|
||||
-X POST "${LOCAL_SYNC_URL}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"action":"enable"}' || true)"
|
||||
echo " enable status: ${ENABLE_CODE}"
|
||||
cat /tmp/cloud_enable_e2e.json; echo
|
||||
if [[ ! "${ENABLE_CODE}" =~ ^2 ]]; then
|
||||
echo "FAIL: cloud enable failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/5] Running explicit sync"
|
||||
SYNC_CODE="$(curl -sS -m "${TIMEOUT_SECONDS}" -o /tmp/cloud_sync_e2e.json -w '%{http_code}' \
|
||||
-X POST "${LOCAL_SYNC_URL}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"action":"sync"}' || true)"
|
||||
echo " sync status: ${SYNC_CODE}"
|
||||
cat /tmp/cloud_sync_e2e.json; echo
|
||||
if [[ ! "${SYNC_CODE}" =~ ^2 ]]; then
|
||||
echo "FAIL: cloud sync failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_payload() {
|
||||
local stream_flag="$1"
|
||||
cat <<JSON
|
||||
{
|
||||
"model": "${MODEL}",
|
||||
"stream": ${stream_flag},
|
||||
"messages": [
|
||||
{"role":"user","content":"Reply with exactly: CLOUD_OK"}
|
||||
],
|
||||
"max_tokens": 64
|
||||
}
|
||||
JSON
|
||||
}
|
||||
|
||||
PAYLOAD="$(build_payload "${STREAM_MODE}")"
|
||||
|
||||
echo "[4/5] Testing cloud OpenAI-compatible endpoint with retry (stream=${STREAM_MODE})"
|
||||
SUCCESS=0
|
||||
for ATTEMPT in $(seq 1 "${MAX_RETRIES}"); do
|
||||
CODE="$(curl -sS -m "${TIMEOUT_SECONDS}" -o /tmp/cloud_chat_e2e.json -w '%{http_code}' \
|
||||
-X POST "${CLOUD_CHAT_URL}" \
|
||||
-H "Authorization: Bearer ${API_KEY}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "${PAYLOAD}" || true)"
|
||||
echo " attempt ${ATTEMPT}/${MAX_RETRIES}: HTTP ${CODE}"
|
||||
cat /tmp/cloud_chat_e2e.json; echo
|
||||
|
||||
if [[ "${CODE}" =~ ^2 ]]; then
|
||||
SUCCESS=1
|
||||
break
|
||||
fi
|
||||
|
||||
sleep "${RETRY_DELAY_SECONDS}"
|
||||
done
|
||||
|
||||
echo "[5/5] Result"
|
||||
if [[ "${SUCCESS}" -eq 1 ]]; then
|
||||
echo "PASS: cloud endpoint accepted synced key"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Primary test failed. Trying fallback with stream=true to distinguish key/auth issues..."
|
||||
PAYLOAD_STREAM_TRUE="$(build_payload "true")"
|
||||
FALLBACK_CODE="$(curl -sS -m "${TIMEOUT_SECONDS}" -o /tmp/cloud_chat_e2e_fallback.json -w '%{http_code}' \
|
||||
-X POST "${CLOUD_CHAT_URL}" \
|
||||
-H "Authorization: Bearer ${API_KEY}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data "${PAYLOAD_STREAM_TRUE}" || true)"
|
||||
echo " fallback stream=true HTTP ${FALLBACK_CODE}"
|
||||
cat /tmp/cloud_chat_e2e_fallback.json; echo
|
||||
|
||||
if [[ "${FALLBACK_CODE}" =~ ^2 ]]; then
|
||||
echo "PARTIAL PASS: API key is valid and cloud works with stream=true; stream=false path appears broken upstream."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "FAIL: cloud endpoint still failing after sync/retries (including stream=true fallback)"
|
||||
exit 1
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE_NAME="${1:-omniroute-local-hardened}"
|
||||
CONTAINER_NAME="${2:-omniroute-hardening-test}"
|
||||
HOST_PORT="${3:-20129}"
|
||||
|
||||
WORKDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
ENV_FILE="${WORKDIR}/.env"
|
||||
|
||||
INITIAL_PASSWORD="$(sed -n 's/^INITIAL_PASSWORD=//p' "${ENV_FILE}" | head -n1)"
|
||||
if [[ -z "${INITIAL_PASSWORD}" ]]; then
|
||||
INITIAL_PASSWORD="123456"
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "[1/6] Building image: ${IMAGE_NAME}"
|
||||
docker build -t "${IMAGE_NAME}" "${WORKDIR}" >/tmp/omniroute_hardening_build.log
|
||||
echo " Build done."
|
||||
|
||||
echo "[2/6] Starting test container: ${CONTAINER_NAME} on :${HOST_PORT}"
|
||||
docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true
|
||||
docker run -d \
|
||||
--name "${CONTAINER_NAME}" \
|
||||
-p "${HOST_PORT}:20128" \
|
||||
--env-file "${ENV_FILE}" \
|
||||
-e REQUIRE_API_KEY=true \
|
||||
-e AUTH_COOKIE_SECURE=true \
|
||||
-e DATA_DIR=/app/data \
|
||||
"${IMAGE_NAME}" >/tmp/omniroute_hardening_container_id.txt
|
||||
|
||||
echo "[3/6] Waiting for service..."
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -fsS "http://127.0.0.1:${HOST_PORT}/api/settings" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
BASE_URL="http://127.0.0.1:${HOST_PORT}"
|
||||
FAILURES=0
|
||||
|
||||
assert_status() {
|
||||
local label="$1"
|
||||
local expected="$2"
|
||||
local actual="$3"
|
||||
if [[ "${actual}" == "${expected}" ]]; then
|
||||
echo " PASS: ${label} -> ${actual}"
|
||||
else
|
||||
echo " FAIL: ${label} -> got ${actual}, expected ${expected}"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local label="$1"
|
||||
local haystack="$2"
|
||||
local needle="$3"
|
||||
local matched=1
|
||||
|
||||
# Prefer rg when available, fallback to grep for portability
|
||||
if command -v rg >/dev/null 2>&1; then
|
||||
echo "${haystack}" | rg -qi "${needle}" && matched=0 || matched=1
|
||||
else
|
||||
echo "${haystack}" | grep -Eiq "${needle}" && matched=0 || matched=1
|
||||
fi
|
||||
|
||||
if [[ "${matched}" -eq 0 ]]; then
|
||||
echo " PASS: ${label}"
|
||||
return
|
||||
fi
|
||||
|
||||
echo " FAIL: ${label} (missing '${needle}')"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
echo "[4/6] Validating cloud auth guardrails"
|
||||
S1="$(curl -s -o /tmp/hardening_cloud_noauth.json -w '%{http_code}' -X POST "${BASE_URL}/api/cloud/auth")"
|
||||
assert_status "/api/cloud/auth without token" "401" "${S1}"
|
||||
|
||||
S2="$(curl -s -o /tmp/hardening_cloud_bad.json -w '%{http_code}' -X POST "${BASE_URL}/api/cloud/auth" -H 'Authorization: Bearer sk-invalid')"
|
||||
assert_status "/api/cloud/auth invalid token" "401" "${S2}"
|
||||
|
||||
echo "[5/6] Validating strict /v1 API key mode"
|
||||
S3="$(curl -s -o /tmp/hardening_v1_noauth.json -w '%{http_code}' -X POST "${BASE_URL}/v1/chat/completions" -H 'Content-Type: application/json' --data '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}')"
|
||||
assert_status "/v1/chat/completions without token" "401" "${S3}"
|
||||
|
||||
S4="$(curl -s -o /tmp/hardening_v1_bad.json -w '%{http_code}' -X POST "${BASE_URL}/v1/chat/completions" -H 'Authorization: Bearer sk-invalid' -H 'Content-Type: application/json' --data '{"model":"openai/gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}')"
|
||||
assert_status "/v1/chat/completions invalid token" "401" "${S4}"
|
||||
|
||||
KEY_JSON="$(curl -s -X POST "${BASE_URL}/api/keys" -H 'Content-Type: application/json' --data '{"name":"hardening-test-key"}')"
|
||||
API_KEY="$(echo "${KEY_JSON}" | jq -r '.key // empty')"
|
||||
if [[ -z "${API_KEY}" || "${API_KEY}" == "null" ]]; then
|
||||
echo " FAIL: Could not create test API key"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
else
|
||||
S5="$(curl -s -o /tmp/hardening_v1_good.json -w '%{http_code}' -X POST "${BASE_URL}/v1/chat/completions" -H "Authorization: Bearer ${API_KEY}" -H 'Content-Type: application/json' --data '{"model":"foo/bar","messages":[{"role":"user","content":"ping"}]}')"
|
||||
if [[ "${S5}" == "401" ]]; then
|
||||
echo " FAIL: /v1/chat/completions valid token still unauthorized"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
else
|
||||
echo " PASS: /v1/chat/completions valid token accepted auth layer (${S5})"
|
||||
fi
|
||||
|
||||
S6="$(curl -s -o /tmp/hardening_cloud_good.json -w '%{http_code}' -X POST "${BASE_URL}/api/cloud/auth" -H "Authorization: Bearer ${API_KEY}")"
|
||||
assert_status "/api/cloud/auth valid token" "200" "${S6}"
|
||||
fi
|
||||
|
||||
echo "[6/6] Validating secure cookie behavior"
|
||||
LOGIN_HEADERS="$(curl -s -i -X POST "${BASE_URL}/api/auth/login" -H 'x-forwarded-proto: https' -H 'Content-Type: application/json' --data "{\"password\":\"${INITIAL_PASSWORD}\"}" || true)"
|
||||
assert_contains "login response sets secure auth cookie" "${LOGIN_HEADERS}" "set-cookie: auth_token=.*secure"
|
||||
|
||||
if [[ "${FAILURES}" -gt 0 ]]; then
|
||||
echo
|
||||
echo "Result: FAILED (${FAILURES} checks failed)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Result: PASSED (all checks)"
|
||||
Reference in New Issue
Block a user