diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index ce5e21d..86a68a8 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -214,7 +214,10 @@ services: opencode: profiles: ["opencode"] - image: ghcr.io/anomalyco/opencode:latest + build: + context: . + dockerfile: docker/opencode/Dockerfile + image: submatter-opencode:tooling container_name: submatter-opencode restart: unless-stopped working_dir: /workspace/repo @@ -235,6 +238,10 @@ services: - OPENCODE_SERVER_PASSWORD=${OPENCODE_SERVER_PASSWORD} - OPENCODE_SERVER_USERNAME=${OPENCODE_SERVER_USERNAME:-opencode} - OMNIROUTE_API_KEY=${OMNIROUTE_API_KEY} + - FORGEJO_TOKEN=${FORGEJO_TOKEN:-} + - GITHUB_TOKEN=${GITHUB_TOKEN:-} + - GIT_USER_NAME=${GIT_USER_NAME:-opencode} + - GIT_USER_EMAIL=${GIT_USER_EMAIL:-opencode@stillwell.cloud} volumes: - echodo-workspace:/workspace - opencode-data:/data @@ -255,7 +262,8 @@ services: deploy: resources: limits: - memory: 2G + # Next.js builds need headroom beyond the bare serve binary. + memory: 3G cpus: "2" networks: - echodo diff --git a/docker/opencode/Dockerfile b/docker/opencode/Dockerfile new file mode 100644 index 0000000..7df4915 --- /dev/null +++ b/docker/opencode/Dockerfile @@ -0,0 +1,31 @@ +# OpenCode serve image + host tooling for interactive / worker agent sessions. +# Official image is Alpine and ships only the `opencode` binary. +FROM ghcr.io/anomalyco/opencode:latest + +USER root + +RUN apk add --no-cache \ + bash \ + ca-certificates \ + curl \ + git \ + github-cli \ + openssh-client \ + ripgrep \ + python3 \ + make \ + g++ \ + libc6-compat \ + nodejs \ + npm \ + && curl -fsSL -o /usr/local/bin/pnpm \ + https://github.com/pnpm/pnpm/releases/download/v9.15.9/pnpm-linuxstatic-x64 \ + && chmod +x /usr/local/bin/pnpm \ + && npm install -g tsx \ + && rm -rf /root/.npm/_cacache /tmp/* + +ENV GIT_CONFIG_COUNT=1 \ + GIT_CONFIG_KEY_0=safe.directory \ + GIT_CONFIG_VALUE_0=* + +WORKDIR /workspace diff --git a/docker/opencode/README.md b/docker/opencode/README.md index b99719e..80e2ac5 100644 --- a/docker/opencode/README.md +++ b/docker/opencode/README.md @@ -7,17 +7,37 @@ Runs `opencode serve` on `:4096` (HTTP API for the worker). Attach a TUI with is happy. The image has no desktop browser — do not use `opencode web` in compose without an `xdg-open` stub (entrypoint provides one if you switch back). +## Tooling image + +`docker/opencode/Dockerfile` extends `ghcr.io/anomalyco/opencode:latest` with: + +- `git`, `openssh-client`, `gh` +- Node.js + `npm` + `pnpm` + `tsx` +- `bash`, `curl`, `ripgrep`, `python3`, `make`, `g++` (native module builds) + +Forgejo HTTPS auth is seeded from `FORGEJO_TOKEN` into `~/.git-credentials` at +container start. Projects live under `/workspace/` on the shared volume. + +```bash +export COMPOSE_PROFILES=opencode +docker compose --project-directory /opt/submatter \ + -f docker/docker-compose.prod.yml --env-file .env \ + build opencode +bash scripts/deploy-prod.sh up +``` + ## Enable On CT 116 `/opt/submatter/.env`: ```bash COMPOSE_PROFILES=opencode -WORKER_SESSION_TYPE=opencode # after worker image rebuild with opencode session +WORKER_SESSION_TYPE=opencode OPENCODE_SERVER_PASSWORD= OPENCODE_SERVER_URL=http://opencode:4096 OMNIROUTE_BASE_URL=https://omniroute.stillwell.cloud/v1 OMNIROUTE_API_KEY= +FORGEJO_TOKEN= ``` Then: @@ -29,9 +49,6 @@ bash scripts/deploy-prod.sh up # bash scripts/deploy-prod.sh build ``` -Keep `WORKER_SESSION_TYPE=minimal` until the rebuilt worker image includes the -`opencode` session type (and the `yaml` dependency fix). - ## URLs | Surface | URL | @@ -45,7 +62,8 @@ Keep `WORKER_SESSION_TYPE=minimal` until the rebuilt worker image includes the - Browser: Authentik via Traefik (`authentik-auth`) **and** OpenCode basic auth. - Worker: same basic auth via `OPENCODE_SERVER_PASSWORD`. - Models: `@omniroute/opencode-plugin` + `OMNIROUTE_API_KEY` (seeded into `auth.json` by `entrypoint.sh`). +- Git: `FORGEJO_TOKEN` → HTTPS credential helper for `forgejo.stillwell.cloud`. ## RAM -Container capped at 2 GB. Keep one concurrent agent until host RAM → 64 GB. +Container capped at 3 GB (Next.js builds). Keep one concurrent agent until host RAM → 64 GB. diff --git a/docker/opencode/entrypoint.sh b/docker/opencode/entrypoint.sh index ad6aee3..c5ee93b 100644 --- a/docker/opencode/entrypoint.sh +++ b/docker/opencode/entrypoint.sh @@ -4,7 +4,7 @@ set -eu CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME:-/data}/.config}" DATA_HOME="${XDG_DATA_HOME:-${HOME:-/data}/.local/share}" -mkdir -p "$CONFIG_HOME/opencode" "$DATA_HOME/opencode" /data/bin +mkdir -p "$CONFIG_HOME/opencode" "$DATA_HOME/opencode" /data/bin "$HOME" # Container has no desktop — stub xdg-open so `opencode web` does not exit. if [ ! -x /data/bin/xdg-open ]; then @@ -24,4 +24,26 @@ if [ -n "${OMNIROUTE_API_KEY:-}" ]; then chmod 600 "$DATA_HOME/opencode/auth.json" || true fi +# Git identity + Forgejo HTTPS auth for clone/push from agent sessions. +if command -v git >/dev/null 2>&1; then + git config --global --add safe.directory '*' + if [ -n "${GIT_AUTHOR_NAME:-}" ]; then + git config --global user.name "$GIT_AUTHOR_NAME" + else + git config --global user.name "${GIT_USER_NAME:-opencode}" + fi + if [ -n "${GIT_AUTHOR_EMAIL:-}" ]; then + git config --global user.email "$GIT_AUTHOR_EMAIL" + else + git config --global user.email "${GIT_USER_EMAIL:-opencode@stillwell.cloud}" + fi + if [ -n "${FORGEJO_TOKEN:-}" ]; then + git config --global credential.helper store + # oauth2: is Forgejo's HTTPS token username convention. + printf 'https://oauth2:%s@forgejo.stillwell.cloud\n' "$FORGEJO_TOKEN" \ + > "$HOME/.git-credentials" + chmod 600 "$HOME/.git-credentials" + fi +fi + exec "$@"