sandbase-harness
sandbaseai
Local-first, self-hosted AI agent runtime and MCP bridge with sandboxed sessions, memory, credentials, audit/replay, and a local Console.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:NIyueeE/dsh-container
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
DeepSeek Harness (dsh) as a batteries-included
container — agent, full toolchain, and a reverse proxy in one image, built from the official source tags.
English | 中文
docker compose -f examples/compose.yaml up -d
docker compose logs dsh | grep 'dsh web:'
# open http://127.0.0.1:3081/ in your local browser (the proxy bootstraps the login)
sudo mkdir -p /etc/containers/systemd
sudo cp examples/dsh.container /etc/containers/systemd/
sudo systemctl daemon-reload
sudo systemctl enable --now dsh.service
Both examples publish 127.0.0.1:3081 and mount one volume at /home/dsh — the user layer
(~/.dsh, caches, user-installed tools) survives image upgrades while the system layer comes from
the image. There is no login step: the proxy bootstraps the dsh session automatically.
| Component | Description |
|---|---|
| Base image | debian:13-slim (pinned; overridable via the BASE_IMAGE build arg) |
| Toolchain | Node.js 22 LTS, pnpm, uv, Rust/cargo (+ rustfmt/clippy), git + git-lfs, build-essential, Caddy, podman + crun, gh — image-owned real binaries, upgraded with the image |
| Agent CLI tools | ripgrep, fd, python3, zip, openssh-client, tmux, sqlite3, vim.tiny/nano, less, rsync, wget, tree, htop, tzdata, patch — baked into the system layer, so a fresh container never re-downloads them (runtime apt install would be lost on recreation) |
| Nested containers | podman in-container (rootless) with XDG_RUNTIME_DIR, subuid/subgid and the network/sysctl defaults preconfigured; the host runtime must allow unprivileged user namespaces + /dev/fuse + /dev/net/tun and must not mask the container's /proc (--security-opt unmask=ALL, Quadlet Unmask=ALL, Docker systempaths=unconfined); resource limits (--memory/--cpus) inside nested containers are not enforced (cgroup v2 constraint) — see docs/deployment.md § In-container podman |
| dsh | Built from the official source tag into /opt/deepseek-harness (DSH_TAG pinnable); no runtime auto-update |
| Exposure | Caddy reverse proxy (0.0.0.0:3081 → dsh's 127.0.0.1:3080) with optional basic auth |
| Supervisor | dsh web auto-restarts on exit; docker exec dsh dsh-restart restarts it manually |
| Remote compatibility | One container-adapt plugin (container/plugin/, mounted via dsh --patch): session-cookie bootstrap inside dsh, headless-hostile "Open config file" button hidden (describe reports no local document), upstream's __DSH_TRANSPORT__ transport-owner declaration injected into the served index (remote settings/credentials), and /container-assets/* serving the build-time-extracted images |
| Observability | OCI labels, HEALTHCHECK (curl 3080 + 3081) |
| Runtime user | uid 1000 (dsh), passwordless sudo; /home/dsh is the persisted user layer |
All container-side adaptation of upstream dsh lives in one Cordis plugin, shipped with the
image at /opt/dsh-container-plugin and mounted into the web profile via dsh --patch:
xdg-open into nothing in a container. The plugin makes settings/describe
report hasDocument: false, so the button never renders (upstream's own UI logic). Settings
persist on the mounted volume under ~/.dsh (upstream v0.1.7+ keeps them per profile in
~/.dsh/profiles/<profile>/cordis.patch.yml; the legacy settings.yaml is imported once).isLoopback from
location.hostname unless a shell declares itself the transport owner. The plugin contributes
the __DSH_TRANSPORT__ = { ownsHost: true } row to upstream's structured index-injection table
— the same declaration upstream's desktop shell and worker-preview tunnel use — so
settings/credentials work through the proxy with no change to upstream build artifacts.pnpm run build:official:
images inlined at ≥ 100 KiB are extracted to /container-assets/<content-hash> (served
immutable by the plugin, fetched only when the screen that uses them opens), the client
bundles are minified with esbuild, and the .dsh-build record is refreshed so it keeps
matching the delivered artifacts.This plugin is the single adaptation maintenance point. When upstream ships an API that makes part of it redundant, the release pipeline deletes that part automatically — the removal shows up in that release's commit list (see docs/upstream-contract.md § Simplification triggers).
dsh web listens on 127.0.0.1:3080 (upstream rejects --host 0.0.0.0); the
exposed port is 3081, published on host loopback by the examples.Host/Origin to loopback, so remote
browsers pass dsh's /api trust fence, including settings/credentials methods that are otherwise
loopback-only. Anyone who can reach 3081 gets full control: enable basic auth
(DSH_PROXY_USER/DSH_PROXY_PASSWORD, set together or the supervisor refuses to start) and keep
the port firewalled.no-store
while the content-hashed /assets/* tree is served immutable, so repeat visits are cheap.DSH_TELEMETRY_MODE=DISABLED, so the OTel
feedback uploader never sends anything unless you opt back in. Separate from it, upstream's
DeepSeek session-log contributor is on by default and attaches session-log suffixes to
DeepSeek API requests — see docs/security.md for what it sends and how to
turn it off.dsh web arguments through the container command, e.g.
["--port", "8080"] (internal port only; exposed port stays 3081).For WAN access, terminate TLS in front of 3081 (the docs include a working nginx config with
WebSocket headers and raised timeouts) — see docs/deployment.md and
docs/security.md.
| Variable | Default | Description |
|---|---|---|
DSH_PROXY_USER / DSH_PROXY_PASSWORD |
(empty) | Basic auth on the exposed proxy (recommended for any non-loopback deployment); set both or neither |
DSH_TELEMETRY_MODE |
DISABLED |
OTel feedback-upload policy; FEEDBACK_ONLY restores the upstream default (uploads on explicit feedback), DISABLED keeps the OTel path local. It does not control the DeepSeek session-log contributor (security.md) |
Everything else uses built-in defaults — dsh data at ~/.dsh, cwd $HOME, writable caches under
~/.cargo / ~/.local/share, image-owned tools in /usr/local/bin and /opt/rust. The whole
/home/dsh is the persistence boundary: mount it as one volume; image upgrades replace the
toolchain, never the data. Details in docs/build.md and
docs/deployment.md.
| Document | Contents |
|---|---|
| docs/deployment.md | Deployment & maintenance: Compose, Quadlet, remote access, offline use, FAQ |
| docs/security.md | Security notes: network exposure tradeoff, credentials, trusted workloads |
| docs/build.md | Build configuration: build args, source tag pinning, reproducible builds |
| docs/releasing.md | Release automation: upstream tag watcher, contract check, agent repair, auto-publish |
| docs/upstream-contract.md | The upstream behaviors this image depends on, and how drift is detected |
| docs/development.md | Directory structure and local development |
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: docker、docker-compose。