Skip to content

Commit d2d98f3

Browse files
committed
fix(lib): pass vibecode-linter
1 parent 86ca11b commit d2d98f3

30 files changed

+1934
-2078
lines changed

packages/lib/.jscpd.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"threshold": 0,
3-
"minTokens": 30,
4-
"minLines": 5,
3+
"minTokens": 50,
4+
"minLines": 15,
55
"ignore": [
66
"**/node_modules/**",
77
"**/build/**",

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

Lines changed: 21 additions & 586 deletions
Large diffs are not rendered by default.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import type { TemplateConfig } from "../domain.js"
2+
import { renderInputRc } from "../templates-prompt.js"
3+
4+
export const renderEntrypointHeader = (config: TemplateConfig): string =>
5+
`#!/usr/bin/env bash
6+
set -euo pipefail
7+
8+
REPO_URL="\${REPO_URL:-}"
9+
REPO_REF="\${REPO_REF:-}"
10+
FORK_REPO_URL="\${FORK_REPO_URL:-}"
11+
TARGET_DIR="\${TARGET_DIR:-${config.targetDir}}"
12+
GIT_AUTH_USER="\${GIT_AUTH_USER:-\${GITHUB_USER:-x-access-token}}"
13+
GIT_AUTH_TOKEN="\${GIT_AUTH_TOKEN:-\${GITHUB_TOKEN:-}}"
14+
GH_TOKEN="\${GH_TOKEN:-\${GIT_AUTH_TOKEN:-}}"
15+
GIT_USER_NAME="\${GIT_USER_NAME:-}"
16+
GIT_USER_EMAIL="\${GIT_USER_EMAIL:-}"
17+
CODEX_AUTO_UPDATE="\${CODEX_AUTO_UPDATE:-1}"
18+
MCP_PLAYWRIGHT_ENABLE="\${MCP_PLAYWRIGHT_ENABLE:-${config.enableMcpPlaywright ? "1" : "0"}}"
19+
MCP_PLAYWRIGHT_CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
20+
MCP_PLAYWRIGHT_ISOLATED="\${MCP_PLAYWRIGHT_ISOLATED:-1}"`
21+
22+
export const renderEntrypointAuthorizedKeys = (config: TemplateConfig): string =>
23+
`# 1) Authorized keys are mounted from host at /authorized_keys
24+
mkdir -p /home/${config.sshUser}/.ssh
25+
chmod 700 /home/${config.sshUser}/.ssh
26+
27+
if [[ -f /authorized_keys ]]; then
28+
cp /authorized_keys /home/${config.sshUser}/.ssh/authorized_keys
29+
chmod 600 /home/${config.sshUser}/.ssh/authorized_keys
30+
fi
31+
32+
chown -R 1000:1000 /home/${config.sshUser}/.ssh`
33+
34+
export const renderEntrypointDockerSocket = (config: TemplateConfig): string =>
35+
`# Ensure docker socket access for ${config.sshUser}
36+
if [[ -S /var/run/docker.sock ]]; then
37+
DOCKER_SOCK_GID="$(stat -c "%g" /var/run/docker.sock)"
38+
DOCKER_GROUP="$(getent group "$DOCKER_SOCK_GID" | cut -d: -f1 || true)"
39+
if [[ -z "$DOCKER_GROUP" ]]; then
40+
DOCKER_GROUP="docker"
41+
groupadd -g "$DOCKER_SOCK_GID" "$DOCKER_GROUP" || true
42+
fi
43+
usermod -aG "$DOCKER_GROUP" ${config.sshUser} || true
44+
printf "export DOCKER_HOST=unix:///var/run/docker.sock\n" > /etc/profile.d/docker-host.sh
45+
fi`
46+
47+
export const renderEntrypointZshShell = (config: TemplateConfig): string =>
48+
String.raw`# Prefer zsh for ${config.sshUser} when available
49+
if command -v zsh >/dev/null 2>&1; then
50+
usermod -s /usr/bin/zsh ${config.sshUser} || true
51+
fi`
52+
53+
export const renderEntrypointZshUserRc = (config: TemplateConfig): string =>
54+
String.raw`# Ensure ${config.sshUser} has a zshrc and disable newuser wizard
55+
ZSHENV_PATH="/etc/zsh/zshenv"
56+
if [[ -f "$ZSHENV_PATH" ]]; then
57+
if ! grep -q "ZSH_DISABLE_NEWUSER_INSTALL" "$ZSHENV_PATH"; then
58+
printf "%s\n" "export ZSH_DISABLE_NEWUSER_INSTALL=1" >> "$ZSHENV_PATH"
59+
fi
60+
else
61+
printf "%s\n" "export ZSH_DISABLE_NEWUSER_INSTALL=1" > "$ZSHENV_PATH"
62+
fi
63+
USER_ZSHRC="/home/${config.sshUser}/.zshrc"
64+
if [[ ! -f "$USER_ZSHRC" ]]; then
65+
cat <<'EOF' > "$USER_ZSHRC"
66+
# docker-git default zshrc
67+
if [ -f /etc/zsh/zshrc ]; then
68+
source /etc/zsh/zshrc
69+
fi
70+
EOF
71+
chown 1000:1000 "$USER_ZSHRC" || true
72+
fi`
73+
74+
export const renderEntrypointInputRc = (config: TemplateConfig): string =>
75+
String.raw`# Ensure readline history search bindings for ${config.sshUser}
76+
INPUTRC_PATH="/home/${config.sshUser}/.inputrc"
77+
if [[ ! -f "$INPUTRC_PATH" ]]; then
78+
cat <<'EOF' > "$INPUTRC_PATH"
79+
${renderInputRc()}
80+
EOF
81+
chown 1000:1000 "$INPUTRC_PATH" || true
82+
fi`
83+
84+
export const renderEntrypointBaseline = (): string =>
85+
`# 4.5) Snapshot baseline processes for terminal session filtering
86+
mkdir -p /run/docker-git
87+
BASELINE_PATH="/run/docker-git/terminal-baseline.pids"
88+
if [[ ! -f "$BASELINE_PATH" ]]; then
89+
ps -eo pid= > "$BASELINE_PATH" || true
90+
fi`
91+
92+
export const renderEntrypointDisableMotd = (): string =>
93+
String.raw`# 4.75) Disable Ubuntu MOTD noise for SSH sessions
94+
PAM_SSHD="/etc/pam.d/sshd"
95+
if [[ -f "$PAM_SSHD" ]]; then
96+
sed -i 's/^[[:space:]]*session[[:space:]]\+optional[[:space:]]\+pam_motd\.so/#&/' "$PAM_SSHD" || true
97+
sed -i 's/^[[:space:]]*session[[:space:]]\+optional[[:space:]]\+pam_lastlog\.so/#&/' "$PAM_SSHD" || true
98+
fi
99+
100+
# Also disable sshd's own banners (e.g. "Last login")
101+
mkdir -p /etc/ssh/sshd_config.d || true
102+
DOCKER_GIT_SSHD_CONF="/etc/ssh/sshd_config.d/zz-docker-git-clean.conf"
103+
cat <<'EOF' > "$DOCKER_GIT_SSHD_CONF"
104+
PrintMotd no
105+
PrintLastLog no
106+
EOF
107+
chmod 0644 "$DOCKER_GIT_SSHD_CONF" || true`
108+
109+
export const renderEntrypointSshd = (): string => `# 5) Run sshd in foreground\nexec /usr/sbin/sshd -D`
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import type { TemplateConfig } from "../domain.js"
2+
3+
export const renderEntrypointCodexHome = (config: TemplateConfig): string =>
4+
`# Ensure Codex home exists if mounted
5+
mkdir -p ${config.codexHome}
6+
chown -R 1000:1000 ${config.codexHome}
7+
8+
# Ensure home ownership matches the dev UID/GID (volumes may be stale)
9+
HOME_OWNER="$(stat -c "%u:%g" /home/${config.sshUser} 2>/dev/null || echo "")"
10+
if [[ "$HOME_OWNER" != "1000:1000" ]]; then
11+
chown -R 1000:1000 /home/${config.sshUser} || true
12+
fi`
13+
14+
export const renderEntrypointCodexSharedAuth = (config: TemplateConfig): string =>
15+
`# Share Codex auth.json across projects (avoids refresh_token_reused)
16+
CODEX_SHARE_AUTH="\${CODEX_SHARE_AUTH:-1}"
17+
if [[ "$CODEX_SHARE_AUTH" == "1" ]]; then
18+
CODEX_SHARED_HOME="${config.codexHome}-shared"
19+
mkdir -p "$CODEX_SHARED_HOME"
20+
chown -R 1000:1000 "$CODEX_SHARED_HOME" || true
21+
22+
AUTH_FILE="${config.codexHome}/auth.json"
23+
SHARED_AUTH_FILE="$CODEX_SHARED_HOME/auth.json"
24+
25+
# Guard against a bad bind mount creating a directory at auth.json.
26+
if [[ -d "$AUTH_FILE" ]]; then
27+
mv "$AUTH_FILE" "$AUTH_FILE.bak-$(date +%s)" || true
28+
fi
29+
if [[ -e "$AUTH_FILE" && ! -L "$AUTH_FILE" ]]; then
30+
rm -f "$AUTH_FILE" || true
31+
fi
32+
33+
ln -sf "$SHARED_AUTH_FILE" "$AUTH_FILE"
34+
fi`
35+
36+
const entrypointMcpPlaywrightTemplate = String.raw`# Optional: configure Playwright MCP for Codex (browser automation)
37+
CODEX_CONFIG_FILE="__CODEX_HOME__/config.toml"
38+
39+
# Keep config.toml consistent with the container build.
40+
# If Playwright MCP is disabled for this container, remove the block so Codex
41+
# doesn't try (and fail) to spawn docker-git-playwright-mcp.
42+
if [[ "$MCP_PLAYWRIGHT_ENABLE" != "1" ]]; then
43+
if [[ -f "$CODEX_CONFIG_FILE" ]] && grep -q "^\[mcp_servers\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
44+
awk '
45+
BEGIN { skip=0 }
46+
/^# docker-git: Playwright MCP/ { next }
47+
/^\[mcp_servers[.]playwright([.]|\])/ { skip=1; next }
48+
skip==1 && /^\[/ { skip=0 }
49+
skip==0 { print }
50+
' "$CODEX_CONFIG_FILE" > "$CODEX_CONFIG_FILE.tmp"
51+
mv "$CODEX_CONFIG_FILE.tmp" "$CODEX_CONFIG_FILE"
52+
fi
53+
else
54+
if [[ ! -f "$CODEX_CONFIG_FILE" ]]; then
55+
mkdir -p "$(dirname "$CODEX_CONFIG_FILE")" || true
56+
cat <<'EOF' > "$CODEX_CONFIG_FILE"
57+
# docker-git codex config
58+
model = "gpt-5.3-codex"
59+
model_reasoning_effort = "xhigh"
60+
personality = "pragmatic"
61+
62+
approval_policy = "never"
63+
sandbox_mode = "danger-full-access"
64+
web_search = "live"
65+
66+
[features]
67+
web_search_request = true
68+
shell_snapshot = true
69+
collab = true
70+
apps = true
71+
shell_tool = true
72+
EOF
73+
chown 1000:1000 "$CODEX_CONFIG_FILE" || true
74+
fi
75+
76+
if [[ -z "$MCP_PLAYWRIGHT_CDP_ENDPOINT" ]]; then
77+
MCP_PLAYWRIGHT_CDP_ENDPOINT="http://__SERVICE_NAME__-browser:9223"
78+
fi
79+
80+
# Replace the docker-git Playwright block to allow upgrades via --force without manual edits.
81+
if grep -q "^\[mcp_servers\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
82+
awk '
83+
BEGIN { skip=0 }
84+
/^# docker-git: Playwright MCP/ { next }
85+
/^\[mcp_servers[.]playwright([.]|\])/ { skip=1; next }
86+
skip==1 && /^\[/ { skip=0 }
87+
skip==0 { print }
88+
' "$CODEX_CONFIG_FILE" > "$CODEX_CONFIG_FILE.tmp"
89+
mv "$CODEX_CONFIG_FILE.tmp" "$CODEX_CONFIG_FILE"
90+
fi
91+
92+
cat <<EOF >> "$CODEX_CONFIG_FILE"
93+
94+
# docker-git: Playwright MCP (connects to Chromium via CDP)
95+
[mcp_servers.playwright]
96+
command = "docker-git-playwright-mcp"
97+
args = []
98+
EOF
99+
fi`
100+
101+
export const renderEntrypointMcpPlaywright = (config: TemplateConfig): string =>
102+
entrypointMcpPlaywrightTemplate
103+
.replaceAll("__CODEX_HOME__", config.codexHome)
104+
.replaceAll("__SERVICE_NAME__", config.serviceName)
105+
106+
export const renderEntrypointCodexResumeHint = (): string =>
107+
`# Ensure codex resume hint is shown for interactive shells
108+
CODEX_HINT_PATH="/etc/profile.d/zz-codex-resume.sh"
109+
if [[ ! -s "$CODEX_HINT_PATH" ]]; then
110+
cat <<'EOF' > "$CODEX_HINT_PATH"
111+
if [ -n "$BASH_VERSION" ]; then
112+
case "$-" in
113+
*i*)
114+
if [ -z "\${CODEX_RESUME_HINT_SHOWN-}" ]; then
115+
echo "Старые сессии можно запустить с помощью codex resume или codex resume <id>, если знаешь айди."
116+
export CODEX_RESUME_HINT_SHOWN=1
117+
fi
118+
;;
119+
esac
120+
fi
121+
if [ -n "$ZSH_VERSION" ]; then
122+
if [[ "$-" == *i* ]]; then
123+
if [[ -z "\${CODEX_RESUME_HINT_SHOWN-}" ]]; then
124+
echo "Старые сессии можно запустить с помощью codex resume или codex resume <id>, если знаешь айди."
125+
export CODEX_RESUME_HINT_SHOWN=1
126+
fi
127+
fi
128+
fi
129+
EOF
130+
chmod 0644 "$CODEX_HINT_PATH"
131+
fi
132+
if ! grep -q "zz-codex-resume.sh" /etc/bash.bashrc 2>/dev/null; then
133+
printf "%s\\n" "if [ -f /etc/profile.d/zz-codex-resume.sh ]; then . /etc/profile.d/zz-codex-resume.sh; fi" >> /etc/bash.bashrc
134+
fi
135+
if [[ -s /etc/zsh/zshrc ]] && ! grep -q "zz-codex-resume.sh" /etc/zsh/zshrc 2>/dev/null; then
136+
printf "%s\\n" "if [ -f /etc/profile.d/zz-codex-resume.sh ]; then source /etc/profile.d/zz-codex-resume.sh; fi" >> /etc/zsh/zshrc
137+
fi`
138+
139+
export const renderEntrypointAgentsNotice = (config: TemplateConfig): string =>
140+
String.raw`# Ensure global AGENTS.md exists for container context
141+
AGENTS_PATH="${config.codexHome}/AGENTS.md"
142+
LEGACY_AGENTS_PATH="/home/${config.sshUser}/AGENTS.md"
143+
PROJECT_LINE="Рабочая папка проекта (git clone): ${config.targetDir}"
144+
INTERNET_LINE="Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе."
145+
if [[ ! -f "$AGENTS_PATH" ]]; then
146+
cat <<'AGENTS_EOF' > "$AGENTS_PATH"
147+
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
148+
Рабочая папка проекта (git clone): ${config.targetDir}
149+
Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
150+
Если ты видишь файлы AGENTS.md внутри проекта, ты обязан их читать и соблюдать инструкции.
151+
AGENTS_EOF
152+
chown 1000:1000 "$AGENTS_PATH" || true
153+
fi
154+
if [[ -f "$AGENTS_PATH" ]]; then
155+
if grep -q "^Рабочая папка проекта (git clone):" "$AGENTS_PATH"; then
156+
sed -i "s|^Рабочая папка проекта (git clone):.*$|$PROJECT_LINE|" "$AGENTS_PATH"
157+
else
158+
printf "%s\n" "$PROJECT_LINE" >> "$AGENTS_PATH"
159+
fi
160+
if grep -q "^Доступ к интернету:" "$AGENTS_PATH"; then
161+
sed -i "s|^Доступ к интернету:.*$|$INTERNET_LINE|" "$AGENTS_PATH"
162+
else
163+
printf "%s\n" "$INTERNET_LINE" >> "$AGENTS_PATH"
164+
fi
165+
fi
166+
if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then
167+
LEGACY_SUM="$(cksum "$LEGACY_AGENTS_PATH" 2>/dev/null | awk '{print $1 \":\" $2}')"
168+
CODEX_SUM="$(cksum "$AGENTS_PATH" 2>/dev/null | awk '{print $1 \":\" $2}')"
169+
if [[ -n "$LEGACY_SUM" && "$LEGACY_SUM" == "$CODEX_SUM" ]]; then
170+
rm -f "$LEGACY_AGENTS_PATH"
171+
fi
172+
fi`
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { TemplateConfig } from "../domain.js"
2+
3+
export const renderEntrypointGitConfig = (config: TemplateConfig): string =>
4+
String.raw`# 2) Ensure GH_TOKEN is available for SSH sessions if provided
5+
if [[ -n "$GH_TOKEN" ]]; then
6+
printf "export GH_TOKEN=%q\n" "$GH_TOKEN" > /etc/profile.d/gh-token.sh
7+
chmod 0644 /etc/profile.d/gh-token.sh
8+
SSH_ENV_PATH="/home/${config.sshUser}/.ssh/environment"
9+
printf "%s\n" "GH_TOKEN=$GH_TOKEN" > "$SSH_ENV_PATH"
10+
if [[ -n "$GITHUB_TOKEN" ]]; then
11+
printf "%s\n" "GITHUB_TOKEN=$GITHUB_TOKEN" >> "$SSH_ENV_PATH"
12+
fi
13+
chmod 600 "$SSH_ENV_PATH"
14+
chown 1000:1000 "$SSH_ENV_PATH" || true
15+
fi
16+
17+
# 3) Configure git identity for the dev user if provided
18+
if [[ -n "$GIT_USER_NAME" ]]; then
19+
SAFE_GIT_USER_NAME="$(printf "%q" "$GIT_USER_NAME")"
20+
su - ${config.sshUser} -c "git config --global user.name $SAFE_GIT_USER_NAME"
21+
fi
22+
23+
if [[ -n "$GIT_USER_EMAIL" ]]; then
24+
SAFE_GIT_USER_EMAIL="$(printf "%q" "$GIT_USER_EMAIL")"
25+
su - ${config.sshUser} -c "git config --global user.email $SAFE_GIT_USER_EMAIL"
26+
fi`
27+
28+
export const renderEntrypointGitHooks = (): string =>
29+
String.raw`# 3) Install global git hooks to protect main/master
30+
HOOKS_DIR="/opt/docker-git/hooks"
31+
PRE_PUSH_HOOK="$HOOKS_DIR/pre-push"
32+
mkdir -p "$HOOKS_DIR"
33+
if [[ ! -f "$PRE_PUSH_HOOK" ]]; then
34+
cat <<'EOF' > "$PRE_PUSH_HOOK"
35+
#!/usr/bin/env bash
36+
set -euo pipefail
37+
38+
protected_branches=("refs/heads/main" "refs/heads/master")
39+
allow_delete="${"${"}DOCKER_GIT_ALLOW_DELETE:-}"
40+
41+
while read -r local_ref local_sha remote_ref remote_sha; do
42+
if [[ -z "$remote_ref" ]]; then
43+
continue
44+
fi
45+
for protected in "${"${"}protected_branches[@]}"; do
46+
if [[ "$remote_ref" == "$protected" || "$local_ref" == "$protected" ]]; then
47+
echo "docker-git: push to protected branch '${"${"}protected##*/}' is disabled."
48+
echo "docker-git: create a new branch: git checkout -b <name>"
49+
exit 1
50+
fi
51+
done
52+
if [[ "$local_sha" == "0000000000000000000000000000000000000000" && "$remote_ref" == refs/heads/* ]]; then
53+
if [[ "$allow_delete" != "1" ]]; then
54+
echo "docker-git: deleting remote branches is disabled (set DOCKER_GIT_ALLOW_DELETE=1 to override)."
55+
exit 1
56+
fi
57+
fi
58+
done
59+
EOF
60+
chmod 0755 "$PRE_PUSH_HOOK"
61+
fi
62+
git config --system core.hooksPath "$HOOKS_DIR" || true
63+
git config --global core.hooksPath "$HOOKS_DIR" || true`

0 commit comments

Comments
 (0)