Skip to content

Commit 40d0b1e

Browse files
committed
fix(shell): route codex auth through controller state
1 parent a2c7c0a commit 40d0b1e

File tree

14 files changed

+134
-44
lines changed

14 files changed

+134
-44
lines changed

docker-compose.api.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33
build:
44
context: .
55
dockerfile: packages/api/Dockerfile
6-
container_name: docker-git-api
6+
container_name: ${DOCKER_GIT_API_CONTAINER_NAME:-docker-git-api}
77
environment:
88
DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334}
99
DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ services:
55
dockerfile: packages/api/Dockerfile
66
args:
77
DOCKER_GIT_CONTROLLER_REV: ${DOCKER_GIT_CONTROLLER_REV:-unknown}
8-
container_name: docker-git-api
8+
container_name: ${DOCKER_GIT_API_CONTAINER_NAME:-docker-git-api}
99
environment:
1010
DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334}
1111
DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}

packages/api/src/http.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ const configuredFederationPublicOrigin =
191191

192192
const configuredFederationActorUsername =
193193
process.env["DOCKER_GIT_FEDERATION_ACTOR"] ?? "docker-git"
194+
const controllerRevision =
195+
process.env["DOCKER_GIT_CONTROLLER_REV"]?.trim() ?? null
194196

195197
const readHeader = (
196198
request: HttpServerRequest.HttpServerRequest,
@@ -245,7 +247,7 @@ export const makeRouter = () => {
245247
),
246248
HttpRouter.get("/ui/styles.css", textResponse(uiStyles, "text/css; charset=utf-8", 200)),
247249
HttpRouter.get("/ui/app.js", textResponse(uiScript, "application/javascript; charset=utf-8", 200)),
248-
HttpRouter.get("/health", jsonResponse({ ok: true }, 200)),
250+
HttpRouter.get("/health", jsonResponse({ ok: true, revision: controllerRevision }, 200)),
249251
HttpRouter.get(
250252
"/auth/github/status",
251253
Effect.gen(function*(_) {

packages/app/src/docker-git/controller-docker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type ControllerRuntime =
1919
| FileSystem.FileSystem
2020
| Path.Path
2121

22-
export const controllerContainerName = "docker-git-api"
22+
export const controllerContainerName = process.env["DOCKER_GIT_API_CONTAINER_NAME"]?.trim() || "docker-git-api"
2323

2424
const inspectNetworksTemplate = String
2525
.raw`{{range $k,$v := .NetworkSettings.Networks}}{{printf "%s=%s\n" $k $v.IPAddress}}{{end}}`

packages/app/src/docker-git/controller.ts

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export { buildApiBaseUrlCandidates, isRemoteDockerHost } from "./controller-reac
3131

3232
let selectedApiBaseUrl: string | undefined
3333

34+
type HealthProbeResult = {
35+
readonly apiBaseUrl: string
36+
readonly revision: string | null
37+
}
38+
3439
const controllerBootstrapError = (message: string): ControllerBootstrapError => ({
3540
_tag: "ControllerBootstrapError",
3641
message
@@ -43,13 +48,30 @@ const rememberSelectedApiBaseUrl = (value: string): void => {
4348
export const resolveApiBaseUrl = (): string =>
4449
resolveExplicitApiBaseUrl() ?? selectedApiBaseUrl ?? resolveConfiguredApiBaseUrl()
4550

46-
const probeHealth = (apiBaseUrl: string): Effect.Effect<void, ControllerBootstrapError> =>
51+
const parseHealthRevision = (text: string): string | null => {
52+
try {
53+
const parsed: unknown = JSON.parse(text)
54+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
55+
return null
56+
}
57+
const revision = Reflect.get(parsed, "revision")
58+
return typeof revision === "string" && revision.trim().length > 0 ? revision.trim() : null
59+
} catch {
60+
return null
61+
}
62+
}
63+
64+
const probeHealth = (apiBaseUrl: string): Effect.Effect<HealthProbeResult, ControllerBootstrapError> =>
4765
Effect.gen(function*(_) {
4866
const client = yield* _(HttpClient.HttpClient)
4967
const response = yield* _(client.get(`${apiBaseUrl}/health`, { headers: { accept: "application/json" } }))
68+
const bodyText = yield* _(response.text)
5069

5170
if (response.status >= 200 && response.status < 300) {
52-
return
71+
return {
72+
apiBaseUrl,
73+
revision: parseHealthRevision(bodyText)
74+
}
5375
}
5476

5577
return yield* _(
@@ -74,6 +96,11 @@ const probeHealth = (apiBaseUrl: string): Effect.Effect<void, ControllerBootstra
7496
const findReachableApiBaseUrl = (
7597
candidateUrls: ReadonlyArray<string>
7698
): Effect.Effect<string, ControllerBootstrapError> =>
99+
findReachableHealthProbe(candidateUrls).pipe(Effect.map(({ apiBaseUrl }) => apiBaseUrl))
100+
101+
const findReachableHealthProbe = (
102+
candidateUrls: ReadonlyArray<string>
103+
): Effect.Effect<HealthProbeResult, ControllerBootstrapError> =>
77104
Effect.gen(function*(_) {
78105
if (candidateUrls.length === 0) {
79106
return yield* _(
@@ -85,14 +112,14 @@ const findReachableApiBaseUrl = (
85112
const healthy = yield* _(
86113
probeHealth(candidateUrl).pipe(
87114
Effect.match({
88-
onFailure: () => false,
89-
onSuccess: () => true
115+
onFailure: () => undefined,
116+
onSuccess: (result) => result
90117
})
91118
)
92119
)
93120

94-
if (healthy) {
95-
return candidateUrl
121+
if (healthy !== undefined) {
122+
return healthy
96123
}
97124
}
98125

@@ -176,10 +203,20 @@ const findReachableApiBaseUrlOption = (
176203
})
177204
)
178205

179-
const findReachableDirectApiBaseUrl = (
206+
const findReachableHealthProbeOption = (
207+
candidateUrls: ReadonlyArray<string>
208+
): Effect.Effect<HealthProbeResult | undefined, ControllerBootstrapError> =>
209+
findReachableHealthProbe(candidateUrls).pipe(
210+
Effect.match({
211+
onFailure: (): HealthProbeResult | undefined => undefined,
212+
onSuccess: (probe) => probe
213+
})
214+
)
215+
216+
const findReachableDirectHealthProbe = (
180217
explicitApiBaseUrl: string | undefined
181-
): Effect.Effect<string | undefined, ControllerBootstrapError> =>
182-
findReachableApiBaseUrlOption(
218+
): Effect.Effect<HealthProbeResult | undefined, ControllerBootstrapError> =>
219+
findReachableHealthProbeOption(
183220
buildApiBaseUrlCandidates({
184221
explicitApiBaseUrl,
185222
cachedApiBaseUrl: selectedApiBaseUrl,
@@ -324,14 +361,25 @@ export const ensureControllerReady = (): Effect.Effect<void, ControllerBootstrap
324361
Effect.gen(function*(_) {
325362
yield* _(failIfRemoteDockerWithoutApiUrl())
326363
const explicitApiBaseUrl = resolveExplicitApiBaseUrl()
327-
const reachableBeforeDocker = yield* _(findReachableDirectApiBaseUrl(explicitApiBaseUrl))
328-
329-
if (reachableBeforeDocker !== undefined) {
330-
rememberSelectedApiBaseUrl(reachableBeforeDocker)
331-
return
364+
const localControllerRevision = yield* _(prepareLocalControllerRevision())
365+
if (explicitApiBaseUrl !== undefined) {
366+
const reachableBeforeDocker = yield* _(findReachableDirectHealthProbe(explicitApiBaseUrl))
367+
if (reachableBeforeDocker !== undefined) {
368+
rememberSelectedApiBaseUrl(reachableBeforeDocker.apiBaseUrl)
369+
return
370+
}
371+
yield* _(failIfExplicitApiUrlIsUnreachable(explicitApiBaseUrl))
372+
} else {
373+
const reachableBeforeDocker = yield* _(findReachableDirectHealthProbe(undefined))
374+
if (
375+
reachableBeforeDocker !== undefined &&
376+
reachableBeforeDocker.revision === localControllerRevision
377+
) {
378+
rememberSelectedApiBaseUrl(reachableBeforeDocker.apiBaseUrl)
379+
return
380+
}
332381
}
333382

334-
yield* _(failIfExplicitApiUrlIsUnreachable(explicitApiBaseUrl))
335383
const bootstrapContext = yield* _(loadControllerBootstrapContext())
336384
const reusedExistingController = yield* _(reuseReachableControllerIfPossible(bootstrapContext))
337385
if (reusedExistingController) {

packages/app/src/lib/core/templates-entrypoint/nested-docker-git.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,6 @@ fi
218218
219219
SOURCE_CODEX_CONFIG="__CODEX_HOME__/config.toml"
220220
copy_if_distinct_file "$SOURCE_CODEX_CONFIG" "$DOCKER_GIT_AUTH_DIR/config.toml" || true
221-
222-
SOURCE_SHARED_AUTH="__CODEX_HOME__-shared/auth.json"
223-
SOURCE_LOCAL_AUTH="__CODEX_HOME__/auth.json"
224-
if [[ -f "$SOURCE_SHARED_AUTH" ]]; then
225-
copy_if_distinct_file "$SOURCE_SHARED_AUTH" "$DOCKER_GIT_AUTH_DIR/auth.json" || true
226-
elif [[ -f "$SOURCE_LOCAL_AUTH" ]]; then
227-
copy_if_distinct_file "$SOURCE_LOCAL_AUTH" "$DOCKER_GIT_AUTH_DIR/auth.json" || true
228-
fi
229221
if [[ -f "$DOCKER_GIT_AUTH_DIR/auth.json" ]]; then
230222
chmod 600 "$DOCKER_GIT_AUTH_DIR/auth.json" || true
231223
fi

packages/app/src/lib/core/templates-entrypoint/opencode.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,6 @@ if (!isRecord(codex)) process.exit(0)
124124
let opencode = readJson(opencodePath)
125125
if (!isRecord(opencode)) opencode = {}
126126
127-
if (opencode.openai) {
128-
process.exit(0)
129-
}
130-
131127
const apiKey = codex.OPENAI_API_KEY
132128
if (typeof apiKey === "string" && apiKey.trim().length > 0) {
133129
opencode.openai = { type: "api", key: apiKey.trim() }

packages/app/src/lib/usecases/auth-copy.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,15 @@ export const copyCodexFile = (
9393
if (!sourceExists) {
9494
return
9595
}
96+
const sourceText = yield* _(fs.readFileString(sourceFile))
9697
const targetExists = yield* _(fs.exists(targetFile))
9798
if (targetExists) {
99+
const targetText = yield* _(fs.readFileString(targetFile))
100+
if (targetText === sourceText) {
101+
return
102+
}
103+
yield* _(fs.writeFileString(targetFile, sourceText))
104+
yield* _(Effect.log(`Synced Codex ${spec.label} from ${sourceFile} to ${targetFile}`))
98105
return
99106
}
100107
yield* _(fs.copyFile(sourceFile, targetFile))

packages/lib/src/core/templates-entrypoint/nested-docker-git.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,6 @@ fi
217217
218218
SOURCE_CODEX_CONFIG="__CODEX_HOME__/config.toml"
219219
copy_if_distinct_file "$SOURCE_CODEX_CONFIG" "$DOCKER_GIT_AUTH_DIR/config.toml" || true
220-
221-
SOURCE_SHARED_AUTH="__CODEX_HOME__-shared/auth.json"
222-
SOURCE_LOCAL_AUTH="__CODEX_HOME__/auth.json"
223-
if [[ -f "$SOURCE_SHARED_AUTH" ]]; then
224-
copy_if_distinct_file "$SOURCE_SHARED_AUTH" "$DOCKER_GIT_AUTH_DIR/auth.json" || true
225-
elif [[ -f "$SOURCE_LOCAL_AUTH" ]]; then
226-
copy_if_distinct_file "$SOURCE_LOCAL_AUTH" "$DOCKER_GIT_AUTH_DIR/auth.json" || true
227-
fi
228220
if [[ -f "$DOCKER_GIT_AUTH_DIR/auth.json" ]]; then
229221
chmod 600 "$DOCKER_GIT_AUTH_DIR/auth.json" || true
230222
fi

packages/lib/src/core/templates-entrypoint/opencode.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,6 @@ if (!isRecord(codex)) process.exit(0)
123123
let opencode = readJson(opencodePath)
124124
if (!isRecord(opencode)) opencode = {}
125125
126-
if (opencode.openai) {
127-
process.exit(0)
128-
}
129-
130126
const apiKey = codex.OPENAI_API_KEY
131127
if (typeof apiKey === "string" && apiKey.trim().length > 0) {
132128
opencode.openai = { type: "api", key: apiKey.trim() }

0 commit comments

Comments
 (0)