diff --git a/AGENTS.md b/AGENTS.md index 518288b..dc5a481 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project overview -This repository builds and publishes a Docker image for [OpenCode](https://opencode.ai), the open source AI coding agent. The image runs OpenCode in headless server mode (`opencode serve`) and is automatically rebuilt and pushed to Docker Hub (`jcabillot/opencode`) every night by a Gitea Actions pipeline. +This repository builds and publishes a Docker image for [OpenCode](https://opencode.ai), the open source AI coding agent. The image ships OpenCode 2, runs it in headless server mode (`opencode serve`), and is automatically rebuilt and pushed to Docker Hub (`jcabillot/opencode`) every night by a Gitea Actions pipeline. ## Repository structure @@ -17,18 +17,25 @@ This repository builds and publishes a Docker image for [OpenCode](https://openc ## Dependency management -- **Always pin versions** in the Dockerfile `npm install` command (e.g. `opencode-ai@1.16.2 n2-soul@9.0.9`). Never leave packages unpinned. +- **Always pin versions** in the Dockerfile `npm install` command (e.g. `@opencode/cli@2.0.32 n2-soul@9.0.9`). Never leave packages unpinned. - **Update renovate customManagers** when adding, removing, or renaming a dependency tracked in the Dockerfile. Each pinned package must have a corresponding `customManager` entry in `renovate.json` with a regex `matchStrings` pattern that captures the version. If a dependency is added without a renovate entry, Renovatebot will not open automated PRs for it. - **apt packages** (apt-get install lines in Dockerfile) and **COPY --from** image references are not currently tracked by Renovate. Pinning these manually is acceptable for now but adding renovate managers for them is encouraged. ## Dockerfile conventions -- **Base image**: `node:24` — Debian-based Node.js image (not Alpine, needed for apt packages). -- **Install**: `npm i -g opencode-ai@ n2-soul@` — installs OpenCode and Soul globally, both pinned. +- **Base image**: `node:26-trixie` — Debian-based Node.js image (not Alpine) so that downstream images can still use `apt-get` and Node-based MCP servers keep working. +- **Install**: `npm i -g @opencode/cli@ n2-soul@` — installs OpenCode 2 and Soul globally, both pinned. `@opencode/cli` replaced `opencode-ai` as the published package in OpenCode 2; `--allow-scripts=@opencode/cli` is required for its `postinstall` to select the native binary. - **Version check**: `RUN opencode --version` after install to validate the build and record the installed version in build logs. - **Dedicated user**: a non-root `opencode` user and group are created with `groupadd`/`useradd` (UID/GID 1000). All runtime steps run as this user. - **Cluster tooling**: `kubectl` is copied from the official `registry.k8s.io/kubectl` image (multi-stage COPY). -- **Entrypoint**: `["opencode"]` — arguments are passed at runtime (e.g. `serve`). +- **Entrypoint**: `["opencode"]` — arguments are passed at runtime; `serve` starts the headless server. Bare `opencode` opens the TUI, so the server is always invoked explicitly. + +## OpenCode 2 notes + +- The npm package is `@opencode/cli` (bins `opencode` and `opencode2`); `opencode-ai` stops at 1.19.x and must not come back. +- `opencode attach` no longer exists: point a client at a server with `opencode --server ` (after the subcommand when one is given). `opencode-attach` wraps this. +- The server always authenticates: username is fixed to `opencode`, password comes from `OPENCODE_SERVER_PASSWORD` or `OPENCODE_PASSWORD`, and a random one is generated and logged when neither is set. +- The v1 REST API was replaced (`/global/health` is gone, every route now lives under `/api/*` and requires auth). Any client calling the v1 API — e.g. `perso/opencode-dispatch` — must be migrated separately. ## CI/CD conventions (Gitea Actions) @@ -44,17 +51,17 @@ This repository builds and publishes a Docker image for [OpenCode](https://openc docker build -t opencode . # Run the server on all interfaces -docker run -it -p 4096:4096 opencode --hostname 0.0.0.0 +docker run -it -p 4096:4096 opencode serve --hostname 0.0.0.0 # Run with a project mounted docker run -it -p 4096:4096 \ -v $(pwd):/home/opencode/project \ - opencode --hostname 0.0.0.0 + opencode serve --hostname 0.0.0.0 # Protect with a password docker run -it -p 4096:4096 \ -e OPENCODE_SERVER_PASSWORD=secret \ - opencode --hostname 0.0.0.0 + opencode serve --hostname 0.0.0.0 ``` ## opencode serve options @@ -63,12 +70,15 @@ docker run -it -p 4096:4096 \ |------|---------|-------------| | `--port` | `4096` | Port to listen on | | `--hostname` | `127.0.0.1` | Hostname to bind | -| `--mdns` | `false` | Enable mDNS discovery | -| `--cors` | `[]` | Additional allowed browser origins | +| `--cors` | `[]` | Additional allowed browser origins (repeatable) | +| `--service` | `false` | Run as the shared background service | +| `--stdio` | `false` | Serve over stdio for editor integrations | ## Environment variables | Variable | Description | |----------|-------------| -| `OPENCODE_SERVER_PASSWORD` | Enables HTTP basic auth | -| `OPENCODE_SERVER_USERNAME` | Overrides the default username (`opencode`) | +| `OPENCODE_SERVER_PASSWORD` | Server password (HTTP basic auth, username `opencode`) | +| `OPENCODE_PASSWORD` | Same, takes precedence over `OPENCODE_SERVER_PASSWORD` | + +`OPENCODE_SERVER_USERNAME` existed in OpenCode 1 and is no longer read: the username is always `opencode`. diff --git a/Dockerfile b/Dockerfile index 14e6a46..5f76ec5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get update && \ npm install -g --no-fund --no-audit --ignore-scripts n2-soul@9.0.9 && \ npm install -g --no-fund --no-audit --allow-scripts=better-sqlite3 better-sqlite3@12.11.1 && \ rm -rf /usr/local/lib/node_modules/n2-soul/node_modules/better-sqlite3 && \ - npm install -g --no-fund --no-audit --allow-scripts=opencode-ai opencode-ai@1.19.0 && \ + npm install -g --no-fund --no-audit --allow-scripts=@opencode/cli @opencode/cli@2.0.32 && \ npm cache clean --force && \ chown -R 1000:1000 /usr/local/lib/node_modules/n2-soul/ diff --git a/README.md b/README.md index a226277..6c86a1d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Docker image for running [OpenCode](https://opencode.ai) as a headless HTTP serv ## What is OpenCode? -OpenCode is an open source AI coding agent available as a terminal interface, desktop app, or IDE extension. This image runs it in server mode (`opencode serve`), exposing an HTTP API that clients can connect to. +OpenCode is an open source AI coding agent available as a terminal interface, desktop app, or IDE extension. This image ships OpenCode 2 and runs it in server mode (`opencode serve`), exposing an HTTP API and web UI that clients can connect to. ## Pull @@ -12,7 +12,7 @@ OpenCode is an open source AI coding agent available as a terminal interface, de docker pull jcabillot/opencode ``` -The image is rebuilt and pushed to Docker Hub automatically every night via the Jenkins pipeline. +The image is rebuilt and pushed to Docker Hub automatically every night by the Gitea Actions pipeline. ## Build locally @@ -22,14 +22,16 @@ docker build -t opencode . ## Run +The entrypoint is the `opencode` binary; pass `serve` to start the headless server: + ```bash -docker run -it -p 4096:4096 jcabillot/opencode +docker run -it -p 4096:4096 jcabillot/opencode serve ``` -By default the server listens on `127.0.0.1:4096`. To expose it on all interfaces: +The server listens on `127.0.0.1:4096` by default. To expose it on all interfaces: ```bash -docker run -it -p 4096:4096 jcabillot/opencode --hostname 0.0.0.0 +docker run -it -p 4096:4096 jcabillot/opencode serve --hostname 0.0.0.0 ``` ### Mount a project @@ -37,7 +39,7 @@ docker run -it -p 4096:4096 jcabillot/opencode --hostname 0.0.0.0 ```bash docker run -it -p 4096:4096 \ -v $(pwd):/home/opencode/project \ - jcabillot/opencode --hostname 0.0.0.0 + jcabillot/opencode serve --hostname 0.0.0.0 ``` ### Secure with a password @@ -45,17 +47,17 @@ docker run -it -p 4096:4096 \ ```bash docker run -it -p 4096:4096 \ -e OPENCODE_SERVER_PASSWORD=your-password \ - jcabillot/opencode --hostname 0.0.0.0 + jcabillot/opencode serve --hostname 0.0.0.0 ``` -Authentication uses HTTP basic auth. The default username is `opencode`, override it with `OPENCODE_SERVER_USERNAME`. +Authentication uses HTTP basic auth. The username is always `opencode`; the password is read from `OPENCODE_SERVER_PASSWORD` (or `OPENCODE_PASSWORD`). When neither is set, OpenCode 2 generates a random password and prints it to the log at startup — the server is never left unauthenticated. ## Troubleshooting helper The image includes an `opencode-attach` wrapper in `PATH` that runs: ```bash -opencode attach -p "${OPENCODE_SERVER_PASSWORD}" "${OPENCODE_API_URL}" +opencode --server "${OPENCODE_API_URL}" ``` Example: @@ -66,31 +68,39 @@ export OPENCODE_API_URL=http://127.0.0.1:4096 opencode-attach ``` +Extra arguments are forwarded to the client, with `--server` appended to the subcommand: + +```bash +opencode-attach session list --format json +``` + ## API Once running, the server exposes an OpenAPI 3.1 spec at: ``` -http://localhost:4096/doc +http://localhost:4096/openapi.json ``` +The web UI is served at `/` and is reachable without credentials; every `/api/*` route requires basic auth. + Key endpoints: | Method | Path | Description | |--------|------|-------------| -| `GET` | `/global/health` | Health check and version | -| `GET` | `/session` | List sessions | -| `POST` | `/session` | Create a session | -| `POST` | `/session/:id/message` | Send a message | -| `GET` | `/event` | Server-sent events stream | +| `GET` | `/api/info` | Health check and version | +| `GET` | `/api/session` | List sessions | +| `POST` | `/api/session` | Create a session | +| `POST` | `/api/session/:id/prompt` | Send a message | +| `GET` | `/api/event` | Server-sent events stream | -See the [OpenCode server docs](https://opencode.ai/docs/server/) for the full API reference. +See the [OpenCode v2 API reference](https://opencode.ai/v2/docs/api/) for the full API surface. ## Image details -- **Base image**: `node:24` (Debian) -- **Install**: `opencode-ai` via npm global install +- **Base image**: `node:26-trixie` (Debian) +- **Install**: `@opencode/cli` (OpenCode 2) via npm global install - **User**: dedicated non-root `opencode` user - **Cluster tooling**: `kubectl` (copied from official registry image) -- **Entrypoint**: `opencode serve` +- **Entrypoint**: `opencode` - **Default port**: `4096` diff --git a/opencode-attach b/opencode-attach index 46adeab..79a6075 100644 --- a/opencode-attach +++ b/opencode-attach @@ -45,4 +45,12 @@ trap cleanup_lock INT TERM EXIT kill_existing_instance printf '%s\n' "$$" > "$LOCKFILE" -exec opencode attach -p "${OPENCODE_SERVER_PASSWORD}" "${OPENCODE_API_URL}" "$@" +# OpenCode 2 dropped `opencode attach`: a client is pointed at a server with +# --server , which must follow the subcommand when one is given. +if [ "$#" -eq 0 ]; then + set -- --server "${OPENCODE_API_URL}" +else + set -- "$@" --server "${OPENCODE_API_URL}" +fi + +exec opencode "$@" diff --git a/renovate.json b/renovate.json index 6564085..fd9abc4 100644 --- a/renovate.json +++ b/renovate.json @@ -12,12 +12,12 @@ }, { "customType": "regex", - "description": "Track opencode-ai npm package pinned in Dockerfile RUN command", + "description": "Track @opencode/cli npm package pinned in Dockerfile RUN command", "managerFilePatterns": ["/^Dockerfile$/"], - "matchStrings": ["opencode-ai@(?[^\\s]+)"], - "depNameTemplate": "opencode-ai", + "matchStrings": ["@opencode/cli@(?[^\\s]+)"], + "depNameTemplate": "@opencode/cli", "datasourceTemplate": "npm", "versioningTemplate": "npm" } ] -} \ No newline at end of file +}