-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdocker-entrypoint.sh
More file actions
403 lines (362 loc) · 13.6 KB
/
docker-entrypoint.sh
File metadata and controls
403 lines (362 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
#!/bin/bash
set -e
DEVA_USER="${DEVA_USER:-deva}"
DEVA_UID="${DEVA_UID:-1001}"
DEVA_GID="${DEVA_GID:-1001}"
DEVA_HOME="${DEVA_HOME:-/home/deva}"
DEVA_AGENT="${DEVA_AGENT:-claude}"
get_claude_version() {
local version=""
for path in "/usr/local/bin/claude" "/usr/bin/claude" "$(command -v claude 2>/dev/null)"; do
if [ -x "$path" ]; then
version=$($path --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
break
fi
done
echo "$version"
}
get_codex_version() {
local version=""
for path in "/usr/local/bin/codex" "/usr/bin/codex" "$(command -v codex 2>/dev/null)"; do
if [ -x "$path" ]; then
version=$($path --version 2>/dev/null | head -1)
break
fi
done
echo "$version"
}
show_environment_info() {
local header="[deva]"
case "$DEVA_AGENT" in
claude)
if [ -n "$CLAUDE_VERSION" ]; then
header+=" Starting Claude Code v$CLAUDE_VERSION"
else
header+=" Claude Code (version detection failed)"
fi
;;
codex)
if [ -n "$CODEX_VERSION" ]; then
header+=" Starting Codex ($CODEX_VERSION)"
else
header+=" Codex CLI (version detection failed)"
fi
;;
*)
header+=" Starting agent: $DEVA_AGENT"
;;
esac
echo "$header"
if [ "$VERBOSE" = "true" ]; then
echo ""
echo "deva.sh YOLO Environment"
echo "================================"
echo "Agent: $DEVA_AGENT"
echo "Working Directory: $(pwd)"
echo "Running as: $(whoami) (UID=$(id -u), GID=$(id -g))"
echo "Python: $(python3 --version 2>/dev/null || echo 'Not found')"
echo "Node.js: $(node --version 2>/dev/null || echo 'Not found')"
echo "Go: $(go version 2>/dev/null || echo 'Not found')"
echo ""
sleep 0.1
if [ "$DEVA_AGENT" = "claude" ]; then
local cli_path=""
for path in "/usr/local/bin/claude" "/usr/bin/claude" "$(command -v claude 2>/dev/null)"; do
if [ -x "$path" ]; then
cli_path="$path"
break
fi
done
if [ -n "$cli_path" ]; then
echo "Claude CLI: $($cli_path --version 2>/dev/null || echo 'Found but version failed')"
echo "Claude location: $cli_path"
else
echo "Claude CLI not found in PATH"
fi
if [ -d "$DEVA_HOME/.claude" ]; then
echo "Claude auth directory mounted"
# shellcheck disable=SC2012
ls -la "$DEVA_HOME/.claude" | head -5
else
echo "warning: Claude auth directory not found in $DEVA_HOME/.claude"
fi
else
local codex_path=""
for path in "/usr/local/bin/codex" "/usr/bin/codex" "$(command -v codex 2>/dev/null)"; do
if [ -x "$path" ]; then
codex_path="$path"
break
fi
done
if [ -n "$codex_path" ]; then
echo "Codex CLI: $($codex_path --version 2>/dev/null || echo 'Found but version failed')"
echo "Codex location: $codex_path"
else
echo "Codex CLI not found in PATH"
fi
if [ -d "$DEVA_HOME/.codex" ]; then
echo "Codex auth directory mounted"
# shellcheck disable=SC2012
ls -la "$DEVA_HOME/.codex" | head -5
else
echo "warning: Codex auth directory not found in $DEVA_HOME/.codex"
fi
fi
if [ -n "$grpc_proxy" ] || [ -n "$HTTP_PROXY" ] || [ -n "$HTTPS_PROXY" ]; then
echo "Proxy configuration:"
[ -n "$grpc_proxy" ] && echo " gRPC: $grpc_proxy"
[ -n "$HTTPS_PROXY" ] && echo " HTTPS: $HTTPS_PROXY"
[ -n "$HTTP_PROXY" ] && echo " HTTP: $HTTP_PROXY"
fi
echo "Running as: $DEVA_USER (UID=$DEVA_UID, GID=$DEVA_GID)"
echo "================================"
fi
}
normalize_locale_variant() {
local in="$1"
[ -n "$in" ] || return 0
local base="${in%%.*}"
local enc="${in#*.}"
if [ "$base" = "$in" ]; then
printf '%s' "$in"
return 0
fi
enc=$(printf '%s' "$enc" | tr '[:upper:]' '[:lower:]')
if [ "$enc" = "utf8" ] || [ "$enc" = "utf-8" ]; then
printf '%s.UTF-8' "$base"
else
printf '%s.%s' "$base" "$enc"
fi
}
ensure_timezone() {
if [ -n "$TZ" ] && [ -e "/usr/share/zoneinfo/$TZ" ]; then
ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime 2>/dev/null || true
echo "$TZ" >/etc/timezone 2>/dev/null || true
fi
}
ensure_locale() {
local want="${LC_ALL:-${LANG:-}}"
[ -n "$want" ] || return 0
local want_norm
want_norm=$(normalize_locale_variant "$want")
local want_lower
want_lower=$(printf '%s' "$want_norm" | tr '[:upper:]' '[:lower:]')
if locale -a 2>/dev/null | tr '[:upper:]' '[:lower:]' | grep -qx -- "$want_lower"; then
return 0
fi
local base="${want_norm%%.*}"
local gen_line="${base}.UTF-8 UTF-8"
if ! grep -q -E "^\s*${base}\.UTF-8\s+UTF-8\s*$" /etc/locale.gen 2>/dev/null; then
printf '%s\n' "$gen_line" >>/etc/locale.gen 2>/dev/null || true
fi
if command -v locale-gen >/dev/null 2>&1; then
locale-gen "$base.UTF-8" >/dev/null 2>&1 || true
fi
if command -v update-locale >/dev/null 2>&1; then
update-locale LANG="$LANG" LC_ALL="${LC_ALL:-$LANG}" LANGUAGE="${LANGUAGE:-}" >/dev/null 2>&1 || true
fi
}
setup_nonroot_user() {
local current_uid
current_uid=$(id -u "$DEVA_USER")
local current_gid
current_gid=$(id -g "$DEVA_USER")
if [ "$DEVA_UID" = "0" ]; then
echo "[entrypoint] WARNING: Host UID is 0. Using fallback 1000."
DEVA_UID=1000
fi
if [ "$DEVA_GID" = "0" ]; then
echo "[entrypoint] WARNING: Host GID is 0. Using fallback 1000."
DEVA_GID=1000
fi
if [ "$DEVA_GID" != "$current_gid" ]; then
[ "$VERBOSE" = "true" ] && echo "[entrypoint] updating $DEVA_USER GID: $current_gid -> $DEVA_GID"
if getent group "$DEVA_GID" >/dev/null 2>&1; then
local existing_group
existing_group=$(getent group "$DEVA_GID" | cut -d: -f1)
usermod -g "$DEVA_GID" "$DEVA_USER" 2>/dev/null || true
[ "$VERBOSE" = "true" ] && echo "[entrypoint] joined existing group $existing_group"
else
groupmod -g "$DEVA_GID" "$DEVA_USER"
fi
fi
if [ "$DEVA_UID" != "$current_uid" ]; then
[ "$VERBOSE" = "true" ] && echo "[entrypoint] updating $DEVA_USER UID: $current_uid -> $DEVA_UID"
# usermod may fail with rc=12 when it can't chown home directory (mounted volumes)
# The UID change itself usually succeeds even when chown fails
if ! usermod -u "$DEVA_UID" -g "$DEVA_GID" "$DEVA_USER" 2>/dev/null; then
# Verify what UID we actually got
local actual_uid
actual_uid=$(id -u "$DEVA_USER" 2>/dev/null)
if [ -z "$actual_uid" ]; then
echo "[entrypoint] ERROR: cannot determine UID for $DEVA_USER" >&2
exit 1
fi
if [ "$actual_uid" != "$DEVA_UID" ]; then
echo "[entrypoint] WARNING: UID change failed ($DEVA_USER is UID $actual_uid, wanted $DEVA_UID)" >&2
# Adapt to reality so subsequent operations use correct UID
DEVA_UID="$actual_uid"
fi
fi
# Fix container-managed directories (whitelist approach - safe for mounted volumes)
# These directories are created at image build time and must be chowned to match host UID
for dir in .npm-global .local .oh-my-zsh .skills .config .cache go; do
if [ -d "$DEVA_HOME/$dir" ] && [ ! -L "$DEVA_HOME/$dir" ]; then
chown -R "$DEVA_UID:$DEVA_GID" "$DEVA_HOME/$dir" 2>/dev/null || true
fi
done
# Fix container-created dotfiles
find "$DEVA_HOME" -maxdepth 1 \( -type f -o -type d \) -name '.*' \
! -name '..' ! -name '.' \
-exec chown "$DEVA_UID:$DEVA_GID" {} \; 2>/dev/null || true
fi
chmod 755 /root 2>/dev/null || true
}
fix_rust_permissions() {
local rh="/opt/rustup"
local ch="/opt/cargo"
if [ -d "$rh" ]; then chown -R "$DEVA_UID:$DEVA_GID" "$rh" 2>/dev/null || true; fi
if [ -d "$ch" ]; then chown -R "$DEVA_UID:$DEVA_GID" "$ch" 2>/dev/null || true; fi
[ -d "$rh" ] || mkdir -p "$rh"
[ -d "$ch" ] || mkdir -p "$ch"
}
fix_docker_socket_permissions() {
if [ -S /var/run/docker.sock ]; then
chmod 666 /var/run/docker.sock 2>/dev/null || true
fi
}
get_node_tmpdir() {
local tmpdir="${TMPDIR:-/tmp}"
if command -v node >/dev/null 2>&1; then
local detected
detected=$(node -p 'require("os").tmpdir()' 2>/dev/null || true)
if [ -n "$detected" ]; then
tmpdir="$detected"
fi
fi
printf '%s' "$tmpdir"
}
setup_claude_chrome_bridge() {
if [ "${DEVA_CHROME_HOST_BRIDGE:-}" != "1" ]; then
return 0
fi
local host_bridge_dir="${DEVA_CHROME_HOST_BRIDGE_DIR:-/deva-host-chrome-bridge}"
if [ ! -d "$host_bridge_dir" ]; then
echo "[entrypoint] error: Claude in Chrome bridge mount missing: $host_bridge_dir" >&2
exit 1
fi
local container_tmpdir
container_tmpdir="$(get_node_tmpdir)"
mkdir -p "$container_tmpdir"
local container_socket_path="$container_tmpdir/claude-mcp-browser-bridge-$DEVA_USER"
rm -f "$container_socket_path" 2>/dev/null || true
ln -snf "$host_bridge_dir" "$container_socket_path"
chown -h "$DEVA_UID:$DEVA_GID" "$container_socket_path" 2>/dev/null || true
if [ "$VERBOSE" = "true" ]; then
echo "[entrypoint] Claude in Chrome bridge: $container_socket_path -> $host_bridge_dir"
fi
}
build_gosu_env_cmd() {
local user="$1"
shift
exec gosu "$user" env "HOME=$DEVA_HOME" "PATH=$PATH" "$@"
}
ensure_agent_binaries() {
case "$DEVA_AGENT" in
claude)
if ! command -v claude >/dev/null 2>&1; then
echo "error: Claude CLI not found in container"
exit 1
fi
;;
codex)
if ! command -v codex >/dev/null 2>&1; then
echo "error: Codex CLI not found in container"
exit 1
fi
;;
esac
}
main() {
export PATH="/home/deva/.local/bin:/home/deva/.npm-global/bin:/root/.local/bin:/usr/local/go/bin:/opt/cargo/bin:/usr/local/cargo/bin:$PATH"
export RUSTUP_HOME="${RUSTUP_HOME:-/opt/rustup}"
export CARGO_HOME="${CARGO_HOME:-/opt/cargo}"
CLAUDE_VERSION="$(get_claude_version)"
CODEX_VERSION="$(get_codex_version)"
if [ -n "$ANTHROPIC_BASE_URL" ]; then
case "$ANTHROPIC_BASE_URL" in
http://localhost:* | http://localhost/* | http://127.0.0.1:* | http://127.0.0.1/*)
export ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL/localhost/host.docker.internal}"
export ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL/127.0.0.1/host.docker.internal}"
;;
esac
fi
ensure_timezone
ensure_locale
show_environment_info
if [ -n "$WORKDIR" ] && [ -d "$WORKDIR" ]; then
cd "$WORKDIR"
fi
setup_nonroot_user
setup_claude_chrome_bridge
fix_rust_permissions
fix_docker_socket_permissions
ensure_agent_binaries
if [ $# -eq 0 ]; then
if [ "$DEVA_AGENT" = "codex" ]; then
build_gosu_env_cmd "$DEVA_USER" codex --dangerously-bypass-approvals-and-sandbox -m "${DEVA_DEFAULT_CODEX_MODEL:-gpt-5-codex}"
else
build_gosu_env_cmd "$DEVA_USER" claude --dangerously-skip-permissions
fi
return
fi
cmd="$1"
shift
if [ "$DEVA_AGENT" = "claude" ]; then
if [ "$cmd" = "claude" ] || [ "$cmd" = "$(command -v claude 2>/dev/null)" ]; then
# Add --dangerously-skip-permissions if not already present
local has_dsp=false
for arg in "$@"; do
if [ "$arg" = "--dangerously-skip-permissions" ]; then
has_dsp=true
break
fi
done
if [ "$has_dsp" = true ]; then
build_gosu_env_cmd "$DEVA_USER" "$cmd" "$@"
else
build_gosu_env_cmd "$DEVA_USER" "$cmd" "$@" --dangerously-skip-permissions
fi
elif [ "$cmd" = "claude-trace" ]; then
# claude-trace: ensure --dangerously-skip-permissions follows --run-with
local has_dsp=false
for arg in "$@"; do
if [ "$arg" = "--dangerously-skip-permissions" ]; then
has_dsp=true
break
fi
done
if [ "$has_dsp" = true ]; then
# Already has --dangerously-skip-permissions, pass through
build_gosu_env_cmd "$DEVA_USER" "$cmd" "$@"
else
# Insert --dangerously-skip-permissions after --run-with
args=("$@")
new_args=()
for arg in "${args[@]}"; do
if [ "$arg" = "--run-with" ]; then
new_args+=("--run-with" "--dangerously-skip-permissions")
else
new_args+=("$arg")
fi
done
build_gosu_env_cmd "$DEVA_USER" "$cmd" "${new_args[@]}"
fi
else
build_gosu_env_cmd "$DEVA_USER" "$cmd" "$@"
fi
else
build_gosu_env_cmd "$DEVA_USER" "$cmd" "$@"
fi
}
main "$@"