|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +RUN_ID="$(date +%s)-$RANDOM" |
| 5 | +ROOT="$(mktemp -d)" |
| 6 | +KEEP="${KEEP:-0}" |
| 7 | + |
| 8 | +# Keep compose project/volume names unique to avoid interfering with any local docker-git state. |
| 9 | +OUT_DIR_REL=".docker-git/e2e/opencode-autoconnect-$RUN_ID" |
| 10 | +OUT_DIR="$ROOT/e2e/opencode-autoconnect-$RUN_ID" |
| 11 | +CONTAINER_NAME="dg-e2e-opencode-$RUN_ID" |
| 12 | +SERVICE_NAME="dg-e2e-opencode-$RUN_ID" |
| 13 | +VOLUME_NAME="dg-e2e-opencode-$RUN_ID-home" |
| 14 | +SSH_PORT="$(( (RANDOM % 1000) + 20000 ))" |
| 15 | + |
| 16 | +export DOCKER_GIT_PROJECTS_ROOT="$ROOT" |
| 17 | +export DOCKER_GIT_STATE_AUTO_SYNC=0 |
| 18 | + |
| 19 | +on_error() { |
| 20 | + local line="$1" |
| 21 | + echo "e2e/opencode-autoconnect: failed at line $line" >&2 |
| 22 | + docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | head -n 50 || true |
| 23 | + if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER_NAME" 2>/dev/null; then |
| 24 | + docker exec -u dev "$CONTAINER_NAME" bash -lc ' |
| 25 | + echo "--- auth mounts ---" |
| 26 | + ls -la ~/.codex ~/.codex-shared ~/.local/share/opencode 2>/dev/null || true |
| 27 | + echo "--- opencode auth link ---" |
| 28 | + readlink -v ~/.local/share/opencode/auth.json 2>/dev/null || true |
| 29 | + echo "--- codex shared auth ---" |
| 30 | + ls -la ~/.codex-shared/auth.json 2>/dev/null || true |
| 31 | + echo "--- opencode shared auth ---" |
| 32 | + ls -la ~/.codex-shared/opencode 2>/dev/null || true |
| 33 | + ls -la ~/.codex-shared/opencode/auth.json 2>/dev/null || true |
| 34 | + ' || true |
| 35 | + fi |
| 36 | + if [[ -d "$OUT_DIR" ]] && [[ -f "$OUT_DIR/docker-compose.yml" ]]; then |
| 37 | + (cd "$OUT_DIR" && docker compose ps) || true |
| 38 | + (cd "$OUT_DIR" && docker compose logs --no-color --tail 200) || true |
| 39 | + fi |
| 40 | +} |
| 41 | + |
| 42 | +cleanup() { |
| 43 | + if [[ "$KEEP" == "1" ]]; then |
| 44 | + echo "e2e/opencode-autoconnect: KEEP=1 set; preserving temp dir: $ROOT" >&2 |
| 45 | + echo "e2e/opencode-autoconnect: container name: $CONTAINER_NAME" >&2 |
| 46 | + echo "e2e/opencode-autoconnect: out dir: $OUT_DIR" >&2 |
| 47 | + return |
| 48 | + fi |
| 49 | + if [[ -d "$OUT_DIR" ]] && [[ -f "$OUT_DIR/docker-compose.yml" ]]; then |
| 50 | + (cd "$OUT_DIR" && docker compose down -v --remove-orphans) >/dev/null 2>&1 || true |
| 51 | + fi |
| 52 | + rm -rf "$ROOT" >/dev/null 2>&1 || true |
| 53 | +} |
| 54 | + |
| 55 | +trap 'on_error $LINENO' ERR |
| 56 | +trap cleanup EXIT |
| 57 | + |
| 58 | +# Ensure docker mounts a file (not an auto-created directory). |
| 59 | +mkdir -p "$ROOT/.orch/auth/codex" "$ROOT/.orch/env" |
| 60 | +: > "$ROOT/authorized_keys" |
| 61 | + |
| 62 | +# Seed a fake (but structurally valid) Codex auth.json so the entrypoint can |
| 63 | +# auto-connect OpenCode without manual /connect. |
| 64 | +node <<'NODE' > "$ROOT/.orch/auth/codex/auth.json" |
| 65 | +const now = Math.floor(Date.now() / 1000) |
| 66 | +const b64 = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url") |
| 67 | +const jwt = (payload) => `${b64({ alg: "none", typ: "JWT" })}.${b64(payload)}.sig` |
| 68 | +
|
| 69 | +const access = jwt({ exp: now + 3600, chatgpt_account_id: "org_test" }) |
| 70 | +const idToken = jwt({ exp: now + 3600, email: "ci@example.com" }) |
| 71 | +
|
| 72 | +const auth = { |
| 73 | + auth_mode: "chatgpt", |
| 74 | + OPENAI_API_KEY: null, |
| 75 | + tokens: { |
| 76 | + id_token: idToken, |
| 77 | + access_token: access, |
| 78 | + refresh_token: "refresh_test", |
| 79 | + account_id: "org_test" |
| 80 | + }, |
| 81 | + last_refresh: new Date().toISOString() |
| 82 | +} |
| 83 | +
|
| 84 | +process.stdout.write(JSON.stringify(auth, null, 2)) |
| 85 | +NODE |
| 86 | + |
| 87 | +# Keep the container startup deterministic and fast for CI. |
| 88 | +mkdir -p "$OUT_DIR/.orch/env" |
| 89 | +cat > "$OUT_DIR/.orch/env/project.env" <<'EOF_ENV' |
| 90 | +# docker-git project env (e2e) |
| 91 | +CODEX_AUTO_UPDATE=0 |
| 92 | +CODEX_SHARE_AUTH=1 |
| 93 | +OPENCODE_SHARE_AUTH=1 |
| 94 | +OPENCODE_AUTO_CONNECT=1 |
| 95 | +EOF_ENV |
| 96 | + |
| 97 | +pnpm run docker-git clone https://github.com/octocat/Hello-World \ |
| 98 | + --force \ |
| 99 | + --repo-ref master \ |
| 100 | + --ssh-port "$SSH_PORT" \ |
| 101 | + --out-dir "$OUT_DIR_REL" \ |
| 102 | + --container-name "$CONTAINER_NAME" \ |
| 103 | + --service-name "$SERVICE_NAME" \ |
| 104 | + --volume-name "$VOLUME_NAME" |
| 105 | + |
| 106 | +# Basic sanity checks. |
| 107 | +docker ps --format '{{.Names}}' | grep -qx "$CONTAINER_NAME" |
| 108 | + |
| 109 | +docker exec "$CONTAINER_NAME" opencode --version >/dev/null |
| 110 | +docker exec -u dev "$CONTAINER_NAME" oh-my-opencode --version >/dev/null |
| 111 | + |
| 112 | +docker exec -u dev "$CONTAINER_NAME" bash -lc \ |
| 113 | + 'test -f ~/.config/opencode/opencode.json && grep -q "oh-my-opencode" ~/.config/opencode/opencode.json' |
| 114 | + |
| 115 | +docker exec -u dev "$CONTAINER_NAME" bash -lc \ |
| 116 | + 'test "$(readlink ~/.local/share/opencode/auth.json)" = "/home/dev/.codex-shared/opencode/auth.json"' |
| 117 | + |
| 118 | +docker exec -u dev "$CONTAINER_NAME" bash -lc 'test -f ~/.codex-shared/auth.json' |
| 119 | + |
| 120 | +docker exec -u dev "$CONTAINER_NAME" bash -lc \ |
| 121 | + 'node - <<'\''NODE'\'' |
| 122 | +const fs = require("fs") |
| 123 | +
|
| 124 | +const p = process.env.HOME + "/.local/share/opencode/auth.json" |
| 125 | +const auth = JSON.parse(fs.readFileSync(p, "utf8")) |
| 126 | +const openai = auth && auth.openai |
| 127 | +if (!openai) process.exit(1) |
| 128 | +if (openai.type === "oauth") { |
| 129 | + if (typeof openai.access !== "string" || openai.access.length === 0) process.exit(1) |
| 130 | + if (typeof openai.refresh !== "string" || openai.refresh.length === 0) process.exit(1) |
| 131 | + if (typeof openai.expires !== "number") process.exit(1) |
| 132 | + process.exit(0) |
| 133 | +} |
| 134 | +if (openai.type === "api") { |
| 135 | + if (typeof openai.key !== "string" || openai.key.length === 0) process.exit(1) |
| 136 | + process.exit(0) |
| 137 | +} |
| 138 | +process.exit(1) |
| 139 | +NODE' |
| 140 | + |
| 141 | +# Exercises Bun-based plugin install path (regression test for BUN_INSTALL env). |
| 142 | +docker exec -u dev "$CONTAINER_NAME" opencode models openai | grep -m 1 -E '^openai/' >/dev/null |
0 commit comments