From 9d1b443542d58267c7570e27a5c00f4fb46f3666 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 31 Mar 2026 20:34:45 +0100 Subject: [PATCH] fix: harden live docker auth harness --- scripts/e2e/Dockerfile.qr-import | 2 +- scripts/e2e/openwebui-docker.sh | 37 +++++---- scripts/lib/live-docker-auth.sh | 55 +++++++++++-- scripts/test-live-acp-bind-docker.sh | 72 +++++++++++++++-- scripts/test-live-cli-backend-docker.sh | 90 ++++++++++++++++++---- scripts/test-live-gateway-models-docker.sh | 74 ++++++++++++++---- scripts/test-live-models-docker.sh | 79 +++++++++++++++---- test/test-env.ts | 25 ++++++ 8 files changed, 359 insertions(+), 75 deletions(-) diff --git a/scripts/e2e/Dockerfile.qr-import b/scripts/e2e/Dockerfile.qr-import index 4b572a705b3..0be3a09ef82 100644 --- a/scripts/e2e/Dockerfile.qr-import +++ b/scripts/e2e/Dockerfile.qr-import @@ -21,6 +21,6 @@ COPY --chown=appuser:appuser patches ./patches # Keep the pre-install copy set limited to the manifests needed for root # workspace resolution so unrelated extension edits do not bust the layer. RUN --mount=type=cache,id=openclaw-pnpm-store,target=/home/appuser/.local/share/pnpm/store,sharing=locked \ - pnpm install --frozen-lockfile + pnpm install --frozen-lockfile --ignore-scripts COPY --chown=appuser:appuser . . diff --git a/scripts/e2e/openwebui-docker.sh b/scripts/e2e/openwebui-docker.sh index 67fc81dd84b..c1c3c565ac6 100755 --- a/scripts/e2e/openwebui-docker.sh +++ b/scripts/e2e/openwebui-docker.sh @@ -31,15 +31,20 @@ if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) fi -AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" +AUTH_DIRS_CSV="" +if ((${#AUTH_DIRS[@]} > 0)); then + AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" +fi EXTERNAL_AUTH_MOUNTS=() -for auth_dir in "${AUTH_DIRS[@]}"; do - host_path="$HOME/$auth_dir" - if [[ -d "$host_path" ]]; then - EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) - fi -done +if ((${#AUTH_DIRS[@]} > 0)); then + for auth_dir in "${AUTH_DIRS[@]}"; do + host_path="$HOME/$auth_dir" + if [[ -d "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) + fi + done +fi cleanup() { docker rm -f "$OW_NAME" >/dev/null 2>&1 || true @@ -75,14 +80,16 @@ docker run -d \ set -euo pipefail [ -f "$HOME/.profile" ] && source "$HOME/.profile" || true IFS="," read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" - for auth_dir in "${auth_dirs[@]}"; do - [ -n "$auth_dir" ] || continue - if [ -d "/host-auth/$auth_dir" ]; then - mkdir -p "$HOME/$auth_dir" - cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" - chmod -R u+rwX "$HOME/$auth_dir" || true - fi - done + if ((${#auth_dirs[@]} > 0)); then + for auth_dir in "${auth_dirs[@]}"; do + [ -n "$auth_dir" ] || continue + if [ -d "/host-auth/$auth_dir" ]; then + mkdir -p "$HOME/$auth_dir" + cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" + chmod -R u+rwX "$HOME/$auth_dir" || true + fi + done + fi entry=dist/index.mjs [ -f "$entry" ] || entry=dist/index.js diff --git a/scripts/lib/live-docker-auth.sh b/scripts/lib/live-docker-auth.sh index b4cd3b7fdba..eb4f9616a0d 100644 --- a/scripts/lib/live-docker-auth.sh +++ b/scripts/lib/live-docker-auth.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash OPENCLAW_DOCKER_LIVE_AUTH_ALL=(.claude .codex .minimax) +OPENCLAW_DOCKER_LIVE_AUTH_FILES_ALL=(.claude.json) openclaw_live_trim() { local value="${1:-}" @@ -33,21 +34,26 @@ openclaw_live_should_include_auth_dir_for_provider() { esac } +openclaw_live_should_include_auth_file_for_provider() { + local provider + provider="$(openclaw_live_trim "${1:-}")" + case "$provider" in + anthropic | claude-cli) + printf '%s\n' ".claude.json" + ;; + esac +} + openclaw_live_collect_auth_dirs_from_csv() { local raw="${1:-}" local token normalized - local -A seen=() [[ -n "$(openclaw_live_trim "$raw")" ]] || return 0 IFS=',' read -r -a tokens <<<"$raw" for token in "${tokens[@]}"; do while IFS= read -r normalized; do - [[ -n "$normalized" ]] || continue - if [[ -z "${seen[$normalized]:-}" ]]; then - printf '%s\n' "$normalized" - seen[$normalized]=1 - fi + printf '%s\n' "$normalized" done < <(openclaw_live_should_include_auth_dir_for_provider "$token") - done + done | awk 'NF && !seen[$0]++' } openclaw_live_collect_auth_dirs_from_override() { @@ -78,6 +84,41 @@ openclaw_live_collect_auth_dirs() { printf '%s\n' "${OPENCLAW_DOCKER_LIVE_AUTH_ALL[@]}" } +openclaw_live_collect_auth_files_from_csv() { + local raw="${1:-}" + local token normalized + [[ -n "$(openclaw_live_trim "$raw")" ]] || return 0 + IFS=',' read -r -a tokens <<<"$raw" + for token in "${tokens[@]}"; do + while IFS= read -r normalized; do + printf '%s\n' "$normalized" + done < <(openclaw_live_should_include_auth_file_for_provider "$token") + done | awk 'NF && !seen[$0]++' +} + +openclaw_live_collect_auth_files_from_override() { + local raw + raw="$(openclaw_live_trim "${OPENCLAW_DOCKER_AUTH_DIRS:-}")" + [[ -n "$raw" ]] || return 1 + case "$raw" in + all) + printf '%s\n' "${OPENCLAW_DOCKER_LIVE_AUTH_FILES_ALL[@]}" + return 0 + ;; + none) + return 0 + ;; + esac + return 0 +} + +openclaw_live_collect_auth_files() { + if openclaw_live_collect_auth_files_from_override; then + return 0 + fi + printf '%s\n' "${OPENCLAW_DOCKER_LIVE_AUTH_FILES_ALL[@]}" +} + openclaw_live_join_csv() { local first=1 value for value in "$@"; do diff --git a/scripts/test-live-acp-bind-docker.sh b/scripts/test-live-acp-bind-docker.sh index fd9cba16de9..8c8e087f188 100644 --- a/scripts/test-live-acp-bind-docker.sh +++ b/scripts/test-live-acp-bind-docker.sh @@ -38,31 +38,67 @@ if [[ -f "$PROFILE_FILE" ]]; then fi AUTH_DIRS=() +AUTH_FILES=() if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files) else while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs_from_csv "$AUTH_PROVIDER") + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files_from_csv "$AUTH_PROVIDER") +fi +AUTH_DIRS_CSV="" +if ((${#AUTH_DIRS[@]} > 0)); then + AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" +fi +AUTH_FILES_CSV="" +if ((${#AUTH_FILES[@]} > 0)); then + AUTH_FILES_CSV="$(openclaw_live_join_csv "${AUTH_FILES[@]}")" fi -AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" EXTERNAL_AUTH_MOUNTS=() -for auth_dir in "${AUTH_DIRS[@]}"; do - host_path="$HOME/$auth_dir" - if [[ -d "$host_path" ]]; then - EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/home/node/"$auth_dir":ro) - fi -done +if ((${#AUTH_DIRS[@]} > 0)); then + for auth_dir in "${AUTH_DIRS[@]}"; do + host_path="$HOME/$auth_dir" + if [[ -d "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/home/node/"$auth_dir":ro) + fi + done +fi +if ((${#AUTH_FILES[@]} > 0)); then + for auth_file in "${AUTH_FILES[@]}"; do + host_path="$HOME/$auth_file" + if [[ -f "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth-files/"$auth_file":ro) + fi + done +fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail [ -f "$HOME/.profile" ] && source "$HOME/.profile" || true export PATH="$HOME/.npm-global/bin:$PATH" +IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" +if ((${#auth_files[@]} > 0)); then + for auth_file in "${auth_files[@]}"; do + [ -n "$auth_file" ] || continue + if [ -f "/host-auth-files/$auth_file" ]; then + cp "/host-auth-files/$auth_file" "$HOME/$auth_file" + chmod u+rw "$HOME/$auth_file" || true + fi + done +fi if [ ! -x "$HOME/.npm-global/bin/acpx" ]; then npm_config_prefix="$HOME/.npm-global" npm install -g "acpx@${OPENCLAW_DOCKER_ACPX_VERSION:-0.3.1}" fi @@ -72,6 +108,24 @@ case "$agent" in if [ ! -x "$HOME/.npm-global/bin/claude" ]; then npm_config_prefix="$HOME/.npm-global" npm install -g @anthropic-ai/claude-code fi + real_claude="$HOME/.npm-global/bin/claude-real" + if [ ! -x "$real_claude" ] && [ -x "$HOME/.npm-global/bin/claude" ]; then + mv "$HOME/.npm-global/bin/claude" "$real_claude" + fi + if [ -x "$real_claude" ]; then + cat > "$HOME/.npm-global/bin/claude" < Run ACP bind live test in Docker" echo "==> Agent: $ACP_AGENT" echo "==> Auth dirs: ${AUTH_DIRS_CSV:-none}" +echo "==> Auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ -u node \ --entrypoint bash \ -e ANTHROPIC_API_KEY \ -e ANTHROPIC_API_KEY_OLD \ + -e OPENCLAW_LIVE_ACP_BIND_ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" \ + -e OPENCLAW_LIVE_ACP_BIND_ANTHROPIC_API_KEY_OLD="${ANTHROPIC_API_KEY_OLD:-}" \ -e OPENAI_API_KEY \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ -e HOME=/home/node \ @@ -126,6 +183,7 @@ docker run --rm -t \ -e OPENCLAW_SKIP_CHANNELS=1 \ -e OPENCLAW_VITEST_FS_MODULE_CACHE=0 \ -e OPENCLAW_DOCKER_ACPX_VERSION="$ACPX_VERSION" \ + -e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \ -e OPENCLAW_LIVE_TEST=1 \ -e OPENCLAW_LIVE_ACP_BIND=1 \ -e OPENCLAW_LIVE_ACP_BIND_AGENT="$ACP_AGENT" \ diff --git a/scripts/test-live-cli-backend-docker.sh b/scripts/test-live-cli-backend-docker.sh index f7a09918a2e..766323f9360 100644 --- a/scripts/test-live-cli-backend-docker.sh +++ b/scripts/test-live-cli-backend-docker.sh @@ -29,40 +29,78 @@ if [[ -f "$PROFILE_FILE" ]]; then fi AUTH_DIRS=() +AUTH_FILES=() if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files) else while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs_from_csv "$CLI_PROVIDER") + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files_from_csv "$CLI_PROVIDER") +fi +AUTH_DIRS_CSV="" +if ((${#AUTH_DIRS[@]} > 0)); then + AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" +fi +AUTH_FILES_CSV="" +if ((${#AUTH_FILES[@]} > 0)); then + AUTH_FILES_CSV="$(openclaw_live_join_csv "${AUTH_FILES[@]}")" fi -AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" EXTERNAL_AUTH_MOUNTS=() -for auth_dir in "${AUTH_DIRS[@]}"; do - host_path="$HOME/$auth_dir" - if [[ -d "$host_path" ]]; then - EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) - fi -done +if ((${#AUTH_DIRS[@]} > 0)); then + for auth_dir in "${AUTH_DIRS[@]}"; do + host_path="$HOME/$auth_dir" + if [[ -d "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) + fi + done +fi +if ((${#AUTH_FILES[@]} > 0)); then + for auth_file in "${AUTH_FILES[@]}"; do + host_path="$HOME/$auth_file" + if [[ -f "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth-files/"$auth_file":ro) + fi + done +fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail [ -f "$HOME/.profile" ] && source "$HOME/.profile" || true export PATH="$HOME/.npm-global/bin:$PATH" IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" -for auth_dir in "${auth_dirs[@]}"; do - [ -n "$auth_dir" ] || continue - if [ -d "/host-auth/$auth_dir" ]; then - mkdir -p "$HOME/$auth_dir" - cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" - chmod -R u+rwX "$HOME/$auth_dir" || true - fi -done +IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" +if ((${#auth_dirs[@]} > 0)); then + for auth_dir in "${auth_dirs[@]}"; do + [ -n "$auth_dir" ] || continue + if [ -d "/host-auth/$auth_dir" ]; then + mkdir -p "$HOME/$auth_dir" + cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" + chmod -R u+rwX "$HOME/$auth_dir" || true + fi + done +fi +if ((${#auth_files[@]} > 0)); then + for auth_file in "${auth_files[@]}"; do + [ -n "$auth_file" ] || continue + if [ -f "/host-auth-files/$auth_file" ]; then + cp "/host-auth-files/$auth_file" "$HOME/$auth_file" + chmod u+rw "$HOME/$auth_file" || true + fi + done +fi provider="${OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER:-claude-cli}" if [ "$provider" = "claude-cli" ]; then if [ -z "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND:-}" ]; then @@ -71,6 +109,24 @@ if [ "$provider" = "claude-cli" ]; then if [ ! -x "${OPENCLAW_LIVE_CLI_BACKEND_COMMAND}" ]; then npm_config_prefix="$HOME/.npm-global" npm install -g @anthropic-ai/claude-code fi + real_claude="$HOME/.npm-global/bin/claude-real" + if [ ! -x "$real_claude" ] && [ -x "$HOME/.npm-global/bin/claude" ]; then + mv "$HOME/.npm-global/bin/claude" "$real_claude" + fi + if [ -x "$real_claude" ]; then + cat > "$HOME/.npm-global/bin/claude" < Run CLI backend live test in Docker" echo "==> Model: $CLI_MODEL" echo "==> Provider: $CLI_PROVIDER" echo "==> External auth dirs: ${AUTH_DIRS_CSV:-none}" +echo "==> External auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ -u node \ --entrypoint bash \ -e ANTHROPIC_API_KEY \ -e ANTHROPIC_API_KEY_OLD \ + -e OPENCLAW_LIVE_CLI_BACKEND_ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" \ + -e OPENCLAW_LIVE_CLI_BACKEND_ANTHROPIC_API_KEY_OLD="${ANTHROPIC_API_KEY_OLD:-}" \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ -e HOME=/home/node \ -e NODE_OPTIONS=--disable-warning=ExperimentalWarning \ -e OPENCLAW_SKIP_CHANNELS=1 \ -e OPENCLAW_VITEST_FS_MODULE_CACHE=0 \ -e OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED="$AUTH_DIRS_CSV" \ + -e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \ -e OPENCLAW_DOCKER_CLI_BACKEND_PROVIDER="$CLI_PROVIDER" \ -e OPENCLAW_LIVE_TEST=1 \ -e OPENCLAW_LIVE_CLI_BACKEND=1 \ diff --git a/scripts/test-live-gateway-models-docker.sh b/scripts/test-live-gateway-models-docker.sh index 43bf0a67c4a..98c32fe0940 100755 --- a/scripts/test-live-gateway-models-docker.sh +++ b/scripts/test-live-gateway-models-docker.sh @@ -15,44 +15,86 @@ if [[ -f "$PROFILE_FILE" ]]; then fi AUTH_DIRS=() +AUTH_FILES=() if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files) elif [[ -n "${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs_from_csv "${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}") + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files_from_csv "${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}") else while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files) +fi +AUTH_DIRS_CSV="" +if ((${#AUTH_DIRS[@]} > 0)); then + AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" +fi +AUTH_FILES_CSV="" +if ((${#AUTH_FILES[@]} > 0)); then + AUTH_FILES_CSV="$(openclaw_live_join_csv "${AUTH_FILES[@]}")" fi -AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" EXTERNAL_AUTH_MOUNTS=() -for auth_dir in "${AUTH_DIRS[@]}"; do - host_path="$HOME/$auth_dir" - if [[ -d "$host_path" ]]; then - EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) - fi -done +if ((${#AUTH_DIRS[@]} > 0)); then + for auth_dir in "${AUTH_DIRS[@]}"; do + host_path="$HOME/$auth_dir" + if [[ -d "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) + fi + done +fi +if ((${#AUTH_FILES[@]} > 0)); then + for auth_file in "${AUTH_FILES[@]}"; do + host_path="$HOME/$auth_file" + if [[ -f "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth-files/"$auth_file":ro) + fi + done +fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail [ -f "$HOME/.profile" ] && source "$HOME/.profile" || true IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" -for auth_dir in "${auth_dirs[@]}"; do - [ -n "$auth_dir" ] || continue - if [ -d "/host-auth/$auth_dir" ]; then - mkdir -p "$HOME/$auth_dir" - cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" - chmod -R u+rwX "$HOME/$auth_dir" || true - fi -done +IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" +if ((${#auth_dirs[@]} > 0)); then + for auth_dir in "${auth_dirs[@]}"; do + [ -n "$auth_dir" ] || continue + if [ -d "/host-auth/$auth_dir" ]; then + mkdir -p "$HOME/$auth_dir" + cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" + chmod -R u+rwX "$HOME/$auth_dir" || true + fi + done +fi +if ((${#auth_files[@]} > 0)); then + for auth_file in "${auth_files[@]}"; do + [ -n "$auth_file" ] || continue + if [ -f "/host-auth-files/$auth_file" ]; then + cp "/host-auth-files/$auth_file" "$HOME/$auth_file" + chmod u+rw "$HOME/$auth_file" || true + fi + done +fi tmp_dir="$(mktemp -d)" cleanup() { rm -rf "$tmp_dir" @@ -81,6 +123,7 @@ docker build --target build -t "$LIVE_IMAGE_NAME" -f "$ROOT_DIR/Dockerfile" "$RO echo "==> Run gateway live model tests (profile keys)" echo "==> External auth dirs: ${AUTH_DIRS_CSV:-none}" +echo "==> External auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ --entrypoint bash \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ @@ -88,6 +131,7 @@ docker run --rm -t \ -e NODE_OPTIONS=--disable-warning=ExperimentalWarning \ -e OPENCLAW_SKIP_CHANNELS=1 \ -e OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED="$AUTH_DIRS_CSV" \ + -e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \ -e OPENCLAW_LIVE_TEST=1 \ -e OPENCLAW_LIVE_GATEWAY_MODELS="${OPENCLAW_LIVE_GATEWAY_MODELS:-modern}" \ -e OPENCLAW_LIVE_GATEWAY_PROVIDERS="${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}" \ diff --git a/scripts/test-live-models-docker.sh b/scripts/test-live-models-docker.sh index 928f8e5b602..e432847a341 100755 --- a/scripts/test-live-models-docker.sh +++ b/scripts/test-live-models-docker.sh @@ -15,11 +15,16 @@ if [[ -f "$PROFILE_FILE" ]]; then fi AUTH_DIRS=() +AUTH_FILES=() if [[ -n "${OPENCLAW_DOCKER_AUTH_DIRS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files) elif [[ -n "${OPENCLAW_LIVE_PROVIDERS:-}" && -n "${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}" ]]; then while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue @@ -30,34 +35,76 @@ elif [[ -n "${OPENCLAW_LIVE_PROVIDERS:-}" && -n "${OPENCLAW_LIVE_GATEWAY_PROVIDE openclaw_live_collect_auth_dirs_from_csv "${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}" } | awk '!seen[$0]++' ) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <( + { + openclaw_live_collect_auth_files_from_csv "${OPENCLAW_LIVE_PROVIDERS:-}" + openclaw_live_collect_auth_files_from_csv "${OPENCLAW_LIVE_GATEWAY_PROVIDERS:-}" + } | awk '!seen[$0]++' + ) else while IFS= read -r auth_dir; do [[ -n "$auth_dir" ]] || continue AUTH_DIRS+=("$auth_dir") done < <(openclaw_live_collect_auth_dirs) + while IFS= read -r auth_file; do + [[ -n "$auth_file" ]] || continue + AUTH_FILES+=("$auth_file") + done < <(openclaw_live_collect_auth_files) +fi +AUTH_DIRS_CSV="" +if ((${#AUTH_DIRS[@]} > 0)); then + AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" +fi +AUTH_FILES_CSV="" +if ((${#AUTH_FILES[@]} > 0)); then + AUTH_FILES_CSV="$(openclaw_live_join_csv "${AUTH_FILES[@]}")" fi -AUTH_DIRS_CSV="$(openclaw_live_join_csv "${AUTH_DIRS[@]}")" EXTERNAL_AUTH_MOUNTS=() -for auth_dir in "${AUTH_DIRS[@]}"; do - host_path="$HOME/$auth_dir" - if [[ -d "$host_path" ]]; then - EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) - fi -done +if ((${#AUTH_DIRS[@]} > 0)); then + for auth_dir in "${AUTH_DIRS[@]}"; do + host_path="$HOME/$auth_dir" + if [[ -d "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth/"$auth_dir":ro) + fi + done +fi +if ((${#AUTH_FILES[@]} > 0)); then + for auth_file in "${AUTH_FILES[@]}"; do + host_path="$HOME/$auth_file" + if [[ -f "$host_path" ]]; then + EXTERNAL_AUTH_MOUNTS+=(-v "$host_path":/host-auth-files/"$auth_file":ro) + fi + done +fi read -r -d '' LIVE_TEST_CMD <<'EOF' || true set -euo pipefail [ -f "$HOME/.profile" ] && source "$HOME/.profile" || true IFS=',' read -r -a auth_dirs <<<"${OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED:-}" -for auth_dir in "${auth_dirs[@]}"; do - [ -n "$auth_dir" ] || continue - if [ -d "/host-auth/$auth_dir" ]; then - mkdir -p "$HOME/$auth_dir" - cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" - chmod -R u+rwX "$HOME/$auth_dir" || true - fi -done +IFS=',' read -r -a auth_files <<<"${OPENCLAW_DOCKER_AUTH_FILES_RESOLVED:-}" +if ((${#auth_dirs[@]} > 0)); then + for auth_dir in "${auth_dirs[@]}"; do + [ -n "$auth_dir" ] || continue + if [ -d "/host-auth/$auth_dir" ]; then + mkdir -p "$HOME/$auth_dir" + cp -R "/host-auth/$auth_dir/." "$HOME/$auth_dir" + chmod -R u+rwX "$HOME/$auth_dir" || true + fi + done +fi +if ((${#auth_files[@]} > 0)); then + for auth_file in "${auth_files[@]}"; do + [ -n "$auth_file" ] || continue + if [ -f "/host-auth-files/$auth_file" ]; then + cp "/host-auth-files/$auth_file" "$HOME/$auth_file" + chmod u+rw "$HOME/$auth_file" || true + fi + done +fi tmp_dir="$(mktemp -d)" cleanup() { rm -rf "$tmp_dir" @@ -86,6 +133,7 @@ docker build --target build -t "$LIVE_IMAGE_NAME" -f "$ROOT_DIR/Dockerfile" "$RO echo "==> Run live model tests (profile keys)" echo "==> External auth dirs: ${AUTH_DIRS_CSV:-none}" +echo "==> External auth files: ${AUTH_FILES_CSV:-none}" docker run --rm -t \ --entrypoint bash \ -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \ @@ -93,6 +141,7 @@ docker run --rm -t \ -e NODE_OPTIONS=--disable-warning=ExperimentalWarning \ -e OPENCLAW_SKIP_CHANNELS=1 \ -e OPENCLAW_DOCKER_AUTH_DIRS_RESOLVED="$AUTH_DIRS_CSV" \ + -e OPENCLAW_DOCKER_AUTH_FILES_RESOLVED="$AUTH_FILES_CSV" \ -e OPENCLAW_LIVE_TEST=1 \ -e OPENCLAW_LIVE_MODELS="${OPENCLAW_LIVE_MODELS:-modern}" \ -e OPENCLAW_LIVE_PROVIDERS="${OPENCLAW_LIVE_PROVIDERS:-}" \ diff --git a/test/test-env.ts b/test/test-env.ts index f7dd51520ad..030e07fcfc0 100644 --- a/test/test-env.ts +++ b/test/test-env.ts @@ -7,6 +7,7 @@ import JSON5 from "json5"; type RestoreEntry = { key: string; value: string | undefined }; const LIVE_EXTERNAL_AUTH_DIRS = [".claude", ".codex", ".minimax"] as const; +const LIVE_EXTERNAL_AUTH_FILES = [".claude.json"] as const; function isTruthyEnvValue(value: string | undefined): boolean { if (!value) { @@ -225,6 +226,26 @@ function copyFileIfExists(sourcePath: string, targetPath: string): void { fs.copyFileSync(sourcePath, targetPath); } +function restoreClaudeConfigFromBackupIfNeeded(tempHome: string): void { + const targetPath = path.join(tempHome, ".claude.json"); + if (fs.existsSync(targetPath)) { + return; + } + const backupsDir = path.join(tempHome, ".claude", "backups"); + if (!fs.existsSync(backupsDir)) { + return; + } + const latestBackup = fs + .readdirSync(backupsDir) + .filter((entry) => entry.startsWith(".claude.json.backup.")) + .toSorted() + .at(-1); + if (!latestBackup) { + return; + } + copyFileIfExists(path.join(backupsDir, latestBackup), targetPath); +} + function sanitizeLiveConfig(raw: string): string { try { const parsed: { @@ -316,6 +337,10 @@ function stageLiveTestState(params: { for (const authDir of LIVE_EXTERNAL_AUTH_DIRS) { copyDirIfExists(path.join(params.realHome, authDir), path.join(params.tempHome, authDir)); } + for (const authFile of LIVE_EXTERNAL_AUTH_FILES) { + copyFileIfExists(path.join(params.realHome, authFile), path.join(params.tempHome, authFile)); + } + restoreClaudeConfigFromBackupIfNeeded(params.tempHome); } export function installTestEnv(): { cleanup: () => void; tempHome: string } {