Add tmux, a pinned Neovim and the workspace launcher
build / Build and push image (push) Successful in 4m51s
build / Build and push image (push) Successful in 4m51s
Removes the per-sandbox setup cost AI_SBX_TOOLS pays on every setup, and makes the sandbox a terminal environment worth working in. Neovim comes from the upstream tarball rather than apt: Ubuntu's build is far behind what a LazyVim config needs. Upstream publishes no checksums, so NEOVIM_SHA256 is taken from the release asset and verified at build time. The version is pinned and labelled because a config that works on the host and breaks in the sandbox on a version skew is expensive to diagnose. The launcher is fetched from ai-sandbox v1.11.0's tasks/ai/workspace rather than vendored here. The task installs its own copy into stock-image sandboxes and skips that when the image supplies one, so two copies could drift and make behaviour depend on which image you are on. A bash -n guard rejects a forge error page served with a 200.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# Spec: base sandbox image — `claude-sbx`
|
||||
|
||||
Implementation spec for the image. The consuming task and its configuration surfaces
|
||||
are specified in `mroberts/ai-sandbox` → `docs/tmux-workspace-spec.md`. Registry,
|
||||
workflow, and plugin-manifest work are in [`HANDOFF.md`](HANDOFF.md).
|
||||
|
||||
## Purpose
|
||||
|
||||
Remove the per-sandbox setup cost that `AI_SBX_TOOLS` currently pays on every `setup`,
|
||||
and provide a terminal environment good enough to work in rather than only to watch an
|
||||
agent in.
|
||||
|
||||
The image carries **tools and the launcher**. It carries **no dotfiles and no
|
||||
credentials** — those arrive at runtime from the host, because they are personal,
|
||||
change often, and in the dotfiles case contain secrets.
|
||||
|
||||
## What the base image already provides
|
||||
|
||||
Verified against `docker/sandbox-templates:claude-code-docker`:
|
||||
|
||||
| Present | Absent |
|
||||
| --- | --- |
|
||||
| `git`, `node`, `npm`, `docker` (29.6.1) | `tmux`, `nvim`, `vim` |
|
||||
|
||||
Also guaranteed by the Sandboxes base image contract, and must be preserved:
|
||||
|
||||
- non-root `agent` at UID 1000 with passwordless sudo
|
||||
- `/home/agent/` owned by `agent`
|
||||
- `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` preserved across sudo
|
||||
|
||||
## Contents
|
||||
|
||||
### 1. tmux
|
||||
|
||||
`apt-get install -y --no-install-recommends tmux` as `root`. Ubuntu's build is current
|
||||
enough; nothing here depends on a recent tmux.
|
||||
|
||||
### 2. Neovim
|
||||
|
||||
**Not from `apt`.** Ubuntu ships a version far behind what a LazyVim-based config
|
||||
needs. Install the upstream release tarball to `/usr/local`, or keep neovim on mise.
|
||||
|
||||
Pin the version explicitly and record it in the image labels — a config that works on
|
||||
the host and breaks in the sandbox because of a version skew is a bad afternoon.
|
||||
|
||||
### 3. The launcher
|
||||
|
||||
`/usr/local/bin/ai-sbx-workspace`, mode 755.
|
||||
|
||||
**Built from `ai-sandbox`'s `tasks/ai/workspace`, not written independently.** The task
|
||||
installs its own copy into sandboxes running a stock image; if the image's copy drifts,
|
||||
behaviour depends on which image you happen to be on. Vendor it at build time (fetch
|
||||
from the tagged `ai-sandbox` release the image is built against) and record that tag in
|
||||
a label.
|
||||
|
||||
Behaviour is specified in the task spec. In summary: attach-or-create session `ai-sbx`,
|
||||
three windows (`agent`, `edit`, `shell`), all in the workspace root, agent window
|
||||
running `claude --dangerously-skip-permissions`.
|
||||
|
||||
### 4. Optional: pre-warmed plugins
|
||||
|
||||
The full Neovim config clones 93 plugins and installs up to 55 mason packages on first
|
||||
launch. Every host that needs is already permitted by the `Balanced` network policy, so
|
||||
this is slow rather than blocked.
|
||||
|
||||
Pre-warming `~/.local/share/nvim` into the image removes that wait at the cost of a
|
||||
much larger image and staleness against `lazy-lock.json`. **Defer this.** Measure the
|
||||
first-launch cost with the plain image before deciding it is a problem worth an extra
|
||||
gigabyte.
|
||||
|
||||
### 5. Deliberately excluded
|
||||
|
||||
| Excluded | Why |
|
||||
| --- | --- |
|
||||
| Dotfiles | Personal, change often, and the image is pushed to a registry |
|
||||
| Credentials | Provisioned at runtime as proxy-substituted placeholders |
|
||||
| `~/.claude` config and plugins | The task copies these per sandbox from the host |
|
||||
| The chezmoi age key | Never leaves the host |
|
||||
|
||||
## Interaction with the task
|
||||
|
||||
The image and the task must agree on exactly two things:
|
||||
|
||||
| Contract | Owner |
|
||||
| --- | --- |
|
||||
| `/usr/local/bin/ai-sbx-workspace` exists and behaves as specified | image |
|
||||
| Skip installing the launcher when the image supplies it | task |
|
||||
|
||||
Everything else — Claude config, plugins, mise, secrets, network policy — is already
|
||||
handled at runtime and needs nothing from the image.
|
||||
|
||||
Consumed by setting, in `~/.config/mise/config.toml`:
|
||||
|
||||
```toml
|
||||
AI_SBX_TEMPLATE = "git.mroberts.dev/mroberts/claude-sbx:v1"
|
||||
```
|
||||
|
||||
Once the image carries tmux and neovim, `AI_SBX_TOOLS` can drop back to `bun`.
|
||||
|
||||
## Build and publish
|
||||
|
||||
Per `HANDOFF.md`: Gitea Actions, pushing to `git.mroberts.dev/mroberts/claude-sbx`.
|
||||
|
||||
- Tag immutably. `AI_SBX_TEMPLATE` pins one ref, and a moving tag means an unreviewed
|
||||
image runs on the developer's workstation with their credentials.
|
||||
- Confirm an Actions runner is registered before writing the workflow —
|
||||
`has_actions: true` only means the feature is enabled. Fall back to
|
||||
`docker build --push` from a workstation if there is none.
|
||||
- The sandbox pulls from the registry directly and does not share the host image
|
||||
store, so a local build is invisible to it. Use `sbx secret set --registry
|
||||
git.mroberts.dev` for pull credentials.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. `docker build` succeeds from a clean checkout with no access to `~/.claude` or
|
||||
`~/.local/share/chezmoi`.
|
||||
2. In a sandbox created with `AI_SBX_TEMPLATE` set to the pushed image:
|
||||
- `tmux -V` and `nvim --version` both report the pinned versions
|
||||
- `ai-sbx-workspace` exists and is executable
|
||||
- `id -u` is 1000 and `sudo true` succeeds
|
||||
- `printenv HTTPS_PROXY` is non-empty under `sudo -E`
|
||||
3. `mise run ai:sbx -- run` with `AI_SBX_LAUNCH=tmux` opens three windows without the
|
||||
task installing anything.
|
||||
4. No credential, transcript, or dotfile is present in any layer — checked by
|
||||
extracting layers, not by inspecting the build context.
|
||||
5. `AI_SBX_TOOLS="bun"` is sufficient; nothing regresses from dropping tmux and neovim
|
||||
out of it.
|
||||
|
||||
## Open questions
|
||||
|
||||
- **Neovim delivery.** Upstream tarball, mise, or a PPA. Tarball is the most
|
||||
predictable and the easiest to pin.
|
||||
- **Image size.** Measure before and after. The `-docker` base is already substantial;
|
||||
tmux and neovim are small, pre-warmed plugins are not.
|
||||
- **Rebuild cadence.** The image lags the host config by definition. Decide whether it
|
||||
tracks a tagged `claude-sbx` release or is rebuilt on dotfile changes — the answer
|
||||
determines whether pre-warming is even coherent.
|
||||
Reference in New Issue
Block a user