adithya-interview / scripts /10-init-persistent-data.sh
calmodovar23's picture
Upload 13 files
6164163 verified
Raw
History Blame Contribute Delete
12.2 kB
#!/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' ' ')"