Skip to content
This repository was archived by the owner on Jun 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.git
.env*
node_modules
dist
.playwright-cli
AGENTS.md
README.md
LICENSE
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
submodules: recursive

- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
Expand Down
27 changes: 15 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,47 @@

- This repo owns the static-web wrapper only. `opencode/` is an upstream git submodule; do not edit files under it here. The only allowed upstream change is moving the submodule pointer.
- Wrapper-owned code lives in `build/`, `runtime/`, `config/`, `scripts/`, `tests/`, and root Docker/CI/package config.
- If you need upstream context, read the closest `AGENTS.md` inside `opencode/` first.
- If you must inspect upstream code, read the closest `AGENTS.md` inside `opencode/` first; upstream's default branch is `dev`, not `main`.

## Entry Points
## Runtime Wiring

- `Dockerfile` is the real integration path: it runs `bun run test:compat`, bundles `runtime/index.ts` with `bun run build:runtime`, builds `opencode/packages/app`, patches the built app with `bun run build:prepare-static`, then serves it with nginx.
- `runtime/generate-nginx-config.sh` is the runtime generation source of truth. It validates `SERVER_<N>_HOST` and `SERVER_<N>_BACKEND`, requires contiguous unpadded indexes starting at `1`, normalizes hostnames and backend URLs, writes per-host runtime configs under `/opt/opencode-web/runtime-configs/<host>.js`, and regenerates `/etc/nginx/conf.d/default.conf` from `config/nginx.conf.template`.
- `Dockerfile` is the real integration path: it installs the filtered upstream app workspace, builds `opencode/packages/app`, copies wrapper sources, runs `build/check-runtime-config-compat.ts`, bundles `runtime/index.ts`, patches `opencode/packages/app/dist`, then serves it with nginx.
- `runtime/generate-nginx-config.sh` is the runtime generation source of truth. It runs as `/docker-entrypoint.d/40-opencode-web.sh` under Alpine `/bin/sh`, requires contiguous unpadded indexes starting at `1`, normalizes hostnames/backend URLs, writes per-host configs under `/opt/opencode-web/runtime-configs/<host>.js`, and regenerates nginx config from `config/nginx.conf.template`.
- `runtime/runtime-config-core.ts` is the browser bootstrap: it replaces the persisted server list and default server URL with the injected backend for that host, prunes other servers/credentials, and removes legacy `server.v3`.
- `build/prepare-static-web.ts` injects `/runtime-config.js` and external `opencode-web-customizations.css` into `index.html`, then patches referenced built JS so the app uses `window.__OPENCODE_SERVER_URL` instead of `location.origin`.
- `build/prepare-static-web.ts` injects `/runtime-config.js`, writes `opencode-web-customizations.css` from `build/customization-css.ts`, then patches referenced built JS so the app uses `window.__OPENCODE_SERVER_URL` instead of `location.origin`.
- `config/nginx.conf.template` is the base nginx cache/CSP contract: unmatched hosts return 404 except `/health`, configured hosts are appended by the generator, only `/assets/` is immutable, and all other configured-host responses stay `no-store` with `add_header ... always`.

## Compatibility Contracts

- Root `bun test` runs only `./tests` because root `bunfig.toml` sets `[test].root = "./tests"`.
- `tests/*.contracts.ts` encode every wrapper assumption about upstream app internals, runtime persistence, CSS selectors, and nginx CSP/cache behavior. If upstream changes break the wrapper, update the contract and wrapper code together.
- `bun run test:compat` is the same upstream-compat guard used during `docker build`.
- `bun run test:compat` is the same upstream-compat guard used during `docker build`; keep it free of extra root-only dependencies because Docker invokes `bun ./build/check-runtime-config-compat.ts` without installing the root package.
- CSP/cache headers are intentionally duplicated in `config/nginx.conf.template` and `runtime/generate-nginx-config.sh`; keep them in sync.

## Commands

- First-time setup: `git submodule update --init --recursive`
- Install root dependencies: `bun install --frozen-lockfile`
- Root dependency install is wrapper-only; upstream app deps are installed separately by Docker or the Docker-equivalent upstream app build command below.
- Fast root checks matching CI's build-compat job: `bun test && bun run test:compat && bun run typecheck && bun run lint && bun run format:check`
- Apply formatting: `bun run format`
- Upstream compatibility check only: `bun run test:compat`
- Runtime bundle only: `bun run build:runtime`
- Static web preparation only: `bun run build:prepare-static -- <dist-dir>`
- Static web preparation only, after the upstream app dist exists: `bun run build:prepare-static -- opencode/packages/app/dist`
- Focused root tests: `bun test tests/<name>.test.ts`, for example `bun test tests/compatibility-contracts.test.ts`
- Runtime/image regression check: `bun run test:runtime-config -- --build`; without `--build`, the script expects an existing image tag, defaulting to `opencode-web-docker`.
- End-to-end build: `docker build -t opencode-web-docker .`
- Quick upstream app build smoke check: `bun run --cwd opencode/packages/app build`
- End-to-end build: `bun run docker:build` or `docker build -t opencode-web-docker .`
- Docker-equivalent upstream app build: `bun install --cwd opencode --filter @opencode-ai/app --frozen-lockfile --ignore-scripts` then `OPENCODE_CHANNEL=prod bun run --cwd opencode/packages/app build -- --sourcemap false`
- Update upstream submodule: `bun run upstream:update -- [tag]`
- Dry-run upstream update: `bun run upstream:update -- --dry-run [tag]`

## Gotchas

- CI enters through `.github/workflows/ci.yml` and reusable `validate.yml`; it includes Docker runtime regression, `bun test`, `test:compat`, typecheck, Biome lint/format, `actionlint`, `zizmor`, and `shellcheck`.
- Root `typecheck`, `lint`, and `format` scripts cover `build/`, `runtime/`, `tests/`, and `scripts/`; edits in `config/` or `.github/workflows/` need separate review/tooling.
- GitHub Actions are pinned by full SHA in workflows; preserve pinning when editing `.github/workflows/*`.
- Root `typecheck`, `lint`, and `format` scripts cover wrapper TS/JSON in `build/`, `runtime/`, `tests/`, and `scripts/`; nginx config, workflows, Docker files, and shell semantics need separate review/tooling.
- The Docker build context excludes `scripts/` and most upstream docs/tests via `.dockerignore`; update `.dockerignore` before relying on ignored files in `Dockerfile`.
- Shell scripts are `/bin/sh`/Alpine-compatible; do not use Bash-only syntax in `runtime/generate-nginx-config.sh`.
- Shell scripts are `/bin/sh`/Alpine-compatible; keep `runtime/generate-nginx-config.sh` env scanning newline-safe so multiline values cannot create fake `SERVER_<N>_*` names.
- The final image runs as non-root `nginx` on port `8080`; keep `/opt/opencode-web/public` read-only and generated config/writable state under `/etc/nginx/conf.d`, `/opt/opencode-web/runtime-configs`, or `/var/cache/nginx`.
- Root TypeScript uses `erasableSyntaxOnly`; avoid enums, namespaces, parameter properties, or other TS syntax that needs transpilation in wrapper `.ts` files.
- Bun version sources are duplicated: `package.json` `packageManager` drives GitHub `setup-bun`, while `Dockerfile` and `@types/bun` must be reviewed separately when changing Bun.
- Upstream OpenCode's default branch is `dev`, not `main`.
11 changes: 5 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ COPY --parents \
RUN bun install --cwd opencode --filter @opencode-ai/app --frozen-lockfile --ignore-scripts

COPY opencode ./opencode
COPY package.json bun.lock tsconfig.json biome.json ./
RUN bun install --frozen-lockfile
# Keep wrapper-only edits from invalidating the upstream app build layer.
RUN OPENCODE_CHANNEL=prod bun run --cwd opencode/packages/app build -- --sourcemap false
COPY build ./build/
COPY runtime ./runtime/
COPY tests ./tests/
COPY config ./config/
RUN bun run test:compat
RUN bun run build:runtime
RUN OPENCODE_CHANNEL=prod bun run --cwd opencode/packages/app build -- --sourcemap false
RUN bun run build:prepare-static -- ./opencode/packages/app/dist
RUN bun ./build/check-runtime-config-compat.ts
RUN bun ./build/transpile-runtime.ts
RUN bun ./build/prepare-static-web.ts ./opencode/packages/app/dist
RUN mkdir -p release/public release/runtime release/config \
&& cp -r config/. release/config/ \
&& cp dist/runtime/runtime-bundle.js release/runtime/ \
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Requests with an unmatched `Host` header never receive a generated runtime confi

## How It Works

1. **Build:** the Docker build compiles the upstream app, injects the runtime bootstrap into `index.html`, patches the built frontend to use `window.__OPENCODE_SERVER_URL`, and runs a compatibility check so upstream persistence changes fail early.
1. **Build:** the Docker build compiles the upstream app, runs a compatibility check before assembling the release, injects the runtime bootstrap into `index.html`, and patches the built frontend to use `window.__OPENCODE_SERVER_URL`.
2. **Runtime:** nginx's official entrypoint runs `/docker-entrypoint.d/40-opencode-web.sh`, which validates `SERVER_<N>_HOST` and `SERVER_<N>_BACKEND`, generates per-host runtime configs under `/opt/opencode-web/runtime-configs/<host>.js`, and writes `/etc/nginx/conf.d/default.conf` from `config/nginx.conf.template`.
3. **Serving:** nginx serves shared static files from `/opt/opencode-web/public`. Configured hosts get exact server blocks, `/runtime-config.js` aliases the matching generated config, extension-like missing static files return `404`, route-like extensionless paths fall back to `/index.html`, and only `/assets/` uses immutable caching.

Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"files": {
"includes": [
"**",
Expand Down
24 changes: 10 additions & 14 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"format:check": "biome format build/ runtime/ tests/ scripts/ package.json tsconfig.json biome.json"
},
"devDependencies": {
"@biomejs/biome": "^2.4.15",
"@biomejs/biome": "^2.4.16",
"@types/bun": "1.3.14",
"@types/node": "25.9.1",
"typescript": "^6.0.3"
Expand Down