Repository navigation
Expand file tree
/
Copy pathentrypoint.sh
More file actions
151 lines (137 loc) · 6.83 KB
/
Copy pathentrypoint.sh
File metadata and controls
151 lines (137 loc) · 6.83 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
#!/bin/bash
PUID=${PUID:-1000}
PGID=${PGID:-1000}
OPENCODE_SERVER_PASSWORD=${OPENCODE_SERVER_PASSWORD:-}
if [ "$(id -g node)" -ne "$PGID" ]; then
groupmod -o -g "$PGID" node
fi
if [ "$(id -u node)" -ne "$PUID" ]; then
usermod -o -u "$PUID" node
fi
chown -R node:node /home/node/.local/share/opencode
chown -R node:node /home/node/.config/opencode
chown -R node:node /home/node/project
# Allow overriding via environment variables
# Ports are validated/normalized here so an invalid value never reaches
# `opencode serve --port`; canonical names are exported so the proxy merges
# the same value the container actually uses.
normalize_port() {
local label="$1"
local value="$2"
local fallback="$3"
if [[ "$value" =~ ^[0-9]+$ ]] && [ "$value" -ge 1 ] && [ "$value" -le 65535 ]; then
printf '%s\n' "$value"
return 0
fi
if [ -n "$value" ]; then
echo "[Config] Warning: ${label}=\"${value}\" is not a valid port (1-65535); using ${fallback}" >&2
fi
printf '%s\n' "$fallback"
}
PROXY_PORT=$(normalize_port "OPENCODE_PROXY_PORT" "${OPENCODE_PROXY_PORT:-}" "")
if [ -z "$PROXY_PORT" ] && [[ "${PORT:-}" =~ ^[0-9]+$ ]] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ]; then
PROXY_PORT="$PORT"
elif [ -z "$PROXY_PORT" ] && [ -n "${OPENCODE_PROXY_PORT:-}${PORT:-}" ]; then
echo "[Config] Warning: OPENCODE_PROXY_PORT=\"${OPENCODE_PROXY_PORT:-}\" PORT=\"${PORT:-}\" is not a valid port (1-65535); leaving unset for config.json/file merge" >&2
fi
SERVER_PORT=$(normalize_port "OPENCODE_SERVER_PORT" "${OPENCODE_SERVER_PORT:-}" 10001)
if [ -n "$PROXY_PORT" ]; then
export OPENCODE_PROXY_PORT="$PROXY_PORT"
else
unset OPENCODE_PROXY_PORT
PROXY_PORT=10000
fi
export OPENCODE_SERVER_PORT="$SERVER_PORT"
# Backend permission lockdown (single source: src/backend/backend-permission.ts,
# mirrored in src/backend/manager.ts for the isolated-home path): the headless
# backend must never `ask` (nobody approves -> prompt hangs until the proxy
# 180s timeout) nor silently execute tools the proxy did not authorize (the
# free-tier strip omits the prompt tools map -> backend agent defaults on).
# Deny-all by default; only explicitly allowlisted internal tools are allowed.
# Applies in every prompt mode (standard and plugin-inject alike).
BACKEND_PERM_JSON=""
if [ -f ./dist/src/backend/backend-permission.js ]; then
BACKEND_PERM_JSON=$(node --input-type=module -e "
import('./dist/src/backend/backend-permission.js').then((m) => {
const rawList = process.env['OPENCODE_INTERNAL_ALLOWED_TOOLS'] ?? '';
const rawFetch = (process.env['OPENCODE_INTERNAL_WEB_FETCH_ENABLED'] ?? '').trim().toLowerCase();
const list = String(rawList).split(',').map((s) => s.trim()).filter(Boolean);
const fetchOn = ['1', 'true', 'yes', 'y', 'on'].includes(rawFetch);
console.log(JSON.stringify(m.buildBackendPermission(list, fetchOn)));
}).catch((e) => { console.error(String(e && e.message || e)); process.exit(1); });" 2>/dev/null) || BACKEND_PERM_JSON=""
fi
if [ -z "$BACKEND_PERM_JSON" ]; then
echo "[Config] Warning: backend permission generator unavailable; using static deny-all fallback" >&2
BACKEND_PERM_JSON='{"read":"deny","edit":"deny","glob":"deny","grep":"deny","list":"deny","bash":"deny","task":"deny","todowrite":"deny","question":"deny","webfetch":"deny","websearch":"deny","lsp":"deny","doom_loop":"deny","skill":"deny","external_directory":{"/home/node/project/**":"allow","*":"deny"}}'
fi
PLUGIN_JSON_LINE=""
if [[ "${OPENCODE_PROXY_PROMPT_MODE:-standard}" == "plugin-inject" ]]; then
echo "Preparing opencode2api plugin-inject prompt mode..."
mkdir -p /home/node/.config/opencode/plugin/opencode2api-empty
cat > /home/node/.config/opencode/plugin/opencode2api-empty/index.js <<'EOF'
export const Opencode2apiEmptyPlugin = async () => ({})
export default Opencode2apiEmptyPlugin
EOF
PLUGIN_JSON_LINE=' "plugin": ["/home/node/.config/opencode/plugin/opencode2api-empty/index.js"],'
fi
mkdir -p /home/node/.config/opencode
# printf-built (never an interpolated heredoc): generated JSON cannot be
# shell-expanded into the config even if it ever contains $, backticks, or \.
{
printf '%s\n' '{'
if [ -n "$PLUGIN_JSON_LINE" ]; then printf '%s\n' "$PLUGIN_JSON_LINE"; fi
printf '%s\n' ' "instructions": [],'
printf '%s\n' ' "theme": "system",'
printf ' "permission": %s\n' "$BACKEND_PERM_JSON"
printf '%s\n' '}'
} > /home/node/.config/opencode/opencode.json
chown -R node:node /home/node/.config/opencode
if [[ "$1" == "opencode" && "$2" == "serve" ]]; then
echo "Initializing OpenCode-to-OpenAI (Server + Proxy)"
# opencode serve rewrites <project>/config.json on (nearly) every API call
# (ConfigHttpApi.update) and its own file watcher treats that as a config
# change: it disposes/recreates the project instance, aborting in-flight
# session work. Under concurrent/rapid proxy traffic this becomes a
# self-sustaining abort storm (backend MessageAbortedError -> proxy
# response.failed "Aborted" -> client retries -> more reloads).
# Pin a skeleton config.json root-owned/read-only for the node user so the
# write fails (EACCES, logged by the backend, otherwise harmless) without
# changing mtime, which keeps the watcher quiet. No real settings live in
# this file (backend state stays under /home/node/.local/share/opencode).
if [ ! -f /home/node/project/config.json ]; then
printf '{\n "$schema": "https://opencode.ai/config.json"\n}\n' > /home/node/project/config.json
fi
chown root:root /home/node/project/config.json
chmod 644 /home/node/project/config.json
echo "Starting OpenCode Server on internal port ${SERVER_PORT}..."
gosu node opencode serve --hostname 0.0.0.0 --port ${SERVER_PORT} &
SERVER_PID=$!
echo "Waiting for OpenCode Server to become available..."
# Cold start on small (1GB) hosts can take minutes; do not kill early.
# Budget: 90 x 2s sleep ~= 180s when the backend refuses fast (normal
# cold start; covered by compose start_period 200s). If the backend
# accepts but hangs every probe, each round costs up to curl -m 5 + 2s
# sleep (worst case ~630s) — that means the backend itself is wedged,
# not slow, and the container will exit 1 so it can be rescheduled.
MAX_RETRIES=90
RETRY_DELAY=2
COUNT=0
while ! curl -s -m 5 http://127.0.0.1:${SERVER_PORT}/health > /dev/null; do
if [ $COUNT -ge $MAX_RETRIES ]; then
echo "Timeout waiting for OpenCode Server."
kill $SERVER_PID 2>/dev/null
exit 1
fi
if ! kill -0 $SERVER_PID 2>/dev/null; then
echo "OpenCode Server process died unexpectedly."
exit 1
fi
sleep $RETRY_DELAY
COUNT=$((COUNT+1))
done
echo "OpenCode Server is up!"
echo "Starting OpenAI Proxy on port ${PROXY_PORT}..."
exec gosu node node dist/index.js
else
exec gosu node "$@"
fi