Skip to content

Commit 54ed9ac

Browse files
committed
feat(lib): preinstall opencode + oh-my-opencode
1 parent a7930be commit 54ed9ac

5 files changed

Lines changed: 105 additions & 4 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from "./templates-entrypoint/codex.js"
2020
import { renderEntrypointGitConfig, renderEntrypointGitHooks } from "./templates-entrypoint/git.js"
2121
import { renderEntrypointDockerGitBootstrap } from "./templates-entrypoint/nested-docker-git.js"
22+
import { renderEntrypointOpenCodeConfig } from "./templates-entrypoint/opencode.js"
2223
import { renderEntrypointBackgroundTasks } from "./templates-entrypoint/tasks.js"
2324
import {
2425
renderEntrypointBashCompletion,
@@ -33,6 +34,7 @@ export const renderEntrypoint = (config: TemplateConfig): string =>
3334
renderEntrypointAuthorizedKeys(config),
3435
renderEntrypointCodexHome(config),
3536
renderEntrypointCodexSharedAuth(config),
37+
renderEntrypointOpenCodeConfig(config),
3638
renderEntrypointDockerGitBootstrap(config),
3739
renderEntrypointMcpPlaywright(config),
3840
renderEntrypointZshShell(config),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ $MANAGED_END
182182
EOF
183183
)"
184184
cat <<EOF > "$AGENTS_PATH"
185-
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
185+
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
186186
$MANAGED_BLOCK
187187
Если ты видишь файлы AGENTS.md внутри проекта, ты обязан их читать и соблюдать инструкции.
188188
EOF
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { TemplateConfig } from "../domain.js"
2+
3+
// CHANGE: bootstrap OpenCode config (permissions + plugins) and share OpenCode auth.json across projects
4+
// WHY: make OpenCode usable out-of-the-box inside disposable docker-git containers
5+
// QUOTE(ТЗ): "Preinstall OpenCode and oh-my-opencode with full authorization of existing tools"
6+
// REF: issue-34
7+
// SOURCE: n/a
8+
// FORMAT THEOREM: forall s: start(s) -> config_exists(s)
9+
// PURITY: CORE
10+
// INVARIANT: never overwrites an existing opencode.json/opencode.jsonc
11+
// COMPLEXITY: O(1)
12+
export const renderEntrypointOpenCodeConfig = (config: TemplateConfig): string =>
13+
`# OpenCode: share auth.json across projects (so /connect is one-time)
14+
OPENCODE_SHARE_AUTH="\${OPENCODE_SHARE_AUTH:-1}"
15+
if [[ "$OPENCODE_SHARE_AUTH" == "1" ]]; then
16+
OPENCODE_DATA_DIR="/home/${config.sshUser}/.local/share/opencode"
17+
OPENCODE_AUTH_FILE="$OPENCODE_DATA_DIR/auth.json"
18+
19+
# Store in the shared auth volume to persist across projects/containers.
20+
OPENCODE_SHARED_HOME="${config.codexHome}-shared/opencode"
21+
OPENCODE_SHARED_AUTH_FILE="$OPENCODE_SHARED_HOME/auth.json"
22+
23+
mkdir -p "$OPENCODE_DATA_DIR" "$OPENCODE_SHARED_HOME"
24+
chown -R 1000:1000 "$OPENCODE_DATA_DIR" "$OPENCODE_SHARED_HOME" || true
25+
26+
# Guard against a bad bind mount creating a directory at auth.json.
27+
if [[ -d "$OPENCODE_AUTH_FILE" ]]; then
28+
mv "$OPENCODE_AUTH_FILE" "$OPENCODE_AUTH_FILE.bak-$(date +%s)" || true
29+
fi
30+
31+
# Migrate existing per-project auth into the shared location once.
32+
if [[ -f "$OPENCODE_AUTH_FILE" && ! -L "$OPENCODE_AUTH_FILE" ]]; then
33+
if [[ -f "$OPENCODE_SHARED_AUTH_FILE" ]]; then
34+
LOCAL_AUTH="$OPENCODE_AUTH_FILE" SHARED_AUTH="$OPENCODE_SHARED_AUTH_FILE" node - <<'NODE'
35+
const fs = require("fs")
36+
const localPath = process.env.LOCAL_AUTH
37+
const sharedPath = process.env.SHARED_AUTH
38+
const readJson = (p) => {
39+
try {
40+
return JSON.parse(fs.readFileSync(p, "utf8"))
41+
} catch {
42+
return {}
43+
}
44+
}
45+
const local = readJson(localPath)
46+
const shared = readJson(sharedPath)
47+
const merged = { ...local, ...shared } // shared wins on conflicts
48+
fs.writeFileSync(sharedPath, JSON.stringify(merged, null, 2), { mode: 0o600 })
49+
NODE
50+
else
51+
cp "$OPENCODE_AUTH_FILE" "$OPENCODE_SHARED_AUTH_FILE" || true
52+
chmod 600 "$OPENCODE_SHARED_AUTH_FILE" || true
53+
fi
54+
chown 1000:1000 "$OPENCODE_SHARED_AUTH_FILE" || true
55+
rm -f "$OPENCODE_AUTH_FILE" || true
56+
fi
57+
58+
ln -sf "$OPENCODE_SHARED_AUTH_FILE" "$OPENCODE_AUTH_FILE"
59+
fi
60+
61+
# OpenCode: ensure global config exists (plugins + permissions)
62+
OPENCODE_CONFIG_DIR="/home/${config.sshUser}/.config/opencode"
63+
OPENCODE_CONFIG_JSON="$OPENCODE_CONFIG_DIR/opencode.json"
64+
OPENCODE_CONFIG_JSONC="$OPENCODE_CONFIG_DIR/opencode.jsonc"
65+
66+
mkdir -p "$OPENCODE_CONFIG_DIR"
67+
chown -R 1000:1000 "$OPENCODE_CONFIG_DIR" || true
68+
69+
if [[ ! -f "$OPENCODE_CONFIG_JSON" && ! -f "$OPENCODE_CONFIG_JSONC" ]]; then
70+
cat <<'EOF' > "$OPENCODE_CONFIG_JSON"
71+
{
72+
"$schema": "https://opencode.ai/config.json",
73+
"plugin": ["oh-my-opencode"],
74+
"permission": {
75+
"doom_loop": "allow",
76+
"external_directory": "allow",
77+
"read": {
78+
"*": "allow",
79+
"*.env": "allow",
80+
"*.env.*": "allow",
81+
"*.env.example": "allow"
82+
}
83+
}
84+
}
85+
EOF
86+
chown 1000:1000 "$OPENCODE_CONFIG_JSON" || true
87+
fi`

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,22 @@ RUN printf "export NVM_DIR=/usr/local/nvm\\n[ -s /usr/local/nvm/nvm.sh ] && . /u
3131
> /etc/profile.d/nvm.sh && chmod 0644 /etc/profile.d/nvm.sh`
3232

3333
const renderDockerfileBunPrelude = (config: TemplateConfig): string =>
34-
`# Tooling: pnpm + Codex CLI (bun)
34+
`# Tooling: pnpm + Codex CLI + oh-my-opencode (bun)
3535
RUN corepack enable && corepack prepare pnpm@${config.pnpmVersion} --activate
3636
ENV BUN_INSTALL=/usr/local/bun
3737
ENV TERM=xterm-256color
3838
ENV PATH="/usr/local/bun/bin:$PATH"
3939
RUN curl -fsSL https://bun.sh/install | bash
4040
RUN ln -sf /usr/local/bun/bin/bun /usr/local/bin/bun
41-
RUN script -q -e -c "bun add -g @openai/codex@latest" /dev/null
42-
RUN ln -sf /usr/local/bun/bin/codex /usr/local/bin/codex`
41+
RUN script -q -e -c "bun add -g @openai/codex@latest oh-my-opencode@latest" /dev/null
42+
RUN ln -sf /usr/local/bun/bin/codex /usr/local/bin/codex
43+
RUN ln -sf /usr/local/bun/bin/oh-my-opencode /usr/local/bin/oh-my-opencode`
44+
45+
const renderDockerfileOpenCode = (): string =>
46+
`# Tooling: OpenCode (binary)
47+
RUN HOME=/usr/local curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path
48+
RUN ln -sf /usr/local/.opencode/bin/opencode /usr/local/bin/opencode
49+
RUN opencode --version`
4350

4451
const dockerfilePlaywrightMcpBlock = String.raw`RUN npm install -g @playwright/mcp@latest
4552
@@ -151,6 +158,7 @@ export const renderDockerfile = (config: TemplateConfig): string =>
151158
renderDockerfilePrompt(),
152159
renderDockerfileNode(),
153160
renderDockerfileBun(config),
161+
renderDockerfileOpenCode(),
154162
renderDockerfileUsers(config),
155163
renderDockerfileWorkspace(config)
156164
].join("\n\n")

packages/lib/tests/usecases/prepare-files.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ describe("prepareProjectFiles", () => {
107107
expect(dockerfile).toContain("docker-compose-v2")
108108
expect(entrypoint).toContain('DOCKER_GIT_HOME="/home/dev/.docker-git"')
109109
expect(entrypoint).toContain('SOURCE_SHARED_AUTH="/home/dev/.codex-shared/auth.json"')
110+
expect(entrypoint).toContain('OPENCODE_DATA_DIR="/home/dev/.local/share/opencode"')
111+
expect(entrypoint).toContain('OPENCODE_SHARED_HOME="/home/dev/.codex-shared/opencode"')
112+
expect(entrypoint).toContain('OPENCODE_CONFIG_DIR="/home/dev/.config/opencode"')
113+
expect(entrypoint).toContain('"plugin": ["oh-my-opencode"]')
110114
expect(composeBefore).toContain(":/home/dev/.docker-git")
111115
expect(composeBefore).not.toContain("dg-test-browser")
112116

0 commit comments

Comments
 (0)