Spaces:
Paused
Paused
| 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' ' ')" | |