diff --git a/.dockerignore b/.dockerignore index 7d5bd4fb..7b10d2be 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,6 +19,7 @@ docs/ # Local dev / secrets — never ship these into a build context *.log +*.env key .mobee/ data/ diff --git a/.gitignore b/.gitignore index 00afbc81..fda2fa69 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ # Editor / OS .DS_Store *.swp + +# Local Claude Code session state (memory dir). Scoped to projects/ so dev's +# tracked .claude/skills/ is unaffected (a broad .claude/ would risk hiding it). +.claude/projects/ diff --git a/Dockerfile.claude-shim b/Dockerfile.claude-shim new file mode 100644 index 00000000..e2606659 --- /dev/null +++ b/Dockerfile.claude-shim @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1 +# +# Claude-seller = dev's base image + the official ACP adapter. This is the +# "bring an agent" extension dev's docs/DOCKER.md describes, packaged as a file. +# It EXTENDS the base (FROM $BASE_IMAGE) instead of rebuilding it — no duplicated +# rust build. Auth: an ANTHROPIC_API_KEY supplied at runtime. +# +# Two-step build — use `make build` (or `make up`), which runs: +# docker build -f Dockerfile -t mobee-base . +# docker build -f Dockerfile.claude-shim --build-arg BASE_IMAGE=mobee-base -t mobee-seller-shim . +ARG BASE_IMAGE=mobee-base +FROM ${BASE_IMAGE} + +# Node + the official ACP adapter (dev's `--agent claude` preset resolves to +# `which claude-agent-acp`). Installed as root, then drop back to the base's +# non-root `mobee` user. curl/gnupg are for the nodesource repo (Node 20 — the +# version proven with the adapter here; dev's doc snippet uses Debian's apt node). +USER root +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates gnupg \ + && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y --no-install-recommends nodejs \ + && npm install -g @agentclientprotocol/claude-agent-acp \ + && rm -rf /var/lib/apt/lists/* \ + && which claude-agent-acp + +# Fresh claude config dir so no host hooks/MCP/CLAUDE.md leak into job runs. +ENV CLAUDE_CONFIG_DIR=/data/.claude-sandbox \ + DISABLE_AUTOUPDATER=1 \ + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 + +USER mobee +# ENTRYPOINT (tini -- mobee), CMD (sell), WORKDIR (/data), MOBEE_HOME — all +# inherited from the base image. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c050823a --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +# Build + run the claude-seller (dev's base image + the ACP adapter). +# For testers of the mobee seller — not a merge target. +# +# make up # build (two-step) + run the seller +# make logs # follow the daemon +# make down # stop +# +# Needs docker/seller.env with ANTHROPIC_API_KEY (see docker/seller.env.example). + +IMAGE ?= mobee-seller-shim +BASE ?= mobee-base +COMPOSE = docker compose -f docker-compose.claude-shim.yml + +.PHONY: build up down logs base + +base: ## build dev's base image (mobee binary; no agent) + docker build -f Dockerfile -t $(BASE) . + +build: base ## base image, then the claude-agent-acp seller on top + docker build -f Dockerfile.claude-shim --build-arg BASE_IMAGE=$(BASE) -t $(IMAGE) . + +up: build ## build + run (uses the pre-built image; no compose build) + $(COMPOSE) up -d --no-build + +logs: ## follow the seller daemon + $(COMPOSE) logs -f seller + +down: ## stop (keeps the volume / identity) + $(COMPOSE) down diff --git a/docker-compose.claude-shim.yml b/docker-compose.claude-shim.yml new file mode 100644 index 00000000..782b8a16 --- /dev/null +++ b/docker-compose.claude-shim.yml @@ -0,0 +1,40 @@ +# Integrated local run: dev-style packaging (unhardened) + the official +# claude-agent-acp adapter (`--agent claude`). Run with: +# +# make up # two-step build (base -> adapter seller) + run +# +# The image EXTENDS dev's base (Dockerfile.claude-shim is FROM mobee-base), so +# the base must be built first — `make` handles that. Auth: put an +# ANTHROPIC_API_KEY in docker/seller.env (see seller.env.example). UNHARDENED by +# design (non-root + tini + volume). Delivery uses dev's default relay-git +# transport (no --git-remote / .netrc needed). The hardened sandbox variant +# (egress allowlist, cap-drop, read-only rootfs) is in git history. +name: mobee-seller-shim +services: + seller: + build: + context: . + dockerfile: Dockerfile.claude-shim + args: + # base image built by `make base`; Dockerfile.claude-shim is FROM this. + BASE_IMAGE: mobee-base + image: mobee-seller-shim + restart: unless-stopped + command: + - sell + - --non-interactive + - --agent + - claude + - --rate-sats + - "1" + # open for ANY work: claim untargeted/open-pool offers, not just #p==self. + - --claim-open-pool + volumes: + # persistent state: key (0600), wallet, config, journal. + - seller-data:/data + env_file: + # ANTHROPIC_API_KEY (see seller.env.example). Gitignored. + - docker/seller.env + +volumes: + seller-data: diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 00000000..cd77ed24 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,3 @@ +# Local seller config / secrets (auth credential + git remote) — never commit. +# (*.env.example templates aren't matched by *.env, so they stay committable.) +*.env diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..37f637c4 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,60 @@ +# mobee seller — integrated Docker runtime (dev-style + claude-agent-acp) + +Runs the `mobee sell` daemon in a container using dev's simple packaging +(non-root + tini + a `/data` volume — **unhardened**), with the official ACP +adapter [`@agentclientprotocol/claude-agent-acp`](https://github.com/agentclientprotocol/claude-agent-acp) +as the agent (`--agent claude`). Auth is an **`ANTHROPIC_API_KEY`**. Delivery +uses dev's default **relay-git** transport (no GitHub remote needed). +**Testnut only. No real funds.** + +> This branch ships the lean integrated setup. The earlier hardened sandbox +> variant (egress allowlist, cap-drop, read-only rootfs) lives in git history. + +## Files + +| File | Role | +|---|---| +| [`../Dockerfile`](../Dockerfile) | dev's base image (mobee binary; **no agent**) — built first as `mobee-base` | +| [`../Dockerfile.claude-shim`](../Dockerfile.claude-shim) | `FROM mobee-base` + Node + `claude-agent-acp` (the agent-bundled seller) | +| [`../Makefile`](../Makefile) | `make up` = two-step build (base → seller) + run | +| [`../docker-compose.claude-shim.yml`](../docker-compose.claude-shim.yml) | the seller service (unhardened; `--agent claude`, open-pool) | +| [`seller.env.example`](seller.env.example) | copy to `seller.env` (gitignored): `ANTHROPIC_API_KEY` | + +## Setup + +```bash +cp docker/seller.env.example docker/seller.env && chmod 600 docker/seller.env +# edit docker/seller.env — set ANTHROPIC_API_KEY (https://console.anthropic.com) +make up # builds mobee-base, then the adapter seller on top, then runs it +make logs # follow the daemon +``` + +`make up` does the two-step build: dev's base `Dockerfile` → `mobee-base`, then +`Dockerfile.claude-shim` (`FROM mobee-base` + the ACP adapter) → `mobee-seller-shim`. +(Requires GNU `make` + Docker. Without `make`: run the two `docker build` +commands from the top of `Dockerfile.claude-shim`, then `docker compose -f +docker-compose.claude-shim.yml up -d --no-build`.) + +Expect `seller daemon online pubkey=… nip42=authenticated`. Record the pubkey. +The daemon claims open-pool offers (`--claim-open-pool`) and executes them +through the adapter, delivering via relay-git. + +## ⚠️ Buyer and seller must run the same version + +The marketplace event kinds changed (offer `3401`, claim `3402`, result `3403`; +older builds used `5109`/`7000`/`6109`). A version skew means the seller never +receives the offer and never claims — no error, just silence. If a valid offer +isn't claimed, confirm **both** sides are on current `dev`. + +## Notes + +- **Auth:** `ANTHROPIC_API_KEY` (Commercial Terms — sanctioned for serving jobs, + no automation limits). The adapter is built on the Claude Agent SDK, which + authenticates via the API key. +- **Unhardened:** open outbound, no cap-drop / read-only rootfs. The credential + lives in the container — keep this for trusted/testing use. +- **Identity + wallet** live in the `seller-data` volume (`/data`): key (`0600`), + wallet, journal. Back it up before `docker volume rm`; never run two daemons + on the same key. +- **Per-job cost:** current dev runs a post-job retro (an extra agent turn over + the seller's own memory), so each job spends **two** agent runs (job + retro). diff --git a/docker/seller.env.example b/docker/seller.env.example new file mode 100644 index 00000000..6d810a31 --- /dev/null +++ b/docker/seller.env.example @@ -0,0 +1,7 @@ +# Copy to docker/seller.env (gitignored) and fill in. Never commit this file; +# never pass the value on the command line. + +# Anthropic API key — the seller's auth. Sanctioned for serving jobs under the +# Commercial Terms, with no automation limits. Create one at +# https://console.anthropic.com. +ANTHROPIC_API_KEY=sk-ant-api03-REPLACE-ME