File size: 12,198 Bytes
e6d5d67
 
 
739bd50
 
 
 
 
 
 
 
a7277a1
 
6164163
739bd50
 
 
 
 
 
 
 
 
 
 
e6d5d67
 
 
 
 
 
 
 
 
 
 
 
 
739bd50
 
 
 
 
e6d5d67
 
 
739bd50
 
 
 
 
 
 
 
e6d5d67
 
 
 
6164163
 
 
 
 
 
 
 
 
 
 
a7277a1
 
739bd50
 
 
 
 
 
 
 
 
 
 
e6d5d67
739bd50
 
e6d5d67
 
a7277a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6164163
 
 
 
a7277a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739bd50
 
 
 
e6d5d67
 
739bd50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7277a1
739bd50
 
 
 
 
 
 
e6d5d67
 
739bd50
 
 
 
e6d5d67
739bd50
 
 
e6d5d67
 
a7277a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6164163
 
 
 
a7277a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6164163
a7277a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dd861a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739bd50
 
 
 
 
 
 
e6d5d67
739bd50
 
 
 
 
e6d5d67
 
739bd50
 
e6d5d67
739bd50
 
a7277a1
739bd50
a7277a1
 
 
739bd50
a7277a1
 
 
739bd50
a7277a1
 
dd861a9
739bd50
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
#!/bin/sh
set -eu

log() {
  printf '%s\n' "[hf-code-server-init] $*"
}

DATA_ROOT="${DATA_ROOT:-/data}"
CODER_HOME="${CODER_HOME:-/home/coder}"
BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-/opt/bootstrap}"
CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
CLAUDE_SYSTEM_BIN="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
CLAUDE_VSCODE_WRAPPER="${CLAUDE_VSCODE_WRAPPER:-/usr/local/bin/claude-vscode-wrapper}"
SPACES_SYNC_BIN="${SPACES_SYNC_BIN:-/usr/local/bin/sync-workspace-spaces}"

WORKSPACE_DATA="${DATA_ROOT}/workspace"
WORKSPACE_LINK="${WORKSPACE:-${CODER_HOME}/workspace}"
CODE_SERVER_ROOT="${DATA_ROOT}/code-server"
USER_DATA_DIR="${CODE_SERVER_USER_DATA:-${CODE_SERVER_ROOT}/user-data}"
EXTENSIONS_DIR="${CODE_SERVER_EXTENSIONS:-${CODE_SERVER_ROOT}/extensions}"
CLAUDE_DIR="${DATA_ROOT}/claude"
NGROK_DIR="${DATA_ROOT}/ngrok"
UV_CACHE_DIR_RUNTIME="${DATA_ROOT}/cache/uv"
HF_CACHE_DIR="${DATA_ROOT}/cache/huggingface"

uid="$(id -u)"
gid="$(id -g)"

ensure_dir() {
  directory="$1"

  if ! mkdir -p "${directory}" 2>/dev/null; then
    sudo mkdir -p "${directory}"
  fi

  if [ ! -w "${directory}" ]; then
    sudo chown "${uid}:${gid}" "${directory}"
  fi

  if [ ! -w "${directory}" ]; then
    log "ERROR: ${directory} is not writable by uid ${uid}"
    exit 1
  fi
}

for directory in \
  "${WORKSPACE_DATA}" \
  "${CODE_SERVER_ROOT}" \
  "${USER_DATA_DIR}" \
  "${EXTENSIONS_DIR}" \
  "${CLAUDE_DIR}" \
  "${NGROK_DIR}" \
  "${UV_CACHE_DIR_RUNTIME}" \
  "${HF_CACHE_DIR}"
do
  ensure_dir "${directory}"
done

# DigitalOcean App Platform has no persistent filesystem volumes. When Spaces
# synchronization is configured, restore a snapshot only into a fresh runtime
# workspace. Existing mounted or local workspace data always takes precedence.
if [ -n "${SPACES_BUCKET-}" ]; then
  "${SPACES_SYNC_BIN}" validate

  if [ -z "$(find "${WORKSPACE_DATA}" -mindepth 1 -maxdepth 1 -print -quit)" ]; then
    "${SPACES_SYNC_BIN}" restore
  fi
fi

# Make the requested editor-visible path point to the candidate's persistent
# workspace. The directory is created before code-server is launched.
if [ -L "${WORKSPACE_LINK}" ]; then
  current_target="$(readlink "${WORKSPACE_LINK}" || true)"
  if [ "${current_target}" != "${WORKSPACE_DATA}" ]; then
    rm -f "${WORKSPACE_LINK}"
  fi
elif [ -e "${WORKSPACE_LINK}" ]; then
  if [ -d "${WORKSPACE_LINK}" ]; then
    cp -a -n "${WORKSPACE_LINK}/." "${WORKSPACE_DATA}/" 2>/dev/null || true
  fi
  rm -rf "${WORKSPACE_LINK}"
fi

if [ ! -L "${WORKSPACE_LINK}" ]; then
  ln -s "${WORKSPACE_DATA}" "${WORKSPACE_LINK}"
fi

# Seed the candidate instructions and uv project metadata on first startup.
# Existing candidate edits are never overwritten on later restarts or rebuilds.
for project_file in README.md pyproject.toml uv.lock; do
  source_file="${BOOTSTRAP_ROOT}/workspace-src/${project_file}"
  target_file="${WORKSPACE_DATA}/${project_file}"

  if [ ! -e "${target_file}" ]; then
    cp "${source_file}" "${target_file}"
  fi
done

# Restore the prebuilt environment if it is absent, is not Python 3.12, or was
# built from a different dependency manifest.
expected_venv_id="$(cat "${BOOTSTRAP_ROOT}/venv.id")"
actual_venv_id="$(cat "${WORKSPACE_DATA}/.venv/.hf-bootstrap-id" 2>/dev/null || true)"

if ! "${WORKSPACE_DATA}/.venv/bin/python" -c \
  'import sys; raise SystemExit(sys.version_info[:2] != (3, 12))' \
  >/dev/null 2>&1 \
  || [ "${actual_venv_id}" != "${expected_venv_id}" ]
then
  log "Restoring Python 3.12 virtual environment"
  rm -rf "${WORKSPACE_DATA}/.venv"
  # Some managed volume implementations allow normal file creation but reject
  # restoring archived mtimes. The virtual environment does not depend on those
  # timestamps, so keep extraction portable across managed container platforms.
  tar --touch -C "${WORKSPACE_DATA}" -xzf "${BOOTSTRAP_ROOT}/venv.tar.gz"
fi

# Each candidate Space owns one repository. Initialize it once, leave existing
# history untouched on later restarts, and keep environment artifacts ignored
# without adding a visible top-level .gitignore file.
new_repository=0
if [ ! -d "${WORKSPACE_DATA}/.git" ]; then
  log "Initializing Git repository"
  if ! git -C "${WORKSPACE_DATA}" init -b main >/dev/null 2>&1; then
    git -C "${WORKSPACE_DATA}" init >/dev/null
    git -C "${WORKSPACE_DATA}" symbolic-ref HEAD refs/heads/main
  fi
  new_repository=1
fi

if ! git -C "${WORKSPACE_DATA}" config --get user.name >/dev/null 2>&1; then
  git -C "${WORKSPACE_DATA}" config user.name "${GIT_USER_NAME:-Candidate}"
fi

if ! git -C "${WORKSPACE_DATA}" config --get user.email >/dev/null 2>&1; then
  git -C "${WORKSPACE_DATA}" config user.email "${GIT_USER_EMAIL:-candidate@localhost}"
fi

exclude_file="${WORKSPACE_DATA}/.git/info/exclude"
touch "${exclude_file}"

if [ -f "${BOOTSTRAP_ROOT}/workspace.gitignore" ]; then
  while IFS= read -r pattern || [ -n "${pattern}" ]; do
    case "${pattern}" in
      ''|'#'*) continue ;;
    esac
    if ! grep -Fqx -- "${pattern}" "${exclude_file}"; then
      printf '%s\n' "${pattern}" >> "${exclude_file}"
    fi
  done < "${BOOTSTRAP_ROOT}/workspace.gitignore"
fi

# Commit the supplied instructions and uv project definition as the clean
# baseline for a fresh repository. Existing history is never rewritten.
if [ "${new_repository}" -eq 1 ] \
  || ! git -C "${WORKSPACE_DATA}" rev-parse --verify HEAD >/dev/null 2>&1
then
  git -C "${WORKSPACE_DATA}" add README.md pyproject.toml uv.lock
  if ! git -C "${WORKSPACE_DATA}" diff --cached --quiet; then
    git -C "${WORKSPACE_DATA}" -c commit.gpgsign=false commit \
      -m "Initialize Python environment" >/dev/null
  fi
fi

# Restore preinstalled extensions and the installer metadata they need.
if [ -d "${BOOTSTRAP_ROOT}/code-server-user-data" ] \
  && [ -z "$(find "${USER_DATA_DIR}" -mindepth 1 -maxdepth 1 -print -quit)" ]
then
  cp -a "${BOOTSTRAP_ROOT}/code-server-user-data/." "${USER_DATA_DIR}/"
fi

cp -a -n "${BOOTSTRAP_ROOT}/extensions/." "${EXTENSIONS_DIR}/"

list_extensions() {
  "${CODE_SERVER_BIN}" \
    --user-data-dir "${USER_DATA_DIR}" \
    --extensions-dir "${EXTENSIONS_DIR}" \
    --list-extensions 2>/dev/null || true
}

has_extension() {
  extension_id="$1"
  printf '%s\n' "${installed_extensions}" \
    | grep -Fxi "${extension_id}" >/dev/null 2>&1
}

installed_extensions="$(list_extensions)"

if ! has_extension 'Anthropic.claude-code' || ! has_extension 'ms-python.python'; then
  log "Repairing required extensions"
  "${CODE_SERVER_BIN}" \
    --user-data-dir "${USER_DATA_DIR}" \
    --extensions-dir "${EXTENSIONS_DIR}" \
    --force \
    --install-extension Anthropic.claude-code \
    --install-extension ms-python.python
  installed_extensions="$(list_extensions)"
fi

if ! has_extension 'Anthropic.claude-code'; then
  log "ERROR: Anthropic.claude-code is unavailable after initialization"
  exit 1
fi

if ! has_extension 'ms-python.python'; then
  log "ERROR: ms-python.python is unavailable after initialization"
  exit 1
fi

patch_claude_extension_binaries() {
  test -x "${CLAUDE_SYSTEM_BIN}"

  for extension_dir in \
    "${EXTENSIONS_DIR}"/anthropic.claude-code-* \
    "${EXTENSIONS_DIR}"/Anthropic.claude-code-*
  do
    [ -d "${extension_dir}" ] || continue

    for binary_name in claude claude.real; do
      bundled_binary="${extension_dir}/resources/native-binary/${binary_name}"
      if [ ! -e "${bundled_binary}" ] && [ ! -L "${bundled_binary}" ]; then
        continue
      fi

      if "${bundled_binary}" --version >/dev/null 2>&1; then
        continue
      fi

      log "Replacing incompatible Claude extension binary"
      rm -f "${bundled_binary}"
      ln -s "${CLAUDE_SYSTEM_BIN}" "${bundled_binary}"
      "${bundled_binary}" --version >/dev/null
    done
  done
}

patch_claude_extension_binaries

# Keep editor configuration outside the candidate project. The Python
# environment and Git metadata stay out of Explorer and search; the visible
# baseline consists of README.md, pyproject.toml, and uv.lock. Workspace Trust
# is unnecessary in this dedicated interview container, where the candidate
# already has terminal access, and disabling it lets the Claude extension load
# immediately instead of starting in Restricted Mode.
settings_file="${USER_DATA_DIR}/User/settings.json"
settings_dir="$(dirname "${settings_file}")"
mkdir -p "${settings_dir}"

if [ ! -f "${settings_file}" ]; then
  printf '{}\n' > "${settings_file}"
fi

if jq -e 'type == "object"' "${settings_file}" >/dev/null 2>&1; then
  settings_tmp="${settings_file}.tmp.$$"
  jq \
    --arg wrapper "${CLAUDE_VSCODE_WRAPPER}" \
    --arg python "${WORKSPACE_LINK}/.venv/bin/python" \
    --arg workspace "${WORKSPACE_LINK}" \
    '. + {
      "claudeCode.claudeProcessWrapper": $wrapper,
      "python.defaultInterpreterPath": $python,
      "python.terminal.activateEnvironment": true,
      "security.workspace.trust.enabled": false,
      "terminal.integrated.cwd": $workspace,
      "workbench.startupEditor": "readme",
      "workbench.browser.enableRemoteProxy": true,
      "workbench.browser.dataStorage": "workspace",
      "workbench.browser.openLocalhostLinks": true,
      "workbench.browser.showInTitleBar": true,
      "files.exclude": ((."files.exclude" // {}) + {
        "**/.venv": true,
        "**/.git": true
      }),
      "search.exclude": ((."search.exclude" // {}) + {
        "**/.venv": true,
        "**/.git": true
      })
    }' \
    "${settings_file}" > "${settings_tmp}"
  mv "${settings_tmp}" "${settings_file}"
else
  log "WARNING: ${settings_file} is not plain JSON; leaving it unchanged"
fi

# Verify the Claude wrapper both with and without the extension-injected path.
test -x "${CLAUDE_VSCODE_WRAPPER}"
"${CLAUDE_VSCODE_WRAPPER}" --version >/dev/null
"${CLAUDE_VSCODE_WRAPPER}" \
  "${EXTENSIONS_DIR}/test/resources/native-binary/claude" \
  --version >/dev/null

# The system-level Claude Code policy pins all primary, fallback, quick-task,
# and subagent roles to OpenRouter's Claude Opus 4.8 model. The API token stays
# in the runtime environment and is never written to this file.
CLAUDE_MANAGED_SETTINGS="/etc/claude-code/managed-settings.json"
test -f "${CLAUDE_MANAGED_SETTINGS}"
jq -e '
  .model == "anthropic/claude-opus-4.8"
  and .availableModels == ["anthropic/claude-opus-4.8"]
  and .env.ANTHROPIC_BASE_URL == "https://openrouter.ai/api"
  and .env.ANTHROPIC_API_KEY == ""
  and .env.ANTHROPIC_MODEL == "anthropic/claude-opus-4.8"
  and .env.ANTHROPIC_DEFAULT_OPUS_MODEL == "anthropic/claude-opus-4.8"
  and .env.ANTHROPIC_DEFAULT_SONNET_MODEL == "anthropic/claude-opus-4.8"
  and .env.ANTHROPIC_DEFAULT_HAIKU_MODEL == "anthropic/claude-opus-4.8"
  and .env.CLAUDE_CODE_SUBAGENT_MODEL == "anthropic/claude-opus-4.8"
' "${CLAUDE_MANAGED_SETTINGS}" >/dev/null

# Persist ngrok's default Linux configuration directory.
mkdir -p "${CODER_HOME}/.config"

if [ -L "${CODER_HOME}/.config/ngrok" ]; then
  current_target="$(readlink "${CODER_HOME}/.config/ngrok" || true)"
  if [ "${current_target}" != "${NGROK_DIR}" ]; then
    rm -f "${CODER_HOME}/.config/ngrok"
  fi
elif [ -e "${CODER_HOME}/.config/ngrok" ]; then
  if [ -d "${CODER_HOME}/.config/ngrok" ]; then
    cp -a -n "${CODER_HOME}/.config/ngrok/." "${NGROK_DIR}/" 2>/dev/null || true
  fi
  rm -rf "${CODER_HOME}/.config/ngrok"
fi

if [ ! -L "${CODER_HOME}/.config/ngrok" ]; then
  ln -s "${NGROK_DIR}" "${CODER_HOME}/.config/ngrok"
fi

test -d "${WORKSPACE_LINK}"
test -f "${WORKSPACE_LINK}/README.md"
test -f "${WORKSPACE_LINK}/pyproject.toml"
test -f "${WORKSPACE_LINK}/uv.lock"
test -x "${WORKSPACE_LINK}/.venv/bin/python"
git -C "${WORKSPACE_DATA}" rev-parse --is-inside-work-tree >/dev/null

log "Workspace ready: ${WORKSPACE_LINK}"
log "Candidate instructions ready: ${WORKSPACE_LINK}/README.md"
log "uv project ready: ${WORKSPACE_LINK}/pyproject.toml"
log "Python ready: $("${WORKSPACE_LINK}/.venv/bin/python" --version 2>&1)"
log "Git ready: $(git -C "${WORKSPACE_DATA}" branch --show-current 2>/dev/null || true)"
log "Claude CLI ready: $("${CLAUDE_SYSTEM_BIN}" --version 2>&1)"
log "Claude routing ready: OpenRouter / anthropic/claude-opus-4.8"
log "Extensions ready: $(printf '%s' "${installed_extensions}" | tr '\n' ' ')"