#!/usr/bin/env bash # opencode-inside entrypoint. Runs inside the container; takes no arguments. # opencode serve on 0.0.0.0:4096, then the control gateway on $M64_PORT. Port 4096 is # [runtime.app]: m64 exposes it (the full opencode web UI and API) on a second front # port. 0.0.0.0 is inside the container: only the gate can reach its internal network. set -eu echo '{"phase":"loading","detail":"starting opencode"}' >> "$M64_STATUS" # HOME is the run's /scratch: opencode's DB, caches and every session dir go with the run. export HOME=/scratch/home export WORK_DIR=/scratch/work mkdir -p "$HOME" "$WORK_DIR/sessions" # Packages opencode installs on first start, warmed at build time (at run time only # openrouter.ai is reachable). cp -rn /opt/opencode-capsule/. "$HOME/" export OPENCODE_CONFIG=/capsule/opencode.json # opencode is a Bun binary: Bun's fetch honours HTTP_PROXY / NO_PROXY from the environment # (set by m64 to the gate). The provider's baseURL is plain http://openrouter.ai: the gate # adds the OpenRouter key and sends the request on over TLS, so the key never enters here. # $OPENROUTER_API_KEY only holds the placeholder m64-injected:OPENROUTER_API_KEY. # Run from $WORK_DIR: the web UI's (and `opencode attach`'s) project is the run's scratch # workspace. The gateway still gives each of its sessions a directory under sessions/. cd "$WORK_DIR" opencode serve --hostname 0.0.0.0 --port 4096 --pure & OPENCODE_PID=$! # If opencode dies, take the container down so m64 reports the error. $$ is the gateway # after the exec below. The gateway never reaps opencode, so a dead opencode lingers as a # zombie (state Z): test the state, not just whether the pid exists. alive() { [ -r "/proc/$1/stat" ] && [ "$(cut -d')' -f2 "/proc/$1/stat" | cut -d' ' -f2)" != Z ]; } ( while alive "$OPENCODE_PID"; do sleep 5; done echo '{"phase":"error","detail":"opencode exited"}' >> "$M64_STATUS" kill -TERM $$ ) & exec /opt/gateway/bin/python /capsule/agent_gateway.py