Spaces:
Sleeping
Let the agent manage Hugging Face repos when HF_TOKEN is set
Browse filesInstalls the official hf CLI (in a venv, since Debian's Python is
externally-managed) and, at boot, drops agents/hf-spaces.md in as opencode's
global AGENTS.md — the config-dir path applies to every session regardless of
which directory is open, so the guidance does not depend on a workspace file
that an ephemeral container would lose. With no HF_TOKEN present the file is
removed instead, so the instructions never advertise access the container
does not have.
The doc leads with "never print the token" because session transcripts are
shareable, and it draws the line at creating repos: deleting, renaming or
changing visibility needs the user's confirmation first. Image grows ~134MB
for python3 plus the CLI.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Dockerfile +14 -0
- README.md +23 -0
- agents/hf-spaces.md +83 -0
- entrypoint.sh +20 -0
|
@@ -12,11 +12,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 12 |
jq \
|
| 13 |
less \
|
| 14 |
openssh-client \
|
|
|
|
|
|
|
| 15 |
ripgrep \
|
| 16 |
tar \
|
| 17 |
unzip \
|
| 18 |
&& rm -rf /var/lib/apt/lists/*
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
# Install opencode at BUILD time so cold starts don't re-download the release.
|
| 21 |
# The official installer hardcodes $HOME/.opencode/bin (it ignores
|
| 22 |
# OPENCODE_INSTALL_DIR), so move the binary onto the shared PATH afterwards.
|
|
@@ -34,6 +47,7 @@ ENV HOME=/home/node
|
|
| 34 |
ENV PATH=/usr/local/bin:${PATH}
|
| 35 |
|
| 36 |
COPY --chown=node:node entrypoint.sh /home/node/entrypoint.sh
|
|
|
|
| 37 |
RUN chmod +x /home/node/entrypoint.sh \
|
| 38 |
&& mkdir -p /home/node/workspace \
|
| 39 |
&& chown -R node:node /home/node
|
|
|
|
| 12 |
jq \
|
| 13 |
less \
|
| 14 |
openssh-client \
|
| 15 |
+
python3 \
|
| 16 |
+
python3-venv \
|
| 17 |
ripgrep \
|
| 18 |
tar \
|
| 19 |
unzip \
|
| 20 |
&& rm -rf /var/lib/apt/lists/*
|
| 21 |
|
| 22 |
+
# Official Hugging Face CLI, so an agent holding HF_TOKEN can create and push
|
| 23 |
+
# repos in one step instead of hand-rolling API calls. Debian marks its Python
|
| 24 |
+
# externally-managed (PEP 668), hence the venv rather than a bare pip install.
|
| 25 |
+
RUN python3 -m venv /opt/hf-cli \
|
| 26 |
+
&& /opt/hf-cli/bin/pip install --no-cache-dir --upgrade pip \
|
| 27 |
+
&& /opt/hf-cli/bin/pip install --no-cache-dir "huggingface_hub[cli]" \
|
| 28 |
+
&& for bin in hf huggingface-cli; do \
|
| 29 |
+
[ -x "/opt/hf-cli/bin/$bin" ] && ln -sf "/opt/hf-cli/bin/$bin" "/usr/local/bin/$bin"; \
|
| 30 |
+
done \
|
| 31 |
+
&& hf version
|
| 32 |
+
|
| 33 |
# Install opencode at BUILD time so cold starts don't re-download the release.
|
| 34 |
# The official installer hardcodes $HOME/.opencode/bin (it ignores
|
| 35 |
# OPENCODE_INSTALL_DIR), so move the binary onto the shared PATH afterwards.
|
|
|
|
| 47 |
ENV PATH=/usr/local/bin:${PATH}
|
| 48 |
|
| 49 |
COPY --chown=node:node entrypoint.sh /home/node/entrypoint.sh
|
| 50 |
+
COPY --chown=node:node agents/hf-spaces.md /home/node/agents/hf-spaces.md
|
| 51 |
RUN chmod +x /home/node/entrypoint.sh \
|
| 52 |
&& mkdir -p /home/node/workspace \
|
| 53 |
&& chown -R node:node /home/node
|
|
@@ -104,6 +104,28 @@ your own domain needs `OPENCODE_CORS_ORIGINS=https://your.domain`.
|
|
| 104 |
|
| 105 |
---
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
## Read this before you deploy
|
| 108 |
|
| 109 |
- **This is a remote shell.** Every route allows command execution and file
|
|
@@ -131,6 +153,7 @@ your own domain needs `OPENCODE_CORS_ORIGINS=https://your.domain`.
|
|
| 131 |
| `OPENCODE_STATE_ROOT` | `/data` | Where to look for a writable volume. |
|
| 132 |
| `OPENCODE_WORKSPACE` | `$STATE_ROOT/workspace` or `$HOME/workspace` | Directory to serve. |
|
| 133 |
| `OPENCODE_CORS_ORIGINS` | — | Comma-separated extra CORS origins. |
|
|
|
|
| 134 |
|
| 135 |
Build arg `OPENCODE_VERSION` pins a release (default: latest).
|
| 136 |
|
|
|
|
| 104 |
|
| 105 |
---
|
| 106 |
|
| 107 |
+
## Giving the agent Hugging Face access
|
| 108 |
+
|
| 109 |
+
Set `HF_TOKEN` as a Space secret (or Railway variable) and the agent can create
|
| 110 |
+
and manage Hugging Face repos: the official `hf` CLI is installed in the image,
|
| 111 |
+
it picks the token up from the environment with no login step, and `entrypoint.sh`
|
| 112 |
+
installs [`agents/hf-spaces.md`](agents/hf-spaces.md) as opencode's global
|
| 113 |
+
`AGENTS.md` so the agent knows the recipes. With no `HF_TOKEN` set, that file is
|
| 114 |
+
removed and nothing advertises the capability.
|
| 115 |
+
|
| 116 |
+
> **This hands your HF account to whoever can reach the server.** A standard HF
|
| 117 |
+
> write token covers every repo the account can write to — including org repos —
|
| 118 |
+
> so the blast radius is much larger than "a shell in a container". Two things
|
| 119 |
+
> make it meaningfully safer:
|
| 120 |
+
>
|
| 121 |
+
> - Use a **fine-grained token** scoped to just the permissions you need (write
|
| 122 |
+
> access to Spaces, say) rather than an account-wide write token.
|
| 123 |
+
> - Remember the agent reads untrusted content — repos, web pages, issue text.
|
| 124 |
+
> Anything it reads can try to talk it into using the token. The instructions
|
| 125 |
+
> tell it never to print the token, but that is a speed bump, not a boundary.
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
## Read this before you deploy
|
| 130 |
|
| 131 |
- **This is a remote shell.** Every route allows command execution and file
|
|
|
|
| 153 |
| `OPENCODE_STATE_ROOT` | `/data` | Where to look for a writable volume. |
|
| 154 |
| `OPENCODE_WORKSPACE` | `$STATE_ROOT/workspace` or `$HOME/workspace` | Directory to serve. |
|
| 155 |
| `OPENCODE_CORS_ORIGINS` | — | Comma-separated extra CORS origins. |
|
| 156 |
+
| `HF_TOKEN` | — | Optional. A Hugging Face token, which lets the agent create and manage HF repos with the bundled `hf` CLI. See the warning below. |
|
| 157 |
|
| 158 |
Build arg `OPENCODE_VERSION` pins a release (default: latest).
|
| 159 |
|
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hugging Face access
|
| 2 |
+
|
| 3 |
+
`HF_TOKEN` is present in this container's environment and the official `hf` CLI
|
| 4 |
+
is installed, so you can create and manage Hugging Face repos directly.
|
| 5 |
+
|
| 6 |
+
**Never print, echo, cat or otherwise reveal the value of `HF_TOKEN`.** Pass it
|
| 7 |
+
by variable reference only. Session transcripts can be shared, and this token
|
| 8 |
+
carries write access to the whole account.
|
| 9 |
+
|
| 10 |
+
## Who am I
|
| 11 |
+
|
| 12 |
+
```bash
|
| 13 |
+
hf auth whoami
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
The CLI reads `HF_TOKEN` from the environment; there is no login step.
|
| 17 |
+
|
| 18 |
+
## Create a Space
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
hf repo create my-space --repo-type space --space_sdk docker
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
Then populate it. A Docker Space needs, at minimum, a `README.md` whose YAML
|
| 25 |
+
front matter declares the SDK and the port your app listens on, plus a
|
| 26 |
+
`Dockerfile`:
|
| 27 |
+
|
| 28 |
+
```markdown
|
| 29 |
+
---
|
| 30 |
+
title: My Space
|
| 31 |
+
emoji: 🚀
|
| 32 |
+
colorFrom: gray
|
| 33 |
+
colorTo: indigo
|
| 34 |
+
sdk: docker
|
| 35 |
+
app_port: 7860
|
| 36 |
+
---
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
`app_port` must match what the container actually listens on. Hugging Face
|
| 40 |
+
exposes exactly one port and routes it through HTTPS on 443 — a Space cannot
|
| 41 |
+
serve a second port, and clients must not append a port to the `*.hf.space`
|
| 42 |
+
URL.
|
| 43 |
+
|
| 44 |
+
Upload files with the CLI:
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
hf upload <user>/<space> ./local-dir . --repo-type space
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
…or use git, keeping the token out of the stored remote:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
git clone "https://huggingface.co/spaces/<user>/<space>" && cd <space>
|
| 54 |
+
# commit files, then:
|
| 55 |
+
git push "https://$(hf auth whoami | head -1):${HF_TOKEN}@huggingface.co/spaces/<user>/<space>" HEAD:main
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## Set Space secrets
|
| 59 |
+
|
| 60 |
+
Secrets are the only correct place for credentials — never commit them.
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
curl -sf -X POST "https://huggingface.co/api/spaces/<user>/<space>/secrets" \
|
| 64 |
+
-H "Authorization: Bearer $HF_TOKEN" -H "Content-Type: application/json" \
|
| 65 |
+
-d '{"key":"SOME_KEY","value":"...","description":"what it is for"}'
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
## Check whether a Space is up
|
| 69 |
+
|
| 70 |
+
```bash
|
| 71 |
+
curl -s "https://huggingface.co/api/spaces/<user>/<space>" | jq '.runtime.stage'
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
`RUNNING` is healthy. `BUILD_ERROR` / `RUNTIME_ERROR` / `CONFIG_ERROR` mean it
|
| 75 |
+
failed — read the build logs on the Space page. Free `cpu-basic` Spaces are
|
| 76 |
+
ephemeral and are stopped after roughly 48h idle, so anything that must survive
|
| 77 |
+
belongs in git or on a persistent-storage mount at `/data`.
|
| 78 |
+
|
| 79 |
+
## Ask before destroying things
|
| 80 |
+
|
| 81 |
+
Creating repos is fine. Deleting or renaming an existing repo, changing its
|
| 82 |
+
visibility, or overwriting someone else's files is not reversible — confirm with
|
| 83 |
+
the user first.
|
|
@@ -61,6 +61,26 @@ cd "$WORKSPACE"
|
|
| 61 |
# which makes git refuse to touch the repo. Trust what we already control.
|
| 62 |
git config --global --add safe.directory '*' 2>/dev/null || true
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
# --- extra CORS origins ----------------------------------------------------
|
| 65 |
# localhost, *.opencode.ai and the desktop app's oc://renderer are allowed out
|
| 66 |
# of the box (packages/server/src/cors.ts). Only a frontend you host yourself
|
|
|
|
| 61 |
# which makes git refuse to touch the repo. Trust what we already control.
|
| 62 |
git config --global --add safe.directory '*' 2>/dev/null || true
|
| 63 |
|
| 64 |
+
# --- agent instructions ----------------------------------------------------
|
| 65 |
+
# opencode reads a global AGENTS.md from its config dir (global.config/AGENTS.md),
|
| 66 |
+
# which applies to every session regardless of which directory is open. Install
|
| 67 |
+
# the Hugging Face guidance there when a token is actually present, so the
|
| 68 |
+
# instructions never advertise a capability the container does not have.
|
| 69 |
+
CONFIG_DIR="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}"
|
| 70 |
+
if [ -n "${HF_TOKEN:-}" ] && [ -f "$HOME/agents/hf-spaces.md" ]; then
|
| 71 |
+
mkdir -p "$CONFIG_DIR"
|
| 72 |
+
{
|
| 73 |
+
echo "<!-- Generated by entrypoint.sh on boot. Edit agents/hf-spaces.md in the"
|
| 74 |
+
echo " opencode-cloud repo instead; changes here are overwritten. -->"
|
| 75 |
+
echo
|
| 76 |
+
cat "$HOME/agents/hf-spaces.md"
|
| 77 |
+
} > "$CONFIG_DIR/AGENTS.md"
|
| 78 |
+
echo "hugging face: HF_TOKEN present, \`hf\` CLI available, agent instructions installed"
|
| 79 |
+
else
|
| 80 |
+
rm -f "$CONFIG_DIR/AGENTS.md" 2>/dev/null || true
|
| 81 |
+
echo "hugging face: no HF_TOKEN set — agent has no Hugging Face access"
|
| 82 |
+
fi
|
| 83 |
+
|
| 84 |
# --- extra CORS origins ----------------------------------------------------
|
| 85 |
# localhost, *.opencode.ai and the desktop app's oc://renderer are allowed out
|
| 86 |
# of the box (packages/server/src/cors.ts). Only a frontend you host yourself
|