diff --git a/AGENTS.md b/AGENTS.md index 99863c6..6619c20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,22 +1,22 @@ ## Scope -- 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. +- This repo owns the static-web wrapper only. `opencode/` is an upstream git submodule; do not edit files under it here except to move the submodule pointer. - Wrapper-owned code lives in `build/`, `runtime/`, `config/`, `scripts/`, `tests/`, and root Docker/CI/package config. - If you must inspect upstream code, read the closest `AGENTS.md` inside `opencode/` first; upstream's default branch is `dev`, not `main`. ## Runtime Wiring -- `Dockerfile` is the real integration path: it installs the filtered upstream app workspace, patches `opencode/packages/app/src/entry.tsx` with `build/patch-upstream-app-source.ts`, builds `opencode/packages/app`, copies wrapper sources, runs `build/check-runtime-config-compat.ts`, bundles `runtime/index.ts`, prepares `opencode/packages/app/dist`, then serves it with nginx. +- `Dockerfile` is the integration source of truth: it installs only the filtered upstream app workspace, patches `opencode/packages/app/src/entry.tsx`, builds `opencode/packages/app`, runs `build/check-runtime-config-compat.ts`, bundles `runtime/index.ts`, prepares `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/.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 writes `opencode-web-customizations.css` from `build/customization-css.ts`; source-level URL injection belongs in `build/patch-upstream-app-source.ts` before the upstream app build. -- `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`. +- `config/nginx.conf.template` plus the generator define the nginx cache/CSP/routing contract: unmatched hosts return 404 except `/health`, configured host routes fall back to `/index.html`, 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"`. +- Root `bun test` runs only `./tests` because root `bunfig.toml` sets `[test].root = "./tests"`; use `bun test tests/.test.ts` for a focused root test. - `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`; keep it free of extra root-only dependencies because Docker invokes `bun ./build/check-runtime-config-compat.ts` without installing the root package. +- `bun run test:compat` is the upstream-compat guard used during `docker build`; keep it free of 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 @@ -29,7 +29,6 @@ - Upstream compatibility check only: `bun run test:compat` - Runtime bundle only: `bun run build:runtime` - 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/.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: `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 `bun ./build/patch-upstream-app-source.ts ./opencode/packages/app/src` then `OPENCODE_CHANNEL=prod bun run --cwd opencode/packages/app build -- --sourcemap false` @@ -46,4 +45,4 @@ - Shell scripts are `/bin/sh`/Alpine-compatible; keep `runtime/generate-nginx-config.sh` env scanning newline-safe so multiline values cannot create fake `SERVER__*` 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. +- Bun version sources are duplicated: `package.json` pins `packageManager` and `@types/bun`, `Dockerfile` pins `oven/bun`, and workflows call `oven-sh/setup-bun` without an explicit version. diff --git a/runtime/generate-nginx-config.sh b/runtime/generate-nginx-config.sh index a3f5a76..c6902ff 100755 --- a/runtime/generate-nginx-config.sh +++ b/runtime/generate-nginx-config.sh @@ -98,12 +98,12 @@ encode_base64() { printf '%s' "$1" | base64 | tr -d '\n' } -runtime_root="/opt/opencode-web" +runtime_root="${OPENCODE_WEB_RUNTIME_ROOT:-/opt/opencode-web}" public_root="$runtime_root/public" runtime_config_root="$runtime_root/runtime-configs" runtime_bundle_path="$runtime_root/runtime/runtime-bundle.js" nginx_template_path="$runtime_root/config/nginx.conf.template" -nginx_config_path="/etc/nginx/conf.d/default.conf" +nginx_config_path="${OPENCODE_WEB_NGINX_CONFIG_PATH:-/etc/nginx/conf.d/default.conf}" nginx_servers_marker="# OPENCODE_WEB_GENERATED_SERVERS" nginx_listen_port="8080" @@ -220,7 +220,7 @@ server { listen $nginx_listen_port; listen [::]:$nginx_listen_port; server_name $host; - root /opt/opencode-web/public; + root $public_root; index index.html; $(write_no_store_headers) @@ -230,7 +230,7 @@ $(write_no_store_headers) } location = /runtime-config.js { - alias /opt/opencode-web/runtime-configs/$host.js; + alias $runtime_config_root/$host.js; } location ^~ /assets/ { diff --git a/scripts/test-runtime-config.ts b/scripts/test-runtime-config.ts index d8725e3..ed04f32 100755 --- a/scripts/test-runtime-config.ts +++ b/scripts/test-runtime-config.ts @@ -74,25 +74,6 @@ async function outputFor(args: string[]) { }; } -async function expectFailure( - name: string, - expectedMessage: string, - args: string[], -): Promise { - console.log(`==> ${name}`); - const result = await outputFor(args); - if (result.exitCode === 0) - throw new Error(`Expected failure, but command succeeded for: ${name}`); - - process.stdout.write(result.output); - - if (!result.output.includes(expectedMessage)) { - throw new Error( - `Expected message not found for: ${name}\nExpected: ${expectedMessage}`, - ); - } -} - async function expectSuccess(name: string, args: string[]): Promise { console.log(`==> ${name}`); await run(args); @@ -111,6 +92,11 @@ async function expectFinalImageLayout( "test -f /opt/opencode-web/config/nginx.conf.template", "test ! -e /opt/opencode-web/config/config", "test -f /opt/opencode-web/public/index.html", + "test -d /opt/opencode-web/public/assets", + "test -s /opt/opencode-web/public/opencode-web-customizations.css", + "test ! -e /opt/opencode-web/public/runtime-config.js", + '! grep -F "