Spaces:
Paused
Paused
Upload 11 files
Browse files- .dockerignore +14 -0
- .gitignore +10 -0
- .python-version +1 -0
- Dockerfile +47 -41
- README.md +7 -109
- pyproject.toml +6 -3
- scripts/10-init-persistent-data.sh +166 -33
- scripts/claude-vscode-wrapper.sh +20 -0
- scripts/start-code-server.sh +7 -6
- workspace.gitignore +13 -0
- workspace/README.md +98 -0
.dockerignore
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.venv
|
| 3 |
+
uv.lock
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.py[cod]
|
| 6 |
+
.pytest_cache/
|
| 7 |
+
.mypy_cache/
|
| 8 |
+
.ruff_cache/
|
| 9 |
+
.env
|
| 10 |
+
.env.*
|
| 11 |
+
.claude/
|
| 12 |
+
.claude.json
|
| 13 |
+
.DS_Store
|
| 14 |
+
*.zip
|
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
uv.lock
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.py[cod]
|
| 5 |
+
.env
|
| 6 |
+
.env.*
|
| 7 |
+
.claude/
|
| 8 |
+
.claude.json
|
| 9 |
+
.DS_Store
|
| 10 |
+
*.zip
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.12
|
Dockerfile
CHANGED
|
@@ -6,35 +6,46 @@ ARG UV_VERSION=0.11.29
|
|
| 6 |
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
|
| 7 |
FROM ghcr.io/coder/code-server:${CODE_SERVER_VERSION}
|
| 8 |
|
|
|
|
|
|
|
| 9 |
USER root
|
| 10 |
|
| 11 |
-
# Install a small
|
| 12 |
# and the official ngrok Agent CLI.
|
| 13 |
-
RUN
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
build-essential \
|
| 16 |
ca-certificates \
|
| 17 |
curl \
|
|
|
|
| 18 |
jq \
|
| 19 |
pkg-config \
|
| 20 |
ripgrep \
|
| 21 |
-
unzip \
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
-o /etc/apt/keyrings/claude-code.asc \
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
> /etc/apt/sources.list.d/claude-code.list \
|
| 28 |
-
|
| 29 |
-
-o /etc/apt/keyrings/ngrok.asc \
|
| 30 |
-
|
| 31 |
'deb [signed-by=/etc/apt/keyrings/ngrok.asc] https://ngrok-agent.s3.amazonaws.com buster main' \
|
| 32 |
-
> /etc/apt/sources.list.d/ngrok.list \
|
| 33 |
-
|
| 34 |
-
|
| 35 |
claude-code \
|
| 36 |
-
ngrok \
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
COPY --from=uv /uv /uvx /usr/local/bin/
|
| 40 |
|
|
@@ -48,10 +59,12 @@ ENV HOME=/home/coder \
|
|
| 48 |
PATH="/home/coder/workspace/.venv/bin:/home/coder/.local/bin:${PATH}" \
|
| 49 |
PYTHONDONTWRITEBYTECODE=1 \
|
| 50 |
PYTHONUNBUFFERED=1 \
|
| 51 |
-
CS_DISABLE_GETTING_STARTED_OVERRIDE=true
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
# /data is mounted
|
| 54 |
-
#
|
| 55 |
RUN install -d -o coder -g coder \
|
| 56 |
/opt/bootstrap \
|
| 57 |
/opt/bootstrap/code-server-user-data \
|
|
@@ -60,25 +73,28 @@ RUN install -d -o coder -g coder \
|
|
| 60 |
/opt/uv/python \
|
| 61 |
/home/coder/workspace
|
| 62 |
|
| 63 |
-
COPY --chown=coder:coder pyproject.toml /
|
|
|
|
|
|
|
| 64 |
|
| 65 |
USER coder
|
| 66 |
|
| 67 |
-
#
|
| 68 |
-
#
|
|
|
|
| 69 |
RUN set -eux; \
|
| 70 |
-
cp /tmp/workspace-deps/pyproject.toml /home/coder/workspace/pyproject.toml; \
|
| 71 |
cd /home/coder/workspace; \
|
| 72 |
uv python install 3.12; \
|
| 73 |
uv sync --python 3.12 --no-install-project; \
|
| 74 |
-
cp uv.lock /opt/bootstrap/
|
|
|
|
|
|
|
| 75 |
tar -C /home/coder/workspace -czf /opt/bootstrap/venv.tar.gz .venv; \
|
| 76 |
cd /; \
|
| 77 |
uv cache clean; \
|
| 78 |
-
rm -rf /home/coder/workspace
|
| 79 |
|
| 80 |
# Install and verify the requested extensions into image-side bootstrap paths.
|
| 81 |
-
# Runtime initialization copies them to /data and repairs the registry if needed.
|
| 82 |
RUN set -eux; \
|
| 83 |
code-server \
|
| 84 |
--user-data-dir /opt/bootstrap/code-server-user-data \
|
|
@@ -93,32 +109,22 @@ RUN set -eux; \
|
|
| 93 |
grep -Fxi 'ms-python.python' /tmp/preinstalled-extensions.txt; \
|
| 94 |
rm -f /tmp/preinstalled-extensions.txt
|
| 95 |
|
| 96 |
-
# Seed the Space repository into the persistent workspace. Include the generated
|
| 97 |
-
# uv.lock even when it is excluded from the repository build context.
|
| 98 |
-
COPY --chown=coder:coder . /opt/bootstrap/workspace-src/
|
| 99 |
-
|
| 100 |
-
RUN set -eux; \
|
| 101 |
-
cp /opt/bootstrap/uv.lock /opt/bootstrap/workspace-src/uv.lock; \
|
| 102 |
-
tar -C /opt/bootstrap/workspace-src -czf /opt/bootstrap/workspace-seed.tar.gz .; \
|
| 103 |
-
rm -rf /opt/bootstrap/workspace-src
|
| 104 |
-
|
| 105 |
USER root
|
| 106 |
|
| 107 |
COPY --chown=root:root --chmod=0755 scripts/10-init-persistent-data.sh \
|
| 108 |
/usr/local/bin/init-persistent-data
|
| 109 |
COPY --chown=root:root --chmod=0755 scripts/start-code-server.sh \
|
| 110 |
/usr/local/bin/start-code-server
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
# These locations exist only after the HF persistent disk is mounted.
|
| 113 |
ENV UV_CACHE_DIR=/data/cache/uv \
|
| 114 |
HF_HOME=/data/cache/huggingface \
|
| 115 |
CLAUDE_CONFIG_DIR=/data/claude
|
| 116 |
|
| 117 |
USER coder
|
| 118 |
|
| 119 |
-
# The workspace symlink is created
|
| 120 |
-
# an image-resident directory. The wrapper changes into the workspace before
|
| 121 |
-
# launching code-server.
|
| 122 |
WORKDIR /home/coder
|
| 123 |
|
| 124 |
EXPOSE 7860
|
|
|
|
| 6 |
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
|
| 7 |
FROM ghcr.io/coder/code-server:${CODE_SERVER_VERSION}
|
| 8 |
|
| 9 |
+
ARG CLAUDE_CODE_CHANNEL=latest
|
| 10 |
+
|
| 11 |
USER root
|
| 12 |
|
| 13 |
+
# Install a small native build toolchain, the official Claude Code CLI,
|
| 14 |
# and the official ngrok Agent CLI.
|
| 15 |
+
RUN set -eux; \
|
| 16 |
+
case "${CLAUDE_CODE_CHANNEL}" in latest|stable) ;; *) exit 2 ;; esac; \
|
| 17 |
+
apt-get update; \
|
| 18 |
+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
| 19 |
build-essential \
|
| 20 |
ca-certificates \
|
| 21 |
curl \
|
| 22 |
+
git \
|
| 23 |
jq \
|
| 24 |
pkg-config \
|
| 25 |
ripgrep \
|
| 26 |
+
unzip; \
|
| 27 |
+
install -d -m 0755 /etc/apt/keyrings; \
|
| 28 |
+
curl -fsSL https://downloads.claude.ai/keys/claude-code.asc \
|
| 29 |
+
-o /etc/apt/keyrings/claude-code.asc; \
|
| 30 |
+
printf '%s\n' \
|
| 31 |
+
"deb [signed-by=/etc/apt/keyrings/claude-code.asc] https://downloads.claude.ai/claude-code/apt/${CLAUDE_CODE_CHANNEL} ${CLAUDE_CODE_CHANNEL} main" \
|
| 32 |
+
> /etc/apt/sources.list.d/claude-code.list; \
|
| 33 |
+
curl -fsSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
|
| 34 |
+
-o /etc/apt/keyrings/ngrok.asc; \
|
| 35 |
+
printf '%s\n' \
|
| 36 |
'deb [signed-by=/etc/apt/keyrings/ngrok.asc] https://ngrok-agent.s3.amazonaws.com buster main' \
|
| 37 |
+
> /etc/apt/sources.list.d/ngrok.list; \
|
| 38 |
+
apt-get update; \
|
| 39 |
+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
| 40 |
claude-code \
|
| 41 |
+
ngrok; \
|
| 42 |
+
CLAUDE_BIN="$(command -v claude)"; \
|
| 43 |
+
test -x "${CLAUDE_BIN}"; \
|
| 44 |
+
ln -sf "${CLAUDE_BIN}" /usr/local/bin/claude-system; \
|
| 45 |
+
/usr/local/bin/claude-system --version; \
|
| 46 |
+
ngrok version; \
|
| 47 |
+
git --version; \
|
| 48 |
+
rm -rf /var/lib/apt/lists/*
|
| 49 |
|
| 50 |
COPY --from=uv /uv /uvx /usr/local/bin/
|
| 51 |
|
|
|
|
| 59 |
PATH="/home/coder/workspace/.venv/bin:/home/coder/.local/bin:${PATH}" \
|
| 60 |
PYTHONDONTWRITEBYTECODE=1 \
|
| 61 |
PYTHONUNBUFFERED=1 \
|
| 62 |
+
CS_DISABLE_GETTING_STARTED_OVERRIDE=true \
|
| 63 |
+
CLAUDE_SYSTEM_BIN=/usr/local/bin/claude-system \
|
| 64 |
+
CLAUDE_VSCODE_WRAPPER=/usr/local/bin/claude-vscode-wrapper
|
| 65 |
|
| 66 |
+
# /data is mounted at runtime. Build all reusable artifacts in the image and
|
| 67 |
+
# restore them into the persistent disk from the startup initializer.
|
| 68 |
RUN install -d -o coder -g coder \
|
| 69 |
/opt/bootstrap \
|
| 70 |
/opt/bootstrap/code-server-user-data \
|
|
|
|
| 73 |
/opt/uv/python \
|
| 74 |
/home/coder/workspace
|
| 75 |
|
| 76 |
+
COPY --chown=coder:coder pyproject.toml /home/coder/workspace/pyproject.toml
|
| 77 |
+
COPY --chown=coder:coder workspace/README.md /opt/bootstrap/workspace-src/README.md
|
| 78 |
+
COPY --chown=coder:coder workspace.gitignore /opt/bootstrap/workspace.gitignore
|
| 79 |
|
| 80 |
USER coder
|
| 81 |
|
| 82 |
+
# Resolve the dependency manifest and build the Python 3.12 environment at the
|
| 83 |
+
# same path it will use at runtime. Archive the environment and preserve the
|
| 84 |
+
# project manifest and lockfile for the candidate workspace.
|
| 85 |
RUN set -eux; \
|
|
|
|
| 86 |
cd /home/coder/workspace; \
|
| 87 |
uv python install 3.12; \
|
| 88 |
uv sync --python 3.12 --no-install-project; \
|
| 89 |
+
cp pyproject.toml uv.lock /opt/bootstrap/workspace-src/; \
|
| 90 |
+
cat pyproject.toml uv.lock | sha256sum | awk '{print $1}' \
|
| 91 |
+
| tee /opt/bootstrap/venv.id > .venv/.hf-bootstrap-id; \
|
| 92 |
tar -C /home/coder/workspace -czf /opt/bootstrap/venv.tar.gz .venv; \
|
| 93 |
cd /; \
|
| 94 |
uv cache clean; \
|
| 95 |
+
rm -rf /home/coder/workspace
|
| 96 |
|
| 97 |
# Install and verify the requested extensions into image-side bootstrap paths.
|
|
|
|
| 98 |
RUN set -eux; \
|
| 99 |
code-server \
|
| 100 |
--user-data-dir /opt/bootstrap/code-server-user-data \
|
|
|
|
| 109 |
grep -Fxi 'ms-python.python' /tmp/preinstalled-extensions.txt; \
|
| 110 |
rm -f /tmp/preinstalled-extensions.txt
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
USER root
|
| 113 |
|
| 114 |
COPY --chown=root:root --chmod=0755 scripts/10-init-persistent-data.sh \
|
| 115 |
/usr/local/bin/init-persistent-data
|
| 116 |
COPY --chown=root:root --chmod=0755 scripts/start-code-server.sh \
|
| 117 |
/usr/local/bin/start-code-server
|
| 118 |
+
COPY --chown=root:root --chmod=0755 scripts/claude-vscode-wrapper.sh \
|
| 119 |
+
/usr/local/bin/claude-vscode-wrapper
|
| 120 |
|
|
|
|
| 121 |
ENV UV_CACHE_DIR=/data/cache/uv \
|
| 122 |
HF_HOME=/data/cache/huggingface \
|
| 123 |
CLAUDE_CONFIG_DIR=/data/claude
|
| 124 |
|
| 125 |
USER coder
|
| 126 |
|
| 127 |
+
# The workspace symlink is created after the HF persistent disk is mounted.
|
|
|
|
|
|
|
| 128 |
WORKDIR /home/coder
|
| 129 |
|
| 130 |
EXPOSE 7860
|
README.md
CHANGED
|
@@ -1,117 +1,15 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
-
#
|
| 12 |
|
| 13 |
-
Open the
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
Required:
|
| 18 |
-
|
| 19 |
-
- `PASSWORD` — code-server web-login password
|
| 20 |
-
|
| 21 |
-
Optional:
|
| 22 |
-
|
| 23 |
-
- `NGROK_AUTHTOKEN` — authenticates the ngrok agent
|
| 24 |
-
- `OPENAI_API_KEY` — used by the OpenAI Python SDK
|
| 25 |
-
- `ANTHROPIC_API_KEY` — used by the Anthropic Python SDK
|
| 26 |
-
|
| 27 |
-
For Claude Code subscription authentication, initially omit `ANTHROPIC_API_KEY` and run:
|
| 28 |
-
|
| 29 |
-
```bash
|
| 30 |
-
claude
|
| 31 |
-
```
|
| 32 |
-
|
| 33 |
-
## Runtime layout
|
| 34 |
-
|
| 35 |
-
- Physical persistent workspace: `/data/workspace`
|
| 36 |
-
- IDE-visible workspace: `/home/coder/workspace`
|
| 37 |
-
- Python 3.12 environment: `/home/coder/workspace/.venv`
|
| 38 |
-
- code-server user data: `/data/code-server/user-data`
|
| 39 |
-
- code-server extensions: `/data/code-server/extensions`
|
| 40 |
-
- Claude Code configuration: `/data/claude`
|
| 41 |
-
- ngrok configuration: `/data/ngrok`
|
| 42 |
-
|
| 43 |
-
The startup wrapper initializes these paths before code-server starts, changes into `/home/coder/workspace`, and opens that directory as the default folder.
|
| 44 |
-
|
| 45 |
-
## Expected startup log
|
| 46 |
-
|
| 47 |
-
```text
|
| 48 |
-
[hf-code-server-init] Workspace ready: /home/coder/workspace -> /data/workspace
|
| 49 |
-
[hf-code-server-init] Python ready: Python 3.12.x
|
| 50 |
-
[hf-code-server-init] Extensions ready: Anthropic.claude-code ms-python.python ...
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
## Verify from the integrated terminal
|
| 54 |
-
|
| 55 |
-
```bash
|
| 56 |
-
pwd
|
| 57 |
-
readlink -f /home/coder/workspace
|
| 58 |
-
python --version
|
| 59 |
-
python -c "import anthropic, fastapi, openai; print('imports OK')"
|
| 60 |
-
|
| 61 |
-
code-server \
|
| 62 |
-
--user-data-dir /data/code-server/user-data \
|
| 63 |
-
--extensions-dir /data/code-server/extensions \
|
| 64 |
-
--list-extensions
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
Expected essentials:
|
| 68 |
-
|
| 69 |
-
```text
|
| 70 |
-
/home/coder/workspace
|
| 71 |
-
/data/workspace
|
| 72 |
-
Python 3.12.x
|
| 73 |
-
Anthropic.claude-code
|
| 74 |
-
ms-python.python
|
| 75 |
-
```
|
| 76 |
-
|
| 77 |
-
Dependencies are declared in `pyproject.toml`. After editing it:
|
| 78 |
-
|
| 79 |
-
```bash
|
| 80 |
-
uv sync
|
| 81 |
-
```
|
| 82 |
-
|
| 83 |
-
## FastAPI and ngrok
|
| 84 |
-
|
| 85 |
-
```bash
|
| 86 |
-
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
| 87 |
-
```
|
| 88 |
-
|
| 89 |
-
Authenticated code-server proxy:
|
| 90 |
-
|
| 91 |
-
```text
|
| 92 |
-
https://<space-subdomain>.hf.space/proxy/8000/
|
| 93 |
-
```
|
| 94 |
-
|
| 95 |
-
Public ngrok tunnel:
|
| 96 |
-
|
| 97 |
-
```bash
|
| 98 |
-
ngrok http 8000
|
| 99 |
-
```
|
| 100 |
-
|
| 101 |
-
## Reset stale POC state
|
| 102 |
-
|
| 103 |
-
The new initializer should repair the existing disk. To reset only code-server settings and extensions while preserving project files:
|
| 104 |
-
|
| 105 |
-
```bash
|
| 106 |
-
sudo rm -rf /data/code-server
|
| 107 |
-
```
|
| 108 |
-
|
| 109 |
-
Then restart the Space.
|
| 110 |
-
|
| 111 |
-
To discard the workspace and its virtual environment too:
|
| 112 |
-
|
| 113 |
-
```bash
|
| 114 |
-
sudo rm -rf /data/workspace /data/code-server
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
Do not run the second command when the workspace contains anything you need.
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Coding Interview Workspace
|
| 3 |
+
emoji: 💻
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# Coding Interview Workspace
|
| 12 |
|
| 13 |
+
Open the application using the URL and password supplied by your interviewer.
|
| 14 |
|
| 15 |
+
Candidate instructions are available as `README.md` in the workspace that opens in the editor.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pyproject.toml
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
[project]
|
| 2 |
-
name = "
|
| 3 |
version = "0.1.0"
|
| 4 |
-
description = "
|
| 5 |
requires-python = ">=3.12,<3.13"
|
| 6 |
dependencies = [
|
| 7 |
"anthropic",
|
|
@@ -10,6 +14,5 @@ dependencies = [
|
|
| 10 |
"uvicorn[standard]",
|
| 11 |
]
|
| 12 |
|
| 13 |
-
# This is an application workspace, not a package to publish.
|
| 14 |
[tool.uv]
|
| 15 |
package = false
|
|
|
|
| 1 |
+
# This project is managed with uv.
|
| 2 |
+
# Add a dependency with: uv add <package>
|
| 3 |
+
# Synchronize the environment with: uv sync
|
| 4 |
+
|
| 5 |
[project]
|
| 6 |
+
name = "interview-workspace"
|
| 7 |
version = "0.1.0"
|
| 8 |
+
description = "Python environment for the coding interview workspace"
|
| 9 |
requires-python = ">=3.12,<3.13"
|
| 10 |
dependencies = [
|
| 11 |
"anthropic",
|
|
|
|
| 14 |
"uvicorn[standard]",
|
| 15 |
]
|
| 16 |
|
|
|
|
| 17 |
[tool.uv]
|
| 18 |
package = false
|
scripts/10-init-persistent-data.sh
CHANGED
|
@@ -9,6 +9,8 @@ DATA_ROOT="${DATA_ROOT:-/data}"
|
|
| 9 |
CODER_HOME="${CODER_HOME:-/home/coder}"
|
| 10 |
BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-/opt/bootstrap}"
|
| 11 |
CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
|
|
|
|
|
|
|
| 12 |
|
| 13 |
WORKSPACE_DATA="${DATA_ROOT}/workspace"
|
| 14 |
WORKSPACE_LINK="${WORKSPACE:-${CODER_HOME}/workspace}"
|
|
@@ -53,30 +55,8 @@ do
|
|
| 53 |
ensure_dir "${directory}"
|
| 54 |
done
|
| 55 |
|
| 56 |
-
#
|
| 57 |
-
#
|
| 58 |
-
seed_marker="${WORKSPACE_DATA}/.hf-code-server-seed-v4"
|
| 59 |
-
if [ ! -f "${seed_marker}" ]; then
|
| 60 |
-
log "Seeding persistent workspace at ${WORKSPACE_DATA}"
|
| 61 |
-
tar --skip-old-files \
|
| 62 |
-
-C "${WORKSPACE_DATA}" \
|
| 63 |
-
-xzf "${BOOTSTRAP_ROOT}/workspace-seed.tar.gz"
|
| 64 |
-
touch "${seed_marker}"
|
| 65 |
-
fi
|
| 66 |
-
|
| 67 |
-
# Restore the prebuilt Python 3.12 virtual environment when it is absent or
|
| 68 |
-
# invalid. It was built at /home/coder/workspace/.venv, so script shebangs keep
|
| 69 |
-
# working through the runtime workspace symlink.
|
| 70 |
-
if ! "${WORKSPACE_DATA}/.venv/bin/python" -c \
|
| 71 |
-
'import sys; raise SystemExit(sys.version_info[:2] != (3, 12))' \
|
| 72 |
-
>/dev/null 2>&1
|
| 73 |
-
then
|
| 74 |
-
log "Restoring Python 3.12 virtual environment"
|
| 75 |
-
rm -rf "${WORKSPACE_DATA}/.venv"
|
| 76 |
-
tar -C "${WORKSPACE_DATA}" -xzf "${BOOTSTRAP_ROOT}/venv.tar.gz"
|
| 77 |
-
fi
|
| 78 |
-
|
| 79 |
-
# Make the requested IDE-visible path point to persistent storage.
|
| 80 |
if [ -L "${WORKSPACE_LINK}" ]; then
|
| 81 |
current_target="$(readlink "${WORKSPACE_LINK}" || true)"
|
| 82 |
if [ "${current_target}" != "${WORKSPACE_DATA}" ]; then
|
|
@@ -93,8 +73,80 @@ if [ ! -L "${WORKSPACE_LINK}" ]; then
|
|
| 93 |
ln -s "${WORKSPACE_DATA}" "${WORKSPACE_LINK}"
|
| 94 |
fi
|
| 95 |
|
| 96 |
-
# Seed
|
| 97 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
if [ -d "${BOOTSTRAP_ROOT}/code-server-user-data" ] \
|
| 99 |
&& [ -z "$(find "${USER_DATA_DIR}" -mindepth 1 -maxdepth 1 -print -quit)" ]
|
| 100 |
then
|
|
@@ -118,11 +170,8 @@ has_extension() {
|
|
| 118 |
|
| 119 |
installed_extensions="$(list_extensions)"
|
| 120 |
|
| 121 |
-
# Copying extension directories is usually enough, but an old persistent
|
| 122 |
-
# extension registry can hide them. Let code-server repair its own registry if
|
| 123 |
-
# either required extension is not visible.
|
| 124 |
if ! has_extension 'Anthropic.claude-code' || ! has_extension 'ms-python.python'; then
|
| 125 |
-
log "Repairing required extensions
|
| 126 |
"${CODE_SERVER_BIN}" \
|
| 127 |
--user-data-dir "${USER_DATA_DIR}" \
|
| 128 |
--extensions-dir "${EXTENSIONS_DIR}" \
|
|
@@ -142,6 +191,84 @@ if ! has_extension 'ms-python.python'; then
|
|
| 142 |
exit 1
|
| 143 |
fi
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
# Persist ngrok's default Linux configuration directory.
|
| 146 |
mkdir -p "${CODER_HOME}/.config"
|
| 147 |
|
|
@@ -161,11 +288,17 @@ if [ ! -L "${CODER_HOME}/.config/ngrok" ]; then
|
|
| 161 |
ln -s "${NGROK_DIR}" "${CODER_HOME}/.config/ngrok"
|
| 162 |
fi
|
| 163 |
|
| 164 |
-
# These checks are fatal because the startup wrapper calls this script directly.
|
| 165 |
test -d "${WORKSPACE_LINK}"
|
| 166 |
-
test -
|
| 167 |
test -f "${WORKSPACE_LINK}/pyproject.toml"
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
-
log "Workspace ready: ${WORKSPACE_LINK}
|
|
|
|
|
|
|
| 170 |
log "Python ready: $("${WORKSPACE_LINK}/.venv/bin/python" --version 2>&1)"
|
|
|
|
|
|
|
| 171 |
log "Extensions ready: $(printf '%s' "${installed_extensions}" | tr '\n' ' ')"
|
|
|
|
| 9 |
CODER_HOME="${CODER_HOME:-/home/coder}"
|
| 10 |
BOOTSTRAP_ROOT="${BOOTSTRAP_ROOT:-/opt/bootstrap}"
|
| 11 |
CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
|
| 12 |
+
CLAUDE_SYSTEM_BIN="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
|
| 13 |
+
CLAUDE_VSCODE_WRAPPER="${CLAUDE_VSCODE_WRAPPER:-/usr/local/bin/claude-vscode-wrapper}"
|
| 14 |
|
| 15 |
WORKSPACE_DATA="${DATA_ROOT}/workspace"
|
| 16 |
WORKSPACE_LINK="${WORKSPACE:-${CODER_HOME}/workspace}"
|
|
|
|
| 55 |
ensure_dir "${directory}"
|
| 56 |
done
|
| 57 |
|
| 58 |
+
# Make the requested editor-visible path point to the candidate's persistent
|
| 59 |
+
# workspace. The directory is created before code-server is launched.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
if [ -L "${WORKSPACE_LINK}" ]; then
|
| 61 |
current_target="$(readlink "${WORKSPACE_LINK}" || true)"
|
| 62 |
if [ "${current_target}" != "${WORKSPACE_DATA}" ]; then
|
|
|
|
| 73 |
ln -s "${WORKSPACE_DATA}" "${WORKSPACE_LINK}"
|
| 74 |
fi
|
| 75 |
|
| 76 |
+
# Seed the candidate instructions and uv project metadata on first startup.
|
| 77 |
+
# Existing candidate edits are never overwritten on later restarts or rebuilds.
|
| 78 |
+
for project_file in README.md pyproject.toml uv.lock; do
|
| 79 |
+
source_file="${BOOTSTRAP_ROOT}/workspace-src/${project_file}"
|
| 80 |
+
target_file="${WORKSPACE_DATA}/${project_file}"
|
| 81 |
+
|
| 82 |
+
if [ ! -e "${target_file}" ]; then
|
| 83 |
+
cp "${source_file}" "${target_file}"
|
| 84 |
+
fi
|
| 85 |
+
done
|
| 86 |
+
|
| 87 |
+
# Restore the prebuilt environment if it is absent, is not Python 3.12, or was
|
| 88 |
+
# built from a different dependency manifest.
|
| 89 |
+
expected_venv_id="$(cat "${BOOTSTRAP_ROOT}/venv.id")"
|
| 90 |
+
actual_venv_id="$(cat "${WORKSPACE_DATA}/.venv/.hf-bootstrap-id" 2>/dev/null || true)"
|
| 91 |
+
|
| 92 |
+
if ! "${WORKSPACE_DATA}/.venv/bin/python" -c \
|
| 93 |
+
'import sys; raise SystemExit(sys.version_info[:2] != (3, 12))' \
|
| 94 |
+
>/dev/null 2>&1 \
|
| 95 |
+
|| [ "${actual_venv_id}" != "${expected_venv_id}" ]
|
| 96 |
+
then
|
| 97 |
+
log "Restoring Python 3.12 virtual environment"
|
| 98 |
+
rm -rf "${WORKSPACE_DATA}/.venv"
|
| 99 |
+
tar -C "${WORKSPACE_DATA}" -xzf "${BOOTSTRAP_ROOT}/venv.tar.gz"
|
| 100 |
+
fi
|
| 101 |
+
|
| 102 |
+
# Each candidate Space owns one repository. Initialize it once, leave existing
|
| 103 |
+
# history untouched on later restarts, and keep environment artifacts ignored
|
| 104 |
+
# without adding a visible top-level .gitignore file.
|
| 105 |
+
new_repository=0
|
| 106 |
+
if [ ! -d "${WORKSPACE_DATA}/.git" ]; then
|
| 107 |
+
log "Initializing Git repository"
|
| 108 |
+
if ! git -C "${WORKSPACE_DATA}" init -b main >/dev/null 2>&1; then
|
| 109 |
+
git -C "${WORKSPACE_DATA}" init >/dev/null
|
| 110 |
+
git -C "${WORKSPACE_DATA}" symbolic-ref HEAD refs/heads/main
|
| 111 |
+
fi
|
| 112 |
+
new_repository=1
|
| 113 |
+
fi
|
| 114 |
+
|
| 115 |
+
if ! git -C "${WORKSPACE_DATA}" config --get user.name >/dev/null 2>&1; then
|
| 116 |
+
git -C "${WORKSPACE_DATA}" config user.name "${GIT_USER_NAME:-Candidate}"
|
| 117 |
+
fi
|
| 118 |
+
|
| 119 |
+
if ! git -C "${WORKSPACE_DATA}" config --get user.email >/dev/null 2>&1; then
|
| 120 |
+
git -C "${WORKSPACE_DATA}" config user.email "${GIT_USER_EMAIL:-candidate@localhost}"
|
| 121 |
+
fi
|
| 122 |
+
|
| 123 |
+
exclude_file="${WORKSPACE_DATA}/.git/info/exclude"
|
| 124 |
+
touch "${exclude_file}"
|
| 125 |
+
|
| 126 |
+
if [ -f "${BOOTSTRAP_ROOT}/workspace.gitignore" ]; then
|
| 127 |
+
while IFS= read -r pattern || [ -n "${pattern}" ]; do
|
| 128 |
+
case "${pattern}" in
|
| 129 |
+
''|'#'*) continue ;;
|
| 130 |
+
esac
|
| 131 |
+
if ! grep -Fqx -- "${pattern}" "${exclude_file}"; then
|
| 132 |
+
printf '%s\n' "${pattern}" >> "${exclude_file}"
|
| 133 |
+
fi
|
| 134 |
+
done < "${BOOTSTRAP_ROOT}/workspace.gitignore"
|
| 135 |
+
fi
|
| 136 |
+
|
| 137 |
+
# Commit the supplied instructions and uv project definition as the clean
|
| 138 |
+
# baseline for a fresh repository. Existing history is never rewritten.
|
| 139 |
+
if [ "${new_repository}" -eq 1 ] \
|
| 140 |
+
|| ! git -C "${WORKSPACE_DATA}" rev-parse --verify HEAD >/dev/null 2>&1
|
| 141 |
+
then
|
| 142 |
+
git -C "${WORKSPACE_DATA}" add README.md pyproject.toml uv.lock
|
| 143 |
+
if ! git -C "${WORKSPACE_DATA}" diff --cached --quiet; then
|
| 144 |
+
git -C "${WORKSPACE_DATA}" -c commit.gpgsign=false commit \
|
| 145 |
+
-m "Initialize Python environment" >/dev/null
|
| 146 |
+
fi
|
| 147 |
+
fi
|
| 148 |
+
|
| 149 |
+
# Restore preinstalled extensions and the installer metadata they need.
|
| 150 |
if [ -d "${BOOTSTRAP_ROOT}/code-server-user-data" ] \
|
| 151 |
&& [ -z "$(find "${USER_DATA_DIR}" -mindepth 1 -maxdepth 1 -print -quit)" ]
|
| 152 |
then
|
|
|
|
| 170 |
|
| 171 |
installed_extensions="$(list_extensions)"
|
| 172 |
|
|
|
|
|
|
|
|
|
|
| 173 |
if ! has_extension 'Anthropic.claude-code' || ! has_extension 'ms-python.python'; then
|
| 174 |
+
log "Repairing required extensions"
|
| 175 |
"${CODE_SERVER_BIN}" \
|
| 176 |
--user-data-dir "${USER_DATA_DIR}" \
|
| 177 |
--extensions-dir "${EXTENSIONS_DIR}" \
|
|
|
|
| 191 |
exit 1
|
| 192 |
fi
|
| 193 |
|
| 194 |
+
patch_claude_extension_binaries() {
|
| 195 |
+
test -x "${CLAUDE_SYSTEM_BIN}"
|
| 196 |
+
|
| 197 |
+
for extension_dir in \
|
| 198 |
+
"${EXTENSIONS_DIR}"/anthropic.claude-code-* \
|
| 199 |
+
"${EXTENSIONS_DIR}"/Anthropic.claude-code-*
|
| 200 |
+
do
|
| 201 |
+
[ -d "${extension_dir}" ] || continue
|
| 202 |
+
|
| 203 |
+
for binary_name in claude claude.real; do
|
| 204 |
+
bundled_binary="${extension_dir}/resources/native-binary/${binary_name}"
|
| 205 |
+
if [ ! -e "${bundled_binary}" ] && [ ! -L "${bundled_binary}" ]; then
|
| 206 |
+
continue
|
| 207 |
+
fi
|
| 208 |
+
|
| 209 |
+
if "${bundled_binary}" --version >/dev/null 2>&1; then
|
| 210 |
+
continue
|
| 211 |
+
fi
|
| 212 |
+
|
| 213 |
+
log "Replacing incompatible Claude extension binary"
|
| 214 |
+
rm -f "${bundled_binary}"
|
| 215 |
+
ln -s "${CLAUDE_SYSTEM_BIN}" "${bundled_binary}"
|
| 216 |
+
"${bundled_binary}" --version >/dev/null
|
| 217 |
+
done
|
| 218 |
+
done
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
patch_claude_extension_binaries
|
| 222 |
+
|
| 223 |
+
# Keep editor configuration outside the candidate project. The Python
|
| 224 |
+
# environment and Git metadata stay out of Explorer and search; the visible
|
| 225 |
+
# baseline consists of README.md, pyproject.toml, and uv.lock.
|
| 226 |
+
settings_file="${USER_DATA_DIR}/User/settings.json"
|
| 227 |
+
settings_dir="$(dirname "${settings_file}")"
|
| 228 |
+
mkdir -p "${settings_dir}"
|
| 229 |
+
|
| 230 |
+
if [ ! -f "${settings_file}" ]; then
|
| 231 |
+
printf '{}\n' > "${settings_file}"
|
| 232 |
+
fi
|
| 233 |
+
|
| 234 |
+
if jq -e 'type == "object"' "${settings_file}" >/dev/null 2>&1; then
|
| 235 |
+
settings_tmp="${settings_file}.tmp.$$"
|
| 236 |
+
jq \
|
| 237 |
+
--arg wrapper "${CLAUDE_VSCODE_WRAPPER}" \
|
| 238 |
+
--arg python "${WORKSPACE_LINK}/.venv/bin/python" \
|
| 239 |
+
--arg workspace "${WORKSPACE_LINK}" \
|
| 240 |
+
'. + {
|
| 241 |
+
"claudeCode.claudeProcessWrapper": $wrapper,
|
| 242 |
+
"python.defaultInterpreterPath": $python,
|
| 243 |
+
"python.terminal.activateEnvironment": true,
|
| 244 |
+
"terminal.integrated.cwd": $workspace,
|
| 245 |
+
"workbench.startupEditor": "readme",
|
| 246 |
+
"workbench.browser.enableRemoteProxy": true,
|
| 247 |
+
"workbench.browser.dataStorage": "workspace",
|
| 248 |
+
"workbench.browser.openLocalhostLinks": true,
|
| 249 |
+
"workbench.browser.showInTitleBar": true,
|
| 250 |
+
"files.exclude": ((."files.exclude" // {}) + {
|
| 251 |
+
"**/.venv": true,
|
| 252 |
+
"**/.git": true
|
| 253 |
+
}),
|
| 254 |
+
"search.exclude": ((."search.exclude" // {}) + {
|
| 255 |
+
"**/.venv": true,
|
| 256 |
+
"**/.git": true
|
| 257 |
+
})
|
| 258 |
+
}' \
|
| 259 |
+
"${settings_file}" > "${settings_tmp}"
|
| 260 |
+
mv "${settings_tmp}" "${settings_file}"
|
| 261 |
+
else
|
| 262 |
+
log "WARNING: ${settings_file} is not plain JSON; leaving it unchanged"
|
| 263 |
+
fi
|
| 264 |
+
|
| 265 |
+
# Verify the Claude wrapper both with and without the extension-injected path.
|
| 266 |
+
test -x "${CLAUDE_VSCODE_WRAPPER}"
|
| 267 |
+
"${CLAUDE_VSCODE_WRAPPER}" --version >/dev/null
|
| 268 |
+
"${CLAUDE_VSCODE_WRAPPER}" \
|
| 269 |
+
"${EXTENSIONS_DIR}/test/resources/native-binary/claude" \
|
| 270 |
+
--version >/dev/null
|
| 271 |
+
|
| 272 |
# Persist ngrok's default Linux configuration directory.
|
| 273 |
mkdir -p "${CODER_HOME}/.config"
|
| 274 |
|
|
|
|
| 288 |
ln -s "${NGROK_DIR}" "${CODER_HOME}/.config/ngrok"
|
| 289 |
fi
|
| 290 |
|
|
|
|
| 291 |
test -d "${WORKSPACE_LINK}"
|
| 292 |
+
test -f "${WORKSPACE_LINK}/README.md"
|
| 293 |
test -f "${WORKSPACE_LINK}/pyproject.toml"
|
| 294 |
+
test -f "${WORKSPACE_LINK}/uv.lock"
|
| 295 |
+
test -x "${WORKSPACE_LINK}/.venv/bin/python"
|
| 296 |
+
git -C "${WORKSPACE_DATA}" rev-parse --is-inside-work-tree >/dev/null
|
| 297 |
|
| 298 |
+
log "Workspace ready: ${WORKSPACE_LINK}"
|
| 299 |
+
log "Candidate instructions ready: ${WORKSPACE_LINK}/README.md"
|
| 300 |
+
log "uv project ready: ${WORKSPACE_LINK}/pyproject.toml"
|
| 301 |
log "Python ready: $("${WORKSPACE_LINK}/.venv/bin/python" --version 2>&1)"
|
| 302 |
+
log "Git ready: $(git -C "${WORKSPACE_DATA}" branch --show-current 2>/dev/null || true)"
|
| 303 |
+
log "Claude CLI ready: $("${CLAUDE_SYSTEM_BIN}" --version 2>&1)"
|
| 304 |
log "Extensions ready: $(printf '%s' "${installed_extensions}" | tr '\n' ' ')"
|
scripts/claude-vscode-wrapper.sh
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
set -eu
|
| 3 |
+
|
| 4 |
+
# Claude Code's VS Code extension invokes a configured process wrapper as:
|
| 5 |
+
# wrapper <bundled-claude-path> <claude-arguments...>
|
| 6 |
+
#
|
| 7 |
+
# The Open VSX linux-x64 package can occasionally contain a native binary that
|
| 8 |
+
# is not runnable on the code-server base image. Drop the extension-injected
|
| 9 |
+
# binary path and launch the separately installed Anthropic CLI instead.
|
| 10 |
+
REAL_CLAUDE="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
|
| 11 |
+
|
| 12 |
+
if [ "$#" -gt 0 ]; then
|
| 13 |
+
case "$1" in
|
| 14 |
+
*/resources/native-binary/claude|*/resources/native-binary/claude.real)
|
| 15 |
+
shift
|
| 16 |
+
;;
|
| 17 |
+
esac
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
exec "${REAL_CLAUDE}" "$@"
|
scripts/start-code-server.sh
CHANGED
|
@@ -6,10 +6,10 @@ INIT_BIN="${INIT_BIN:-/usr/local/bin/init-persistent-data}"
|
|
| 6 |
DUMB_INIT_BIN="${DUMB_INIT_BIN:-/usr/bin/dumb-init}"
|
| 7 |
CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
|
| 8 |
|
| 9 |
-
#
|
| 10 |
eval "$("${FIXUID_BIN}" -q)"
|
| 11 |
|
| 12 |
-
#
|
| 13 |
if [ "${DOCKER_USER-}" ]; then
|
| 14 |
USER="${DOCKER_USER}"
|
| 15 |
export USER
|
|
@@ -35,15 +35,15 @@ export CODE_SERVER_EXTENSIONS="${CODE_SERVER_EXTENSIONS:-/data/code-server/exten
|
|
| 35 |
export UV_CACHE_DIR="${UV_CACHE_DIR:-/data/cache/uv}"
|
| 36 |
export HF_HOME="${HF_HOME:-/data/cache/huggingface}"
|
| 37 |
export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-/data/claude}"
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
# Run initialization directly. A broken workspace, virtual environment, or
|
| 40 |
-
# extension install now prevents code-server from starting in a partial state.
|
| 41 |
"${INIT_BIN}"
|
| 42 |
|
| 43 |
-
# This makes the code-server process and integrated terminals start from the
|
| 44 |
-
# requested workspace, in addition to passing it as the folder to open.
|
| 45 |
cd "${WORKSPACE}"
|
| 46 |
|
|
|
|
|
|
|
| 47 |
exec "${DUMB_INIT_BIN}" "${CODE_SERVER_BIN}" \
|
| 48 |
--bind-addr "0.0.0.0:${PORT}" \
|
| 49 |
--auth password \
|
|
@@ -52,4 +52,5 @@ exec "${DUMB_INIT_BIN}" "${CODE_SERVER_BIN}" \
|
|
| 52 |
--extensions-dir "${CODE_SERVER_EXTENSIONS}" \
|
| 53 |
--disable-telemetry \
|
| 54 |
--disable-update-check \
|
|
|
|
| 55 |
"${WORKSPACE}"
|
|
|
|
| 6 |
DUMB_INIT_BIN="${DUMB_INIT_BIN:-/usr/bin/dumb-init}"
|
| 7 |
CODE_SERVER_BIN="${CODE_SERVER_BIN:-/usr/bin/code-server}"
|
| 8 |
|
| 9 |
+
# Preserve the UID-repair behavior from the official image.
|
| 10 |
eval "$("${FIXUID_BIN}" -q)"
|
| 11 |
|
| 12 |
+
# Preserve the official image's optional user-renaming behavior.
|
| 13 |
if [ "${DOCKER_USER-}" ]; then
|
| 14 |
USER="${DOCKER_USER}"
|
| 15 |
export USER
|
|
|
|
| 35 |
export UV_CACHE_DIR="${UV_CACHE_DIR:-/data/cache/uv}"
|
| 36 |
export HF_HOME="${HF_HOME:-/data/cache/huggingface}"
|
| 37 |
export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-/data/claude}"
|
| 38 |
+
export CLAUDE_SYSTEM_BIN="${CLAUDE_SYSTEM_BIN:-/usr/local/bin/claude-system}"
|
| 39 |
+
export CLAUDE_VSCODE_WRAPPER="${CLAUDE_VSCODE_WRAPPER:-/usr/local/bin/claude-vscode-wrapper}"
|
| 40 |
|
|
|
|
|
|
|
| 41 |
"${INIT_BIN}"
|
| 42 |
|
|
|
|
|
|
|
| 43 |
cd "${WORKSPACE}"
|
| 44 |
|
| 45 |
+
# --ignore-last-opened makes the command-line folder authoritative instead of
|
| 46 |
+
# restoring an older /home/coder session from persistent editor state.
|
| 47 |
exec "${DUMB_INIT_BIN}" "${CODE_SERVER_BIN}" \
|
| 48 |
--bind-addr "0.0.0.0:${PORT}" \
|
| 49 |
--auth password \
|
|
|
|
| 52 |
--extensions-dir "${CODE_SERVER_EXTENSIONS}" \
|
| 53 |
--disable-telemetry \
|
| 54 |
--disable-update-check \
|
| 55 |
+
--ignore-last-opened \
|
| 56 |
"${WORKSPACE}"
|
workspace.gitignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
.pytest_cache/
|
| 5 |
+
.mypy_cache/
|
| 6 |
+
.ruff_cache/
|
| 7 |
+
.coverage
|
| 8 |
+
htmlcov/
|
| 9 |
+
.env
|
| 10 |
+
.env.*
|
| 11 |
+
.claude/
|
| 12 |
+
.claude.json
|
| 13 |
+
.DS_Store
|
workspace/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Coding Interview
|
| 2 |
+
|
| 3 |
+
Use the password supplied by your interviewer. Keep your work in the repository already open in the editor:
|
| 4 |
+
|
| 5 |
+
```text
|
| 6 |
+
/home/coder/workspace
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
## Python environment
|
| 10 |
+
|
| 11 |
+
Python 3.12 is ready, and this project uses **uv** for dependency and environment management:
|
| 12 |
+
|
| 13 |
+
- `pyproject.toml` lists the direct dependencies available for the exercise.
|
| 14 |
+
- `uv.lock` records the exact resolved dependency versions.
|
| 15 |
+
- `.venv` contains the active virtual environment.
|
| 16 |
+
|
| 17 |
+
Useful checks:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
pwd
|
| 21 |
+
python --version
|
| 22 |
+
uv --version
|
| 23 |
+
uv tree
|
| 24 |
+
git status
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Add a dependency with:
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
uv add <package>
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
After editing `pyproject.toml` manually, synchronize the environment with:
|
| 34 |
+
|
| 35 |
+
```bash
|
| 36 |
+
uv sync
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
## Run and preview a FastAPI app
|
| 40 |
+
|
| 41 |
+
Assuming the FastAPI application is exposed as `app` in `main.py`, start it in a terminal:
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
uv run uvicorn main:app --host 127.0.0.1 --port 8000 --reload
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
The first choice is to view Swagger UI inside the editor:
|
| 48 |
+
|
| 49 |
+
1. Open the Command Palette with **Ctrl/Cmd+Shift+P**.
|
| 50 |
+
2. Run **Browser: Open Integrated Browser**. In some editor versions, the command may be named **Simple Browser: Show**.
|
| 51 |
+
3. Open:
|
| 52 |
+
|
| 53 |
+
```text
|
| 54 |
+
http://127.0.0.1:8000/docs
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
ReDoc is available at:
|
| 58 |
+
|
| 59 |
+
```text
|
| 60 |
+
http://127.0.0.1:8000/redoc
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
If the integrated browser does not open the local address, open the current code-server URL in a normal browser tab and append:
|
| 64 |
+
|
| 65 |
+
```text
|
| 66 |
+
/proxy/8000/docs
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## Optional: expose the API with ngrok
|
| 70 |
+
|
| 71 |
+
Use ngrok only when a service outside the editor must reach your API, such as a webhook provider or an external API client.
|
| 72 |
+
|
| 73 |
+
Keep Uvicorn running, open a second terminal, and run:
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
ngrok http 8000
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
ngrok prints a public HTTPS URL. Append `/docs` to that URL to open Swagger UI, for example:
|
| 80 |
+
|
| 81 |
+
```text
|
| 82 |
+
https://<generated-address>.ngrok.app/docs
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
If ngrok asks for authentication and an authtoken was supplied for the interview, configure it once and retry:
|
| 86 |
+
|
| 87 |
+
```bash
|
| 88 |
+
ngrok config add-authtoken <token>
|
| 89 |
+
ngrok http 8000
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
The generated URL is publicly reachable. Do not expose credentials, environment variables, or sensitive data, and stop the tunnel with **Ctrl+C** when it is no longer needed.
|
| 93 |
+
|
| 94 |
+
## Source control and tools
|
| 95 |
+
|
| 96 |
+
The workspace is already a Git repository on the `main` branch. Make commits at useful milestones and do not commit credentials or access tokens.
|
| 97 |
+
|
| 98 |
+
Claude Code is available when the interview task calls for it.
|