Files
opencode-devbox/scripts/generate-dockerhub-md.py
T
joakimp f86c4b18cf
Validate / docs-check (push) Successful in 16s
Validate / validate-base (push) Successful in 11m16s
Validate / validate-omos (push) Failing after 18m33s
Validate / validate-with-pi (push) Successful in 13m46s
Validate / validate-omos-with-pi (push) Failing after 19m52s
Rewrite DOCKER_HUB.md as a hand-maintained slim template
The previous derive-from-README mechanism (split_sections, SECTION_RULES,
TRIM_SUBSECTIONS, REPLACEMENTS) generated a 24 997 byte Hub doc with
3 byte headroom against the 25 kB Hub limit. Every README addition
forced a 'trim something else first' exercise, and the resulting copy
was awkward (terse, repetitive linkbacks injected mid-section).

Replace with a single hand-maintained HUB_TEMPLATE constant. The Hub
doc is now intentionally slim (~5.5 kB, ~78 percent headroom) and
focuses on what Hub readers actually need: elevator pitch, image
variants, quick start, what's inside, auth, persistence, and link-outs
to the gitea README for depth.

Trade-off: when image-variants or quick-start change, update
HUB_TEMPLATE here too. That coupling is now explicit and local rather
than spread across SECTION_RULES + REPLACEMENTS + TRIM machinery,
and most README edits no longer require regenerating DOCKER_HUB.md
at all.

Generator simplified from 323 lines to 199 lines (270-line net
reduction across the script + DOCKER_HUB.md). README and Hub doc are
now independent surfaces.

CHANGELOG and AGENTS updated to reflect the new coupling. Release-day
checklist tightened: README -> regenerate DOCKER_HUB ONLY if
HUB_TEMPLATE changed -> promote CHANGELOG -> grep AGENTS -> commit
-> tag.
2026-05-09 15:49:43 +02:00

212 lines
8.7 KiB
Python
Executable File

#!/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/mariozechner/pi-coding-agent) as alternative/complementary harness (shares the mempalace install with opencode) |
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together |
All variants support `linux/amd64` and `linux/arm64`.
## Quick Start
```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
```
Drops you straight into opencode with your project mounted at `/workspace`.
For an interactive shell first (useful for AWS SSO login, multi-harness workflows, or just `bash`):
```bash
docker run -it --rm \\
-e ANTHROPIC_API_KEY=your-key \\
-e OPENCODE_PROVIDER=anthropic \\
-v ~/projects:/workspace \\
-v ~/.ssh:/home/developer/.ssh:ro \\
joakimp/opencode-devbox:latest bash
```
Then run `opencode`, `pi` (on `*-with-pi` variants), or `aws sso login` from the shell.
For docker-compose users, the source repo provides `docker-compose.yml`, `.env.example`, and a one-liner `docker compose up -d` workflow with named volumes pre-wired.
## What's Inside
- **[opencode](https://opencode.ai)** — primary coding-agent harness. Multi-provider (Anthropic, OpenAI, Bedrock, Google, Groq, etc.).
- **[pi](https://github.com/mariozechner/pi-coding-agent)** *(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>
## 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())