Skip to content

Commit 7d789e1

Browse files
committed
fix(lib): bootstrap nested docker-git auth and compose v2
1 parent 0612c56 commit 7d789e1

11 files changed

Lines changed: 131 additions & 3 deletions

File tree

packages/lib/src/core/command-builders.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ const resolveNames = (
104104
})
105105

106106
type PathConfig = {
107+
readonly dockerGitPath: string
107108
readonly authorizedKeysPath: string
108109
readonly envGlobalPath: string
109110
readonly envProjectPath: string
@@ -126,6 +127,9 @@ const resolvePaths = (
126127
const defaultAuthorizedKeysPath = normalizedSecretsRoot === undefined
127128
? defaultTemplateConfig.authorizedKeysPath
128129
: `${normalizedSecretsRoot}/authorized_keys`
130+
const defaultDockerGitPath = normalizedSecretsRoot === undefined
131+
? defaultTemplateConfig.dockerGitPath
132+
: normalizedSecretsRoot
129133
const defaultEnvGlobalPath = normalizedSecretsRoot === undefined
130134
? defaultTemplateConfig.envGlobalPath
131135
: `${normalizedSecretsRoot}/global.env`
@@ -135,6 +139,7 @@ const resolvePaths = (
135139
const defaultCodexAuthPath = normalizedSecretsRoot === undefined
136140
? defaultTemplateConfig.codexAuthPath
137141
: `${normalizedSecretsRoot}/codex`
142+
const dockerGitPath = defaultDockerGitPath
138143
const authorizedKeysPath = yield* _(
139144
nonEmpty("--authorized-keys", raw.authorizedKeysPath, defaultAuthorizedKeysPath)
140145
)
@@ -149,7 +154,16 @@ const resolvePaths = (
149154
const codexHome = yield* _(nonEmpty("--codex-home", raw.codexHome, defaultTemplateConfig.codexHome))
150155
const outDir = yield* _(nonEmpty("--out-dir", raw.outDir, `.docker-git/${repoPath}`))
151156

152-
return { authorizedKeysPath, envGlobalPath, envProjectPath, codexAuthPath, codexSharedAuthPath, codexHome, outDir }
157+
return {
158+
dockerGitPath,
159+
authorizedKeysPath,
160+
envGlobalPath,
161+
envProjectPath,
162+
codexAuthPath,
163+
codexSharedAuthPath,
164+
codexHome,
165+
outDir
166+
}
153167
})
154168

155169
// CHANGE: build a typed create command from raw options (CLI or API)
@@ -190,6 +204,7 @@ export const buildCreateCommand = (
190204
repoRef: repo.repoRef,
191205
targetDir: repo.targetDir,
192206
volumeName: names.volumeName,
207+
dockerGitPath: paths.dockerGitPath,
193208
authorizedKeysPath: paths.authorizedKeysPath,
194209
envGlobalPath: paths.envGlobalPath,
195210
envProjectPath: paths.envProjectPath,

packages/lib/src/core/domain.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface TemplateConfig {
1212
readonly forkRepoUrl?: string
1313
readonly targetDir: string
1414
readonly volumeName: string
15+
readonly dockerGitPath: string
1516
readonly authorizedKeysPath: string
1617
readonly envGlobalPath: string
1718
readonly envProjectPath: string
@@ -194,6 +195,7 @@ export const defaultTemplateConfig = {
194195
repoRef: "main",
195196
targetDir: "/home/dev/app",
196197
volumeName: "dev_home",
198+
dockerGitPath: "./.docker-git",
197199
authorizedKeysPath: "./.docker-git/authorized_keys",
198200
envGlobalPath: "./.docker-git/.orch/env/global.env",
199201
envProjectPath: "./.orch/env/project.env",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
renderEntrypointCodexHome,
1616
renderEntrypointCodexResumeHint,
1717
renderEntrypointCodexSharedAuth,
18+
renderEntrypointDockerGitBootstrap,
1819
renderEntrypointMcpPlaywright
1920
} from "./templates-entrypoint/codex.js"
2021
import { renderEntrypointGitConfig, renderEntrypointGitHooks } from "./templates-entrypoint/git.js"
@@ -32,6 +33,7 @@ export const renderEntrypoint = (config: TemplateConfig): string =>
3233
renderEntrypointAuthorizedKeys(config),
3334
renderEntrypointCodexHome(config),
3435
renderEntrypointCodexSharedAuth(config),
36+
renderEntrypointDockerGitBootstrap(config),
3537
renderEntrypointMcpPlaywright(config),
3638
renderEntrypointZshShell(config),
3739
renderEntrypointZshUserRc(config),

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,97 @@ if [[ "$CODEX_SHARE_AUTH" == "1" ]]; then
3333
ln -sf "$SHARED_AUTH_FILE" "$AUTH_FILE"
3434
fi`
3535

36+
export const renderEntrypointDockerGitBootstrap = (config: TemplateConfig): string =>
37+
`# Bootstrap ~/.docker-git for nested docker-git usage inside this container.
38+
DOCKER_GIT_HOME="/home/${config.sshUser}/.docker-git"
39+
DOCKER_GIT_AUTH_DIR="$DOCKER_GIT_HOME/.orch/auth/codex"
40+
DOCKER_GIT_ENV_DIR="$DOCKER_GIT_HOME/.orch/env"
41+
DOCKER_GIT_ENV_GLOBAL="$DOCKER_GIT_ENV_DIR/global.env"
42+
DOCKER_GIT_ENV_PROJECT="$DOCKER_GIT_ENV_DIR/project.env"
43+
DOCKER_GIT_AUTH_KEYS="$DOCKER_GIT_HOME/authorized_keys"
44+
45+
mkdir -p "$DOCKER_GIT_AUTH_DIR" "$DOCKER_GIT_ENV_DIR" "$DOCKER_GIT_HOME/.orch/auth/gh"
46+
47+
if [[ -f "/home/${config.sshUser}/.ssh/authorized_keys" ]]; then
48+
cp "/home/${config.sshUser}/.ssh/authorized_keys" "$DOCKER_GIT_AUTH_KEYS"
49+
elif [[ -f /authorized_keys ]]; then
50+
cp /authorized_keys "$DOCKER_GIT_AUTH_KEYS"
51+
fi
52+
if [[ -f "$DOCKER_GIT_AUTH_KEYS" ]]; then
53+
chmod 600 "$DOCKER_GIT_AUTH_KEYS" || true
54+
fi
55+
56+
if [[ ! -f "$DOCKER_GIT_ENV_GLOBAL" ]]; then
57+
cat <<'EOF' > "$DOCKER_GIT_ENV_GLOBAL"
58+
# docker-git env
59+
# KEY=value
60+
EOF
61+
fi
62+
if [[ ! -f "$DOCKER_GIT_ENV_PROJECT" ]]; then
63+
cat <<'EOF' > "$DOCKER_GIT_ENV_PROJECT"
64+
# docker-git project env defaults
65+
CODEX_SHARE_AUTH=1
66+
CODEX_AUTO_UPDATE=1
67+
DOCKER_GIT_ZSH_AUTOSUGGEST=1
68+
DOCKER_GIT_ZSH_AUTOSUGGEST_STYLE=fg=8,italic
69+
DOCKER_GIT_ZSH_AUTOSUGGEST_STRATEGY=history completion
70+
MCP_PLAYWRIGHT_ISOLATED=1
71+
EOF
72+
fi
73+
74+
upsert_env_var() {
75+
local file="$1"
76+
local key="$2"
77+
local value="$3"
78+
local tmp
79+
tmp="$(mktemp)"
80+
awk -v key="$key" 'index($0, key "=") != 1 { print }' "$file" > "$tmp"
81+
printf "%s=%s\\n" "$key" "$value" >> "$tmp"
82+
mv "$tmp" "$file"
83+
}
84+
85+
copy_if_distinct_file() {
86+
local source="$1"
87+
local target="$2"
88+
if [[ ! -f "$source" ]]; then
89+
return 1
90+
fi
91+
local source_real=""
92+
local target_real=""
93+
source_real="$(readlink -f "$source" 2>/dev/null || true)"
94+
target_real="$(readlink -f "$target" 2>/dev/null || true)"
95+
if [[ -n "$source_real" && -n "$target_real" && "$source_real" == "$target_real" ]]; then
96+
return 0
97+
fi
98+
cp "$source" "$target"
99+
return 0
100+
}
101+
102+
if [[ -n "$GH_TOKEN" ]]; then
103+
upsert_env_var "$DOCKER_GIT_ENV_GLOBAL" "GH_TOKEN" "$GH_TOKEN"
104+
fi
105+
if [[ -n "$GITHUB_TOKEN" ]]; then
106+
upsert_env_var "$DOCKER_GIT_ENV_GLOBAL" "GITHUB_TOKEN" "$GITHUB_TOKEN"
107+
elif [[ -n "$GH_TOKEN" ]]; then
108+
upsert_env_var "$DOCKER_GIT_ENV_GLOBAL" "GITHUB_TOKEN" "$GH_TOKEN"
109+
fi
110+
111+
SOURCE_CODEX_CONFIG="${config.codexHome}/config.toml"
112+
copy_if_distinct_file "$SOURCE_CODEX_CONFIG" "$DOCKER_GIT_AUTH_DIR/config.toml" || true
113+
114+
SOURCE_SHARED_AUTH="${config.codexHome}-shared/auth.json"
115+
SOURCE_LOCAL_AUTH="${config.codexHome}/auth.json"
116+
if [[ -f "$SOURCE_SHARED_AUTH" ]]; then
117+
copy_if_distinct_file "$SOURCE_SHARED_AUTH" "$DOCKER_GIT_AUTH_DIR/auth.json" || true
118+
elif [[ -f "$SOURCE_LOCAL_AUTH" ]]; then
119+
copy_if_distinct_file "$SOURCE_LOCAL_AUTH" "$DOCKER_GIT_AUTH_DIR/auth.json" || true
120+
fi
121+
if [[ -f "$DOCKER_GIT_AUTH_DIR/auth.json" ]]; then
122+
chmod 600 "$DOCKER_GIT_AUTH_DIR/auth.json" || true
123+
fi
124+
125+
chown -R 1000:1000 "$DOCKER_GIT_HOME" || true`
126+
36127
const entrypointMcpPlaywrightTemplate = String.raw`# Optional: configure Playwright MCP for Codex (browser automation)
37128
CODEX_CONFIG_FILE="__CODEX_HOME__/config.toml"
38129

packages/lib/src/core/templates/docker-compose.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ${maybePlaywrightEnv}${maybeDependsOn} env_file:
3838
- "127.0.0.1:${config.sshPort}:22"
3939
volumes:
4040
- ${config.volumeName}:/home/${config.sshUser}
41+
- ${config.dockerGitPath}:/home/${config.sshUser}/.docker-git
4142
- ${config.authorizedKeysPath}:/authorized_keys:ro
4243
- ${config.codexAuthPath}:${config.codexHome}
4344
- ${config.codexSharedAuthPath}:${config.codexHome}-shared

packages/lib/src/core/templates/dockerfile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ENV NVM_DIR=/usr/local/nvm
99
1010
RUN apt-get update && apt-get install -y --no-install-recommends \
1111
openssh-server git gh ca-certificates curl unzip bsdutils sudo \
12-
make docker.io docker-compose bash-completion zsh zsh-autosuggestions xauth \
12+
make docker.io docker-compose-v2 bash-completion zsh zsh-autosuggestions xauth \
1313
ncurses-term \
1414
&& rm -rf /var/lib/apt/lists/*
1515

packages/lib/src/shell/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const TemplateConfigSchema = Schema.Struct({
1919
repoRef: Schema.String,
2020
targetDir: Schema.String,
2121
volumeName: Schema.String,
22+
dockerGitPath: Schema.optionalWith(Schema.String, {
23+
default: () => defaultTemplateConfig.dockerGitPath
24+
}),
2225
authorizedKeysPath: Schema.String,
2326
envGlobalPath: Schema.optionalWith(Schema.String, {
2427
default: () => defaultTemplateConfig.envGlobalPath

packages/lib/src/usecases/actions/create-project.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const makeCreateContext = (path: Path.Path, baseDir: string): CreateContext => {
4646

4747
const resolveRootedConfig = (command: CreateCommand, ctx: CreateContext): CreateCommand["config"] => ({
4848
...command.config,
49+
dockerGitPath: ctx.resolveRootPath(command.config.dockerGitPath),
4950
authorizedKeysPath: ctx.resolveRootPath(command.config.authorizedKeysPath),
5051
envGlobalPath: ctx.resolveRootPath(command.config.envGlobalPath),
5152
envProjectPath: ctx.resolveRootPath(command.config.envProjectPath),

packages/lib/src/usecases/actions/paths.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export const buildProjectConfigs = (
4444

4545
const globalConfig = {
4646
...resolvedConfig,
47+
dockerGitPath: resolvePathFromBase(path, baseDir, resolvedConfig.dockerGitPath),
4748
authorizedKeysPath: resolvePathFromBase(path, baseDir, resolvedConfig.authorizedKeysPath),
4849
envGlobalPath: resolvePathFromBase(path, baseDir, resolvedConfig.envGlobalPath),
4950
envProjectPath: resolvePathFromBase(path, baseDir, resolvedConfig.envProjectPath),
@@ -52,6 +53,7 @@ export const buildProjectConfigs = (
5253
}
5354
const projectConfig = {
5455
...resolvedConfig,
56+
dockerGitPath: relativeFromOutDir(globalConfig.dockerGitPath),
5557
authorizedKeysPath: relativeFromOutDir(globalConfig.authorizedKeysPath),
5658
envGlobalPath: "./.orch/env/global.env",
5759
envProjectPath: path.isAbsolute(resolvedConfig.envProjectPath)

packages/lib/src/usecases/state-normalize.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ const normalizeTemplateConfig = (
2727
projectDir: string,
2828
template: TemplateConfig
2929
): TemplateConfig | null => {
30-
const needs = shouldNormalizePath(path, template.authorizedKeysPath) ||
30+
const needs = shouldNormalizePath(path, template.dockerGitPath) ||
31+
shouldNormalizePath(path, template.authorizedKeysPath) ||
3132
shouldNormalizePath(path, template.envGlobalPath) ||
3233
shouldNormalizePath(path, template.envProjectPath) ||
3334
shouldNormalizePath(path, template.codexAuthPath) ||
@@ -40,6 +41,7 @@ const normalizeTemplateConfig = (
4041
// The state repo is shared across machines, so never persist absolute host paths in tracked files.
4142
const authorizedKeysAbs = path.join(projectsRoot, "authorized_keys")
4243
const authorizedKeysRel = toPosixPath(path.relative(projectDir, authorizedKeysAbs))
44+
const dockerGitRel = toPosixPath(path.relative(projectDir, projectsRoot))
4345

4446
const envGlobalPath = "./.orch/env/global.env"
4547
const envProjectPath = "./.orch/env/project.env"
@@ -49,6 +51,7 @@ const normalizeTemplateConfig = (
4951

5052
return {
5153
...template,
54+
dockerGitPath: dockerGitRel.length > 0 ? dockerGitRel : "./.docker-git",
5255
authorizedKeysPath: authorizedKeysRel.length > 0 ? authorizedKeysRel : "./authorized_keys",
5356
envGlobalPath,
5457
envProjectPath,

0 commit comments

Comments
 (0)