-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathentrypoint.sh
More file actions
executable file
·444 lines (387 loc) · 13.6 KB
/
Copy pathentrypoint.sh
File metadata and controls
executable file
·444 lines (387 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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#!/bin/bash
set -e
# ---------------------------------------------------------------------------
# Defaults
# ---------------------------------------------------------------------------
PUID=${PUID:-1000}
PGID=${PGID:-1000}
OPENCODE_PORT=${OPENCODE_PORT:-4096}
CODE_SERVER_PORT=${CODE_SERVER_PORT:-8080}
HOME_DIR="/home/opencode"
CONFIG_DIR="${HOME_DIR}/.config"
ZSH_CONFIG_DIR="${CONFIG_DIR}/zsh"
OH_MY_ZSH_DIR="${CONFIG_DIR}/oh-my-zsh"
RUN_AS="${PUID}:${PGID}"
SSHD_PID=""
# ---------------------------------------------------------------------------
# User & group setup
# ---------------------------------------------------------------------------
setup_user() {
local groupadd_args=""
local useradd_args=""
if [ "${PGID}" -lt 1000 ]; then
groupadd_args="--system"
fi
if [ "${PUID}" -lt 1000 ]; then
useradd_args="--system"
fi
if getent group "${PGID}" > /dev/null 2>&1; then
echo "Using existing group with GID ${PGID}"
elif getent group opencode > /dev/null 2>&1; then
groupmod -g "${PGID}" opencode
else
groupadd ${groupadd_args} -g "${PGID}" opencode
fi
if id -u "${PUID}" > /dev/null 2>&1; then
echo "Using existing user with UID ${PUID}"
elif id opencode > /dev/null 2>&1; then
usermod -u "${PUID}" -g "${PGID}" -s /bin/zsh opencode
else
useradd ${useradd_args} -u "${PUID}" -g "${PGID}" -d "${HOME_DIR}" -s /bin/zsh opencode
fi
if id opencode > /dev/null 2>&1; then
usermod -s /bin/zsh opencode
# Unlock the account so sshd allows pubkey login.
# useradd sets the password field to '!' (locked) by default, which
# causes OpenSSH to reject the user with "account is locked".
# Setting the password to '*' removes the lock while keeping the
# account safe — '*' is not a valid password hash, so password
# login remains impossible.
usermod -p '*' opencode
fi
}
chown_tree_if_exists() {
for path in "$@"; do
if [ -e "${path}" ]; then
chown -R "${PUID}:${PGID}" "${path}"
fi
done
}
chown_path_if_dir() {
for path in "$@"; do
if [ -d "${path}" ]; then
chown "${PUID}:${PGID}" "${path}"
fi
done
}
append_file_block_if_missing() {
local file="$1"
local marker="$2"
local block="$3"
touch "${file}"
if ! grep -Fq "${marker}" "${file}"; then
printf '\n%s\n' "${block}" >> "${file}"
fi
}
# ---------------------------------------------------------------------------
# File ownership
# ---------------------------------------------------------------------------
fix_ownership() {
# Own the home directory itself and top-level dotfiles/dirs the entrypoint
# creates (shell configs, .ssh, .cache, .local). Skip bind mounts that may
# be read-only or intentionally host-owned.
chown "${PUID}:${PGID}" "${HOME_DIR}"
find "${HOME_DIR}" -maxdepth 1 ! -path "${HOME_DIR}" ! -name ".config" ! -name ".ssh-keys" \
-exec chown -R "${PUID}:${PGID}" {} +
# Own .config directory containers themselves (not recursively — mounted
# files keep their host ownership).
chown_path_if_dir \
"${CONFIG_DIR}" \
"${CONFIG_DIR}/opencode" \
"${CONFIG_DIR}/mise" \
"${ZSH_CONFIG_DIR}" \
"${OH_MY_ZSH_DIR}"
# /repos: only fix the top-level directory so the user can write into it.
# Don't recurse — repos can be huge and the host already owns the files
# via PUID/PGID matching.
chown "${PUID}:${PGID}" /repos
}
# ---------------------------------------------------------------------------
# SSH keys & config
# ---------------------------------------------------------------------------
setup_ssh() {
mkdir -p "${HOME_DIR}/.ssh"
# Copy keys from read-only mount into writable .ssh directory
if [ -d "${HOME_DIR}/.ssh-keys" ]; then
cp -a "${HOME_DIR}/.ssh-keys/." "${HOME_DIR}/.ssh/" 2>/dev/null || true
fi
# Write default SSH config (won't overwrite if user mounted one)
if [ ! -f "${HOME_DIR}/.ssh/config" ]; then
cat > "${HOME_DIR}/.ssh/config" <<'SSHEOF'
Host github.com
StrictHostKeyChecking accept-new
IdentitiesOnly yes
Host *
StrictHostKeyChecking accept-new
SSHEOF
fi
# Fix permissions
chmod 700 "${HOME_DIR}/.ssh"
chmod 600 "${HOME_DIR}/.ssh/config" 2>/dev/null || true
chmod 600 "${HOME_DIR}/.ssh/authorized_keys" 2>/dev/null || true
chmod 600 "${HOME_DIR}/.ssh/id_"* 2>/dev/null || true
chmod 644 "${HOME_DIR}/.ssh/"*.pub 2>/dev/null || true
chmod 644 "${HOME_DIR}/.ssh/known_hosts" 2>/dev/null || true
}
# ---------------------------------------------------------------------------
# SSH server
# ---------------------------------------------------------------------------
setup_sshd() {
mkdir -p /run/sshd
if [ ! -f /etc/ssh/ssh_host_rsa_key ]; then
ssh-keygen -A
fi
cat > /etc/ssh/sshd_config <<SSHEOF
Port 22
Protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM no
PubkeyAuthentication yes
AuthorizedKeysFile ${HOME_DIR}/.ssh/authorized_keys
PermitEmptyPasswords no
X11Forwarding no
PrintMotd no
Subsystem sftp /usr/lib/openssh/sftp-server
PidFile /run/sshd.pid
SSHEOF
}
start_sshd() {
if [ ! -f "${HOME_DIR}/.ssh/authorized_keys" ]; then
echo "Warning: ${HOME_DIR}/.ssh/authorized_keys not found; sshd will start without any authorized keys."
fi
echo "Starting sshd on port 22 (root login disabled, password auth disabled)..."
/usr/sbin/sshd -D -e &
SSHD_PID=$!
}
# ---------------------------------------------------------------------------
# Git configuration
# ---------------------------------------------------------------------------
setup_git() {
# Trust all directories (single-purpose container; all code is under /repos)
if ! gosu "${RUN_AS}" git config --global --get-all safe.directory | grep -Fxq '*'; then
gosu "${RUN_AS}" git config --global --add safe.directory '*'
fi
}
# ---------------------------------------------------------------------------
# Mise toolchain
# ---------------------------------------------------------------------------
setup_mise() {
export HOME="${HOME_DIR}"
# Self-update mise to latest version (skip if updated within the last 24h)
local stamp="/tmp/.mise-last-update"
local now
now=$(date +%s)
local stale=true
if [ -f "${stamp}" ]; then
local last
last=$(cat "${stamp}" 2>/dev/null || echo 0)
if [ $((now - last)) -lt 86400 ]; then
stale=false
fi
fi
# Update the mise binary itself (runs as root because mise lives in /usr/local/bin)
if [ "${stale}" = true ]; then
echo "Checking for mise updates..."
if mise self-update --yes 2>&1; then
echo "${now}" > "${stamp}"
echo "mise is up to date: $(mise --version)"
else
echo "Warning: mise self-update failed, continuing with installed version."
fi
fi
# mise self-update runs as root and may create ~/.cache owned by root.
# Fix ownership so the unprivileged user can write to it later.
chown_tree_if_exists "${HOME_DIR}/.cache"
# Install user-defined toolchains (Node, Python, etc.) from mise config
if [ -f "${HOME_DIR}/.config/mise/config.toml" ]; then
echo "Installing mise tools from config..."
gosu "${RUN_AS}" mise install --yes 2>&1
echo "Mise tools installed."
fi
# Add mise shims to PATH so opencode's bash tool can find installed runtimes
MISE_SHIMS="${HOME_DIR}/.local/share/mise/shims"
export PATH="${MISE_SHIMS}:${PATH}"
}
# ---------------------------------------------------------------------------
# Shell environment
# ---------------------------------------------------------------------------
setup_shell_env() {
local marker="# opencode-docker mise setup"
local zsh_env_marker="# opencode-docker zsh env"
local zsh_marker="# opencode-docker zsh setup"
local profile_block
local bashrc_block
local bash_profile_block
local zshenv_block
local zprofile_block
local zshrc_block
mkdir -p "${ZSH_CONFIG_DIR}"
profile_block=$(cat <<'EOF'
# opencode-docker mise setup
eval "$(mise activate bash --shims)"
EOF
)
bashrc_block=$(cat <<'EOF'
# opencode-docker mise setup
eval "$(mise activate bash)"
EOF
)
bash_profile_block=$(cat <<'EOF'
# opencode-docker mise setup
if [ -f "/home/opencode/.profile" ]; then
. "/home/opencode/.profile"
fi
if [ -f "/home/opencode/.bashrc" ]; then
. "/home/opencode/.bashrc"
fi
EOF
)
zshenv_block=$(cat <<'EOF'
# opencode-docker zsh env
export ZDOTDIR="$HOME/.config/zsh"
export ZSH="$HOME/.config/oh-my-zsh"
EOF
)
zprofile_block=$(cat <<'EOF'
# opencode-docker mise setup
if [ -f "/home/opencode/.profile" ]; then
. "/home/opencode/.profile"
fi
EOF
)
zshrc_block=$(cat <<'EOF'
# opencode-docker zsh setup
export ZSH="$HOME/.config/oh-my-zsh"
ZSH_THEME="robbyrussell"
plugins=(git)
DISABLE_AUTO_UPDATE="true"
source "$ZSH/oh-my-zsh.sh"
eval "$(mise activate zsh)"
EOF
)
append_file_block_if_missing "${HOME_DIR}/.profile" "${marker}" "${profile_block}"
append_file_block_if_missing "${HOME_DIR}/.bashrc" "${marker}" "${bashrc_block}"
append_file_block_if_missing "${HOME_DIR}/.bash_profile" "${marker}" "${bash_profile_block}"
append_file_block_if_missing "${HOME_DIR}/.zshenv" "${zsh_env_marker}" "${zshenv_block}"
append_file_block_if_missing "${ZSH_CONFIG_DIR}/.zprofile" "${marker}" "${zprofile_block}"
append_file_block_if_missing "${ZSH_CONFIG_DIR}/.zshrc" "${zsh_marker}" "${zshrc_block}"
}
# ---------------------------------------------------------------------------
# Oh My Zsh
# ---------------------------------------------------------------------------
setup_oh_my_zsh() {
mkdir -p "${OH_MY_ZSH_DIR}"
if [ ! -d "${OH_MY_ZSH_DIR}/.git" ]; then
echo "Installing Oh My Zsh into ${OH_MY_ZSH_DIR}..."
rm -rf "${OH_MY_ZSH_DIR}"
gosu "${RUN_AS}" env \
HOME="${HOME_DIR}" \
ZSH="${OH_MY_ZSH_DIR}" \
CHSH=no \
RUNZSH=no \
KEEP_ZSHRC=yes \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
echo "Oh My Zsh installed."
fi
}
# ---------------------------------------------------------------------------
# code-server settings
# ---------------------------------------------------------------------------
setup_code_server_settings() {
local settings_dir="${HOME_DIR}/.local/share/code-server/User"
local settings_file="${settings_dir}/settings.json"
local tmp_file
mkdir -p "${settings_dir}"
if [ ! -f "${settings_file}" ]; then
printf '{}\n' > "${settings_file}"
fi
tmp_file=$(mktemp)
jq '
.["terminal.integrated.profiles.linux"].zsh = {
"path": "/bin/zsh"
}
| .["terminal.integrated.defaultProfile.linux"] = "zsh"
' "${settings_file}" > "${tmp_file}"
mv "${tmp_file}" "${settings_file}"
}
# ---------------------------------------------------------------------------
# Start code-server
# ---------------------------------------------------------------------------
start_code_server() {
# Resolve password: reuse OpenCode password if not set separately
local cs_password="${CODE_SERVER_PASSWORD:-${OPENCODE_SERVER_PASSWORD:-}}"
local cs_auth="none"
[ -n "${cs_password}" ] && cs_auth="password"
echo "Starting code-server on port ${CODE_SERVER_PORT} (auth=${cs_auth})..."
gosu "${RUN_AS}" env \
HOME="${HOME_DIR}" \
PATH="${PATH}" \
ZDOTDIR="${ZSH_CONFIG_DIR}" \
ZSH="${OH_MY_ZSH_DIR}" \
PASSWORD="${cs_password}" \
code-server \
--bind-addr "0.0.0.0:${CODE_SERVER_PORT}" \
--user-data-dir "${HOME_DIR}/.local/share/code-server" \
--auth "${cs_auth}" \
--disable-telemetry \
--disable-update-check \
/repos &
CODE_SERVER_PID=$!
}
# ---------------------------------------------------------------------------
# Start opencode
# ---------------------------------------------------------------------------
start_opencode() {
echo "Starting opencode serve on port ${OPENCODE_PORT}..."
gosu "${RUN_AS}" env \
HOME="${HOME_DIR}" \
PATH="${PATH}" \
SHELL="/bin/zsh" \
ZDOTDIR="${ZSH_CONFIG_DIR}" \
ZSH="${OH_MY_ZSH_DIR}" \
opencode serve \
--port "${OPENCODE_PORT}" \
--hostname 0.0.0.0 &
OPENCODE_PID=$!
}
# ---------------------------------------------------------------------------
# Graceful shutdown
# ---------------------------------------------------------------------------
cleanup() {
echo "Shutting down..."
kill "$SSHD_PID" 2>/dev/null
kill "$CODE_SERVER_PID" 2>/dev/null
kill "$OPENCODE_PID" 2>/dev/null
wait
}
# ===========================================================================
# Main
# ===========================================================================
echo "Starting opencode-docker"
echo " PUID=${PUID} PGID=${PGID}"
echo " OpenCode port=${OPENCODE_PORT}"
echo " code-server port=${CODE_SERVER_PORT}"
setup_user
mkdir -p /repos
setup_ssh
setup_sshd
setup_shell_env
setup_code_server_settings
fix_ownership
setup_oh_my_zsh
setup_git
setup_mise
trap cleanup SIGTERM SIGINT
start_sshd
start_code_server
start_opencode
# Wait for either process to exit; if one dies, stop the other
wait -n
echo "A process exited, shutting down..."
cleanup