#!/usr/bin/env python3 """ Generate DOCKER_HUB.md. Rationale --------- DOCKER_HUB.md is the public-facing description shown on Docker Hub. It has two hard constraints the README does not: 1. A 25 kB byte limit on the full_description field. 2. A different audience: Hub readers want a 30-second evaluation — "what is this, how do I run it, does it have what I need" — and reference material is better consulted in context on gitea. For a long time this script tried to derive DOCKER_HUB.md from README.md by section selection + targeted replacement. As the README grew that approach pushed against the 25 kB ceiling on every change, costing a trim-something-else exercise per edit (final state: 3 byte headroom). The new approach is much simpler: a hand-written HUB_TEMPLATE below. The template intentionally stays slim and links out to the gitea README for everything that benefits from depth. README.md grows freely. Trade-off: when image-variants table or quick-start flow changes, update HUB_TEMPLATE here too. That coupling is now explicit and local rather than spread across SECTION_RULES + REPLACEMENTS + TRIM machinery. Usage ----- Regenerate in place: python3 scripts/generate-dockerhub-md.py Fail if DOCKER_HUB.md is out of sync with what this script would emit (run this in CI): python3 scripts/generate-dockerhub-md.py --check """ from __future__ import annotations import argparse import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent DOCKER_HUB = REPO_ROOT / "DOCKER_HUB.md" # Max size for Docker Hub full_description (bytes, UTF-8). MAX_SIZE_BYTES = 25_000 # Where readers go for the full reference. GITEA = "https://gitea.jordbo.se/joakimp/opencode-devbox" HUB_TEMPLATE = f"""# opencode-devbox Portable AI developer environment for [opencode](https://opencode.ai). Debian-based, with git, SSH, Node.js, AWS CLI v2, and common dev tools pre-installed. Designed for teams who want a reproducible coding-agent setup that runs the same on every laptop and CI runner — without forcing each developer to install Bun, Node, AWS CLI, mempalace, or maintain shell config drift across machines. ## Image Variants | Tag | Description | |---|---| | `latest` / `vX.Y.Z` | Base image — opencode, Node.js, AWS CLI, dev tools | | `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun | | `latest-with-pi` / `vX.Y.Z-with-pi` | Base + [pi](https://github.com/earendil-works/pi) as alternative/complementary harness (shares the mempalace install with opencode) | | `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together | | `latest-pi-only` / `vX.Y.Z-pi-only` | pi without opencode — the lean, pi-focused variant (basis of the separate `joakimp/pi-devbox` image) | All variants support `linux/amd64` and `linux/arm64`. ## Quick Start For a fully-configured environment with persistent state (opencode config, mempalace memory, neovim plugins, bash history) surviving container recreation, use docker-compose. **You don't need to clone the repo** — just grab two template files: ```bash mkdir -p ~/opencode-devbox && cd ~/opencode-devbox curl -O https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/docker-compose.yml curl -fsSL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/.env.example -o .env # Edit .env — set OPENCODE_PROVIDER, the matching API key, # WORKSPACE_PATH, GIT_USER_NAME, GIT_USER_EMAIL. docker compose run --rm devbox ``` This drops you straight into opencode with your project mounted at `/workspace`. Use `bash` as the command (e.g. `docker compose run --rm devbox bash`) to land in a shell first — useful for `aws sso login`, `pi` (on `*-with-pi` variants), or multi-harness workflows. **One-shot run, no persistence:** ```bash docker run -it --rm \\ -e ANTHROPIC_API_KEY=your-key \\ -e OPENCODE_PROVIDER=anthropic \\ -e GIT_USER_NAME="Your Name" \\ -e GIT_USER_EMAIL="you@example.com" \\ -v ~/projects:/workspace \\ -v ~/.ssh:/home/developer/.ssh:ro \\ joakimp/opencode-devbox:latest ``` Full setup guide — authentication for each provider (Anthropic, OpenAI, Bedrock SSO + static), persistence model, build args, troubleshooting: <{GITEA}#readme> ## What's Inside - **[opencode](https://opencode.ai)** — primary coding-agent harness. Multi-provider (Anthropic, OpenAI, Bedrock, Google, Groq, etc.). - **[pi](https://github.com/earendil-works/pi)** *(in `*-with-pi` variants)* — lightweight TUI coding-agent that coexists with opencode and shares the same mempalace install. Includes the `mcp-loader` extension so any local-stdio or remote streamable-HTTP MCP server (searxng, gitea, context7, …) can be added by editing `~/.pi/agent/settings.json`. - **[mempalace](https://github.com/MemPalace/mempalace)** — persistent AI memory layer (ChromaDB + SQLite). Wing/diary/knowledge-graph entries are mutually visible to opencode and pi. - **[oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim)** *(in `*-omos` variants)* — multi-agent orchestration on top of opencode (council, fallback chains, named agents). - **AWS CLI v2** with SSO support, **Node.js LTS**, **Bun** (OMOS variants), **uv** (Python), **gosu** for clean UID/GID adjustment to match your host workspace. - **MCP wrappers** for mempalace pre-installed and pre-wired to both harnesses. ## Authentication The container reads provider credentials from environment variables and host-mounted config: - **Anthropic / OpenAI / Groq / others:** set `OPENCODE_PROVIDER` and the corresponding `*_API_KEY` via `-e` or `.env`. - **AWS Bedrock (SSO):** mount `~/.aws` from the host, `OPENCODE_PROVIDER=amazon-bedrock`, then `aws sso login` inside the container. Tokens persist across container restarts via the host bind-mount. - **OAuth / device-code providers:** auth state lives in opencode's config, which is persisted via the `devbox-opencode-config` named volume. Full Bedrock walkthrough (IAM roles, permissions, multi-account setups): see the [AWS Bedrock Authentication]( {GITEA}#aws-bedrock-authentication ) section on gitea. ## Persistence | Volume | Mount | Survives | |---|---|---| | `devbox-opencode-config` | `~/.config/opencode` | container recreate, image rebuild | | `devbox-pi-config` | `~/.pi` | container recreate, image rebuild — incl. user-installed pi packages via `pi install` (`NPM_CONFIG_PREFIX` points into the volume) | | `devbox-palace` (uncomment) | `~/.mempalace` | container recreate, image rebuild — palace data is precious, treat as primary storage | | `devbox-chroma-cache` | `~/.cache/chroma` | container recreate (model cache, disposable — re-downloads in seconds) | Workspace bind-mount (`/workspace`) is your project directory on the host, so source code is never inside the container. Full persistence reference, including multi-user (`SIGNUM`) isolation and host bind-mount alternatives: see the [README on gitea]({GITEA}#persistence). ## Where to Go Next - **Full README** with build args, every feature in detail, troubleshooting: <{GITEA}> - **CHANGELOG** for version history: <{GITEA}/src/branch/main/CHANGELOG.md> - **Issues / source / docker-compose templates:** <{GITEA}> - **Agent-facing internals** (for future maintainers / coding agents working in the repo): <{GITEA}/src/branch/main/AGENTS.md> ## Sibling images - **[`joakimp/pi-devbox`](https://hub.docker.com/r/joakimp/pi-devbox)** — pi-only image built on top of this image's base layer. Smaller (~700 MB) and version-tracks the [pi npm package](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) directly. Use this if you want pi without opencode. Source: ## License MIT. See <{GITEA}/src/branch/main/LICENSE>. --- > This description is generated by `scripts/generate-dockerhub-md.py` from a hand-maintained template. Edit the template (not this file) and regenerate. """ def generate() -> str: return HUB_TEMPLATE def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--check", action="store_true", help="Fail if DOCKER_HUB.md differs from generated content.", ) args = parser.parse_args() content = generate() size = len(content.encode("utf-8")) if size > MAX_SIZE_BYTES: print( f"ERROR: generated DOCKER_HUB.md is {size} bytes, exceeding the " f"Docker Hub limit of {MAX_SIZE_BYTES} bytes.", file=sys.stderr, ) return 1 if args.check: existing = DOCKER_HUB.read_text(encoding="utf-8") if DOCKER_HUB.exists() else "" if existing != content: print( "ERROR: DOCKER_HUB.md is out of sync with the template.\n" "Run: python3 scripts/generate-dockerhub-md.py", file=sys.stderr, ) import difflib diff = difflib.unified_diff( existing.splitlines(keepends=True), content.splitlines(keepends=True), fromfile="DOCKER_HUB.md (committed)", tofile="DOCKER_HUB.md (generated)", n=2, ) sys.stderr.writelines(list(diff)[:80]) return 1 print( f"OK: DOCKER_HUB.md is in sync with HUB_TEMPLATE " f"({size} bytes, {MAX_SIZE_BYTES} limit, " f"{MAX_SIZE_BYTES - size} bytes headroom).", ) return 0 DOCKER_HUB.write_text(content, encoding="utf-8") print( f"Wrote {DOCKER_HUB} ({size} bytes, {MAX_SIZE_BYTES} limit, " f"{MAX_SIZE_BYTES - size} bytes headroom).", ) return 0 if __name__ == "__main__": raise SystemExit(main())