diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 141a0c7..5c4af37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: bash -n tests/test_helpers.sh bash -n tests/test_dry_run.sh bash -n tests/test_build_expected_version.sh + bash -n tests/test_runtime_defaults.sh bash -n tests/test_launch_mode_flags.sh bash -n tests/test_container_replace.sh bash -n tests/wrapper-dryrun.sh @@ -56,6 +57,7 @@ jobs: shellcheck -x tests/test_helpers.sh shellcheck -x tests/test_dry_run.sh shellcheck -x tests/test_build_expected_version.sh + shellcheck -x tests/test_runtime_defaults.sh shellcheck -x tests/test_launch_mode_flags.sh shellcheck -x tests/test_container_replace.sh shellcheck -x tests/wrapper-dryrun.sh @@ -115,6 +117,11 @@ jobs: - name: Validate required binaries run: | docker run --rm --entrypoint sh opencode_web_yolo:ci -lc 'command -v gh && command -v git && command -v ssh && command -v sqlite3 && command -v timeout && command -v tini && opencode serve --help' + - name: Validate Node 22 runtime metadata + run: | + test "$(docker run --rm --entrypoint sh opencode_web_yolo:ci -lc 'node --version')" = "$(docker run --rm --entrypoint cat opencode_web_yolo:ci /opt/opencode-web-yolo-node-version)" + test "$(docker run --rm --entrypoint cat opencode_web_yolo:ci /opt/opencode-web-yolo-node-major)" = 22 + docker run --rm --entrypoint sh opencode_web_yolo:ci -lc 'node_version="$(node --version)"; node_major="${node_version#v}"; node_major="${node_major%%.*}"; test "${node_major}" = 22' - name: Validate real startup VACUUM run: | set -euo pipefail diff --git a/.opencode_web_yolo.Dockerfile b/.opencode_web_yolo.Dockerfile index 5c061ed..44b581a 100644 --- a/.opencode_web_yolo.Dockerfile +++ b/.opencode_web_yolo.Dockerfile @@ -1,5 +1,7 @@ -ARG BASE_IMAGE=node:22-slim -FROM ${BASE_IMAGE} +FROM node:22-slim + +RUN node_version="$(node --version)" \ + && printf '%s\n' "${node_version}" | grep -Eq '^v22\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' ENV DEBIAN_FRONTEND=noninteractive ENV OPENCODE_WEB_YOLO_HOME=/home/opencode @@ -22,9 +24,8 @@ RUN apt-get update \ tini \ && rm -rf /var/lib/apt/lists/* -ARG OPENCODE_NPM_PACKAGE=opencode-ai ARG OPENCODE_VERSION=latest -RUN npm install -g "${OPENCODE_NPM_PACKAGE}@${OPENCODE_VERSION}" +RUN npm install -g "opencode-ai@${OPENCODE_VERSION}" ARG OPENCODE_WEB_BUILD_PLAYWRIGHT=0 ARG PLAYWRIGHT_VERSION=1.62.1 @@ -44,6 +45,11 @@ RUN if [ "${OPENCODE_WEB_BUILD_WRANGLER}" = "1" ]; then \ ARG WRAPPER_VERSION=0.0.0 RUN mkdir -p /opt /workspace "${OPENCODE_WEB_YOLO_HOME}" /app \ + && node_version="$(node --version)" \ + && node_major="${node_version#v}" \ + && node_major="${node_major%%.*}" \ + && printf '%s\n' "${node_version}" >/opt/opencode-web-yolo-node-version \ + && printf '%s\n' "${node_major}" >/opt/opencode-web-yolo-node-major \ && opencode --version | tr -d '[:space:]' >/opt/opencode-version \ && printf '%s\n' "${WRAPPER_VERSION}" >/opt/opencode-web-yolo-version \ && printf '%s\n' "${OPENCODE_WEB_BUILD_PLAYWRIGHT}" >/opt/opencode-web-yolo-playwright \ diff --git a/.opencode_web_yolo.sh b/.opencode_web_yolo.sh index 0c21934..e96cc81 100755 --- a/.opencode_web_yolo.sh +++ b/.opencode_web_yolo.sh @@ -23,6 +23,7 @@ fi VERBOSE="${OPENCODE_WEB_VERBOSE}" PLAYWRIGHT_DEFAULT_VERSION="1.62.1" +OPENCODE_PACKAGE="opencode-ai" is_true() { case "${1:-0}" in @@ -39,6 +40,10 @@ normalize_bool() { fi } +node_version_is_22() { + [[ "${1:-}" =~ ^v22\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] +} + validate_retention_days() { local value case "${OPENCODE_WEB_RETENTION_DAYS}" in @@ -548,50 +553,49 @@ write_default_config() { local config_file config_dir config_file="${OPENCODE_WEB_CONFIG_FILE}" config_dir="$(dirname "$config_file")" - mkdir -p "$config_dir" - if [ -e "$config_file" ]; then + ( umask 077; mkdir -p "$config_dir" ) + if [ -e "$config_file" ] || [ -L "$config_file" ]; then die "Config file already exists at ${config_file}. Refusing to overwrite." fi - cat >"$config_file" <<'EOF' + ( + umask 077 + set -C + cat >"$config_file" <<'EOF' # opencode_web_yolo user config -export OPENCODE_WEB_PORT=4096 -export OPENCODE_WEB_HOSTNAME=0.0.0.0 -export OPENCODE_WEB_YOLO_IMAGE=opencode_web_yolo:latest -export OPENCODE_WEB_BASE_IMAGE=node:22-slim -export OPENCODE_WEB_NPM_PACKAGE=opencode-ai -export OPENCODE_WEB_CONTAINER_NAME=opencode_web_yolo -export OPENCODE_WEB_RESTART_POLICY=unless-stopped -export OPENCODE_WEB_RUN_DETACHED=1 -export OPENCODE_WEB_AUTO_PULL=1 -export OPENCODE_WEB_BUILD_PLAYWRIGHT=0 -# Set OPENCODE_WEB_BUILD_PLAYWRIGHT=1 here to persist the Playwright build. -# This explicit pin remains effective even when version checks are skipped. -# export OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION=1.62.1 -export OPENCODE_WEB_BUILD_WRANGLER=0 -export OPENCODE_WEB_RETENTION_DAYS=0 -# Set OPENCODE_WEB_RETENTION_DAYS to a non-negative integer to enable weekly cleanup. -# export OPENCODE_WEB_RETENTION_DRY_RUN=1 -export OPENCODE_WEB_RETENTION_POLL_SECONDS=3600 -export OPENCODE_WEB_RETENTION_FETCH_TIMEOUT_MS=10000 -export OPENCODE_WEB_RETENTION_VERIFY_TIMEOUT_MS=10000 -export OPENCODE_WEB_SKIP_UPDATE_CHECK=0 -export OPENCODE_WEB_SKIP_VERSION_CHECK=0 # Required: set a non-empty password before running the server. -# export OPENCODE_SERVER_PASSWORD=change-me-now -# Optional: +# export OPENCODE_SERVER_PASSWORD='change-me-now' # export OPENCODE_SERVER_USERNAME=opencode +# export OPENCODE_WEB_PORT=4096 +# export OPENCODE_WEB_CONTAINER_NAME=opencode_web_yolo +# export OPENCODE_WEB_RESTART_POLICY=unless-stopped +# export OPENCODE_WEB_RUN_DETACHED=1 +# export OPENCODE_WEB_AUTO_PULL=0 +# export OPENCODE_WEB_YOLO_IMAGE=opencode_web_yolo:latest # export OPENCODE_WEB_CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/opencode # export OPENCODE_WEB_DATA_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/opencode +# export OPENCODE_WEB_BUILD_PLAYWRIGHT=1 +# export OPENCODE_WEB_BUILD_WRANGLER=1 +# export OPENCODE_WEB_RETENTION_DAYS=30 +# Advanced persistent overrides: +# export OPENCODE_WEB_SKIP_UPDATE_CHECK=1 # export OPENCODE_WEB_YOLO_REPO=laurenceputra/opencode_web_yolo # export OPENCODE_WEB_YOLO_BRANCH=main +# export OPENCODE_WEB_RETENTION_POLL_SECONDS=3600 +# export OPENCODE_WEB_RETENTION_FETCH_TIMEOUT_MS=10000 +# export OPENCODE_WEB_RETENTION_VERIFY_TIMEOUT_MS=10000 +# One-shot/troubleshooting controls are intentionally not generated here: +# --pull, --no-pull, OPENCODE_WEB_BUILD_PULL, OPENCODE_WEB_BUILD_NO_CACHE, +# --dry-run, OPENCODE_WEB_DRY_RUN, --verbose, OPENCODE_WEB_RETENTION_DRY_RUN, +# and version-check overrides. EOF + ) log "Wrote ${config_file}." } show_health() { local status=0 - local image_wrapper_version image_opencode_version image_playwright image_playwright_version image_playwright_expected_version image_wrangler + local image_wrapper_version image_opencode_version image_node_version image_node_major image_playwright image_playwright_version image_playwright_expected_version image_wrangler local runtime_home runtime_xdg_config runtime_xdg_data runtime_xdg_state local container_home_env container_xdg_config_env container_xdg_data_env container_xdg_state_env @@ -645,12 +649,16 @@ show_health() { printf '%s\n' " image_present=yes" image_wrapper_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-version 2>/dev/null || true)" image_opencode_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-version 2>/dev/null || true)" + image_node_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-node-version 2>/dev/null || true)" + image_node_major="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-node-major 2>/dev/null || true)" image_playwright="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-playwright 2>/dev/null || true)" image_playwright_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-playwright-version 2>/dev/null || true)" image_playwright_expected_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-playwright-expected-version 2>/dev/null || true)" image_wrangler="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-wrangler 2>/dev/null || true)" printf '%s\n' " image_wrapper_version=${image_wrapper_version:-unknown}" printf '%s\n' " image_opencode_version=${image_opencode_version:-unknown}" + printf '%s\n' " image_node_version=${image_node_version:-unknown}" + printf '%s\n' " image_node_major=${image_node_major:-unknown}" printf '%s\n' " image_build_playwright=${image_playwright:-unknown}" printf '%s\n' " image_playwright_version=${image_playwright_version:-unknown}" printf '%s\n' " image_playwright_expected_version=${image_playwright_expected_version:-unknown}" @@ -715,7 +723,7 @@ resolve_expected_opencode_version() { return 0 fi - npm view "${OPENCODE_WEB_NPM_PACKAGE}" version --json 2>/dev/null | tr -d '"' | tr -d '[:space:]' + npm view "${OPENCODE_PACKAGE}" version --json 2>/dev/null | tr -d '"' | tr -d '[:space:]' } resolve_expected_playwright_version() { @@ -725,11 +733,6 @@ resolve_expected_playwright_version() { return 0 fi - if [ -n "${OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION:-}" ]; then - printf '%s\n' "${OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION}" - return 0 - fi - if is_true "${OPENCODE_WEB_SKIP_VERSION_CHECK}"; then debug "Skipping Playwright npm version check." return 0 @@ -761,7 +764,7 @@ build_image() { fi requested_playwright_version="${2:-}" - build_playwright_version="${requested_playwright_version:-${OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION:-${PLAYWRIGHT_DEFAULT_VERSION}}}" + build_playwright_version="${requested_playwright_version:-${PLAYWRIGHT_DEFAULT_VERSION}}" build_cmd=(docker build -f "${SCRIPT_DIR}/.opencode_web_yolo.Dockerfile") if is_true "${OPENCODE_WEB_BUILD_PULL}"; then @@ -772,9 +775,7 @@ build_image() { fi build_cmd+=( - --build-arg "BASE_IMAGE=${OPENCODE_WEB_BASE_IMAGE}" --build-arg "WRAPPER_VERSION=${WRAPPER_VERSION}" - --build-arg "OPENCODE_NPM_PACKAGE=${OPENCODE_WEB_NPM_PACKAGE}" --build-arg "OPENCODE_VERSION=${build_opencode_version}" --build-arg "OPENCODE_WEB_BUILD_PLAYWRIGHT=${OPENCODE_WEB_BUILD_PLAYWRIGHT}" --build-arg "PLAYWRIGHT_VERSION=${build_playwright_version}" @@ -788,15 +789,18 @@ build_image() { } ensure_image() { - local expected_opencode_version expected_playwright_version image_wrapper_version image_opencode_version image_playwright image_playwright_version image_wrangler + local expected_opencode_version expected_playwright_version image_wrapper_version image_opencode_version image_node_version image_node_major image_playwright image_playwright_version image_wrangler + local compatibility_rebuild_requires_pull local -a reasons reasons=() + compatibility_rebuild_requires_pull=0 expected_opencode_version="$(resolve_expected_opencode_version || true)" expected_playwright_version="$(resolve_expected_playwright_version || true)" if ! docker image inspect "${OPENCODE_WEB_YOLO_IMAGE}" >/dev/null 2>&1; then reasons+=("image '${OPENCODE_WEB_YOLO_IMAGE}' is missing") + compatibility_rebuild_requires_pull=1 fi if is_true "${OPENCODE_WEB_BUILD_PULL}"; then @@ -811,6 +815,7 @@ ensure_image() { image_wrapper_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-version 2>/dev/null || true)" if [ -z "$image_wrapper_version" ] || [ "$image_wrapper_version" != "$WRAPPER_VERSION" ]; then reasons+=("wrapper version metadata mismatch (image='${image_wrapper_version:-missing}', local='${WRAPPER_VERSION}')") + compatibility_rebuild_requires_pull=1 fi image_opencode_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-version 2>/dev/null || true)" @@ -818,6 +823,13 @@ ensure_image() { reasons+=("OpenCode version mismatch (image='${image_opencode_version:-missing}', expected='${expected_opencode_version}')") fi + image_node_version="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-node-version 2>/dev/null || true)" + image_node_major="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-node-major 2>/dev/null || true)" + if [ "${image_node_major}" != "22" ] || ! node_version_is_22 "${image_node_version}"; then + reasons+=("Node runtime metadata mismatch (image_version='${image_node_version:-missing}', image_major='${image_node_major:-missing}', expected_major='22')") + compatibility_rebuild_requires_pull=1 + fi + image_playwright="$(docker run --rm --entrypoint cat "${OPENCODE_WEB_YOLO_IMAGE}" /opt/opencode-web-yolo-playwright 2>/dev/null || true)" if [ "$image_playwright" != "${OPENCODE_WEB_BUILD_PLAYWRIGHT}" ]; then reasons+=("Playwright build mismatch (image='${image_playwright:-missing}', expected='${OPENCODE_WEB_BUILD_PLAYWRIGHT}')") @@ -845,6 +857,14 @@ ensure_image() { for reason in "${reasons[@]}"; do log " - ${reason}" done + if [ "$compatibility_rebuild_requires_pull" -eq 1 ]; then + # A compatibility/version-driven rebuild may have been selected with + # --no-pull or stale OPENCODE_WEB_AUTO_PULL=0. Refreshing the release-owned + # runtime base is mandatory, so do not let those one-shot controls suppress + # Docker's --pull. + OPENCODE_WEB_BUILD_PULL=1 + log "Compatibility/version rebuild requires Docker --pull." + fi build_image "$expected_opencode_version" "$expected_playwright_version" } diff --git a/.opencode_web_yolo_config.sh b/.opencode_web_yolo_config.sh index fc2119e..32843ca 100755 --- a/.opencode_web_yolo_config.sh +++ b/.opencode_web_yolo_config.sh @@ -5,9 +5,25 @@ set -euo pipefail OPENCODE_WEB_YOLO_CONFIG_FILE_DEFAULT="${HOME}/.opencode_web_yolo/config" OPENCODE_WEB_CONFIG_FILE="${OPENCODE_WEB_YOLO_CONFIG_FILE:-$OPENCODE_WEB_YOLO_CONFIG_FILE_DEFAULT}" +# These controls remain environment-compatible, but are intentionally not +# persistent config settings. Capture the caller's environment before loading +# the user file so old generated configs cannot turn troubleshooting switches +# into durable defaults. +_opencode_web_build_no_cache_env_set="${OPENCODE_WEB_BUILD_NO_CACHE+x}" +_opencode_web_build_no_cache_env="${OPENCODE_WEB_BUILD_NO_CACHE-}" +_opencode_web_build_pull_env_set="${OPENCODE_WEB_BUILD_PULL+x}" +_opencode_web_build_pull_env="${OPENCODE_WEB_BUILD_PULL-}" +_opencode_web_dry_run_env_set="${OPENCODE_WEB_DRY_RUN+x}" +_opencode_web_dry_run_env="${OPENCODE_WEB_DRY_RUN-}" +_opencode_web_verbose_env_set="${OPENCODE_WEB_VERBOSE+x}" +_opencode_web_verbose_env="${OPENCODE_WEB_VERBOSE-}" +_opencode_web_skip_version_check_env_set="${OPENCODE_WEB_SKIP_VERSION_CHECK+x}" +_opencode_web_skip_version_check_env="${OPENCODE_WEB_SKIP_VERSION_CHECK-}" +_opencode_web_retention_dry_run_env_set="${OPENCODE_WEB_RETENTION_DRY_RUN+x}" +_opencode_web_retention_dry_run_env="${OPENCODE_WEB_RETENTION_DRY_RUN-}" + # Runtime defaults : "${OPENCODE_WEB_YOLO_IMAGE:=opencode_web_yolo:latest}" -: "${OPENCODE_WEB_BASE_IMAGE:=node:22-slim}" : "${OPENCODE_WEB_PORT:=4096}" : "${OPENCODE_WEB_HOSTNAME:=0.0.0.0}" : "${OPENCODE_WEB_YOLO_HOME:=/home/opencode}" @@ -20,7 +36,6 @@ OPENCODE_WEB_CONFIG_FILE="${OPENCODE_WEB_YOLO_CONFIG_FILE:-$OPENCODE_WEB_YOLO_CO : "${OPENCODE_WEB_BUILD_NO_CACHE:=0}" : "${OPENCODE_WEB_BUILD_PULL:=0}" : "${OPENCODE_WEB_BUILD_PLAYWRIGHT:=0}" -: "${OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION:=}" : "${OPENCODE_WEB_BUILD_WRANGLER:=0}" : "${OPENCODE_WEB_AUTO_PULL:=1}" : "${OPENCODE_WEB_RUN_DETACHED:=1}" @@ -28,7 +43,6 @@ OPENCODE_WEB_CONFIG_FILE="${OPENCODE_WEB_YOLO_CONFIG_FILE:-$OPENCODE_WEB_YOLO_CO : "${OPENCODE_WEB_VERBOSE:=0}" : "${OPENCODE_WEB_YOLO_REPO:=laurenceputra/opencode_web_yolo}" : "${OPENCODE_WEB_YOLO_BRANCH:=main}" -: "${OPENCODE_WEB_NPM_PACKAGE:=opencode-ai}" : "${OPENCODE_SERVER_USERNAME:=opencode}" if [ "${OPENCODE_WEB_RETENTION_DAYS+x}" != x ]; then OPENCODE_WEB_RETENTION_DAYS=0 @@ -49,5 +63,44 @@ if [ -f "$OPENCODE_WEB_CONFIG_FILE" ]; then . "$OPENCODE_WEB_CONFIG_FILE" fi +# Release-owned runtime settings are fixed here, after the user file is +# sourced. This keeps existing configs usable while making stale assignments +# harmless without rewriting them. +OPENCODE_WEB_HOSTNAME=0.0.0.0 +OPENCODE_WEB_YOLO_HOME=/home/opencode +OPENCODE_WEB_YOLO_WORKDIR=/workspace +OPENCODE_WEB_YOLO_CLEANUP=1 + +if [ "${_opencode_web_build_no_cache_env_set}" = x ]; then + OPENCODE_WEB_BUILD_NO_CACHE="${_opencode_web_build_no_cache_env}" +else + OPENCODE_WEB_BUILD_NO_CACHE=0 +fi +if [ "${_opencode_web_build_pull_env_set}" = x ]; then + OPENCODE_WEB_BUILD_PULL="${_opencode_web_build_pull_env}" +else + OPENCODE_WEB_BUILD_PULL=0 +fi +if [ "${_opencode_web_dry_run_env_set}" = x ]; then + OPENCODE_WEB_DRY_RUN="${_opencode_web_dry_run_env}" +else + OPENCODE_WEB_DRY_RUN=0 +fi +if [ "${_opencode_web_verbose_env_set}" = x ]; then + OPENCODE_WEB_VERBOSE="${_opencode_web_verbose_env}" +else + OPENCODE_WEB_VERBOSE=0 +fi +if [ "${_opencode_web_skip_version_check_env_set}" = x ]; then + OPENCODE_WEB_SKIP_VERSION_CHECK="${_opencode_web_skip_version_check_env}" +else + OPENCODE_WEB_SKIP_VERSION_CHECK=0 +fi +if [ "${_opencode_web_retention_dry_run_env_set}" = x ]; then + OPENCODE_WEB_RETENTION_DRY_RUN="${_opencode_web_retention_dry_run_env}" +else + OPENCODE_WEB_RETENTION_DRY_RUN=0 +fi + : "${OPENCODE_WEB_CONFIG_DIR:=${XDG_CONFIG_HOME:-$HOME/.config}/opencode}" : "${OPENCODE_WEB_DATA_DIR:=${XDG_DATA_HOME:-$HOME/.local/share}/opencode}" diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0a128..9a03671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project are documented here. +## [0.4.0] - 2026-09-20 + +- Made the runtime release the owner of the fixed `node:22-slim` base image and `opencode-ai` package, with Node 22 build assertions and image metadata validation. +- Added compatibility rebuilds that force Docker `--pull` for legacy, missing, or non-Node-22 image metadata, including after wrapper self-update and `--no-pull` invocations. +- Changed generated config to a secure, override-only commented template and stopped honoring stale release-owned runtime settings without rewriting existing user config. +- Preserved `OPENCODE_WEB_AUTO_PULL` as a persistent config override while retaining mandatory pulls for missing or incompatible images. + ## [0.3.0] - 2026-09-13 - Switched runtime launches from `opencode web` to `opencode serve`. diff --git a/README.md b/README.md index 587c851..e424626 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Defaults: - Bind/publish: `127.0.0.1:4096:4096` - OpenCode serve host inside container: `0.0.0.0` - OpenCode package install target: `latest` at build time +- Runtime base image: `node:22-slim` (release-owned and enforced during build) - Playwright build: disabled by default; opt in with `OPENCODE_WEB_BUILD_PLAYWRIGHT=1` in the persistent config or `--playwright` for one run - Container name: `opencode_web_yolo` - Restart policy: `unless-stopped` @@ -81,7 +82,7 @@ If a container with the configured name already exists, wrapper launch replaces ### Self-update and repair -Managed installs check the configured GitHub branch on startup. An update downloads one branch archive snapshot, validates the complete release (including the runtime supervisor and retention worker), and only then promotes it before re-executing with the original arguments and environment. The tracked `.opencode_web_yolo.manifest` controls the release file set. Incomplete installs are repaired even when their local `VERSION` equals the remote version; malformed or incomplete archives are rejected before Docker build. Set `OPENCODE_WEB_SKIP_UPDATE_CHECK=1` to skip network checks, but an incomplete managed install still fails closed and must be repaired with `install.sh`. +Managed installs check the configured GitHub branch on startup. An update downloads one branch archive snapshot, validates the complete release (including the runtime supervisor and retention worker), and only then promotes it before re-executing with the original arguments and environment. The re-exec marker prevents an update loop; explicit and inherited safety controls remain enabled, and original CLI flags remain authoritative. A value exported by a historical wrapper's config can survive that immediate re-exec, but the new loader ignores stale config assignments on the next fresh invocation. The tracked `.opencode_web_yolo.manifest` controls the release file set. Incomplete installs are repaired even when their local `VERSION` equals the remote version; malformed or incomplete archives are rejected before Docker build. Set `OPENCODE_WEB_SKIP_UPDATE_CHECK=1` to skip network checks, but an incomplete managed install still fails closed and must be repaired with `install.sh`. Bootstrap installation from `curl | bash` uses the same archive-and-validation flow. `curl` and `tar` are required for streamed/bootstrap installs and self-update repairs. Branch names containing `/` are supported; other URL-significant branch characters are encoded safely. @@ -90,8 +91,11 @@ Recovery note for historical `0.1.10` installs: that old updater relies on GNU ` ## Configuration Run `opencode_web_yolo config` to generate a sample config file at `~/.opencode_web_yolo/config`. -The wrapper sources that file on startup, so it is the right place for persistent operator defaults. -For one-off runs you can still prefix the command with environment variables in your shell, but if the same variable is also exported in the config file, the config-file value wins because it is loaded during wrapper startup. +The wrapper sources that file on startup, so it is the right place for persistent operator overrides. +The generated file is an override-only, mode-0600 commented template. Release-owned runtime +settings (the Node base image, OpenCode package, internal hostname/home/workdir/cleanup, and +Playwright package-version pin) are not configurable; stale assignments in older config files +are ignored. One-shot build/debug controls should be supplied as flags or environment variables. Common workflow: @@ -108,22 +112,17 @@ Operator-facing settings: | `OPENCODE_SERVER_PASSWORD` | none, required | Required non-empty password for OpenCode Web. Startup fails if it is missing or empty. | | `OPENCODE_SERVER_USERNAME` | `opencode` | Login username paired with `OPENCODE_SERVER_PASSWORD`. | | `OPENCODE_WEB_PORT` | `4096` | Host/container port used for `opencode serve` and the local Docker publish mapping. | -| `OPENCODE_WEB_HOSTNAME` | `0.0.0.0` | Hostname passed to `opencode serve` inside the container. | | `OPENCODE_WEB_CONTAINER_NAME` | `opencode_web_yolo` | Docker container name used for launch, replacement, and diagnostics. | | `OPENCODE_WEB_RESTART_POLICY` | `unless-stopped` | Docker restart policy applied to the container. | | `OPENCODE_WEB_RUN_DETACHED` | `1` | Launch mode default. Use `1` for background mode or `0` for attached runs unless overridden by flags. | -| `OPENCODE_WEB_AUTO_PULL` | `1` | Pull/rebuild behavior default. Use `0` to disable pull-on-start unless `--pull` is passed. | +| `OPENCODE_WEB_AUTO_PULL` | `1` | Persistent pull-on-start setting. Set to `0` in `~/.opencode_web_yolo/config` to disable ordinary automatic pulls; compatibility rebuilds still force Docker `--pull`. | | `OPENCODE_WEB_YOLO_REPO` | `laurenceputra/opencode_web_yolo` | GitHub repo used for wrapper self-update checks and bootstrap downloads. | | `OPENCODE_WEB_YOLO_BRANCH` | `main` | Branch used with `OPENCODE_WEB_YOLO_REPO` for update checks and bootstrap downloads. | | `OPENCODE_WEB_SKIP_UPDATE_CHECK` | `0` | Set to `1` to skip the wrapper's remote `VERSION` check and self-update flow. | -| `OPENCODE_WEB_SKIP_VERSION_CHECK` | `0` | Set to `1` to skip remote npm lookups and OpenCode/enabled-Playwright package version drift checks when deciding whether to rebuild; explicit Playwright pins still apply. | | `OPENCODE_WEB_CONFIG_DIR` | `${XDG_CONFIG_HOME:-$HOME/.config}/opencode` | Host OpenCode config directory mounted into the container for persistent config and rules. | | `OPENCODE_WEB_DATA_DIR` | `${XDG_DATA_HOME:-$HOME/.local/share}/opencode` | Host OpenCode data directory mounted into the container for persistent sessions, provider auth, and state. | | `OPENCODE_WEB_YOLO_IMAGE` | `opencode_web_yolo:latest` | Docker image tag the wrapper builds and runs. | -| `OPENCODE_WEB_BASE_IMAGE` | `node:22-slim` | Base image used when rebuilding the runtime image. | -| `OPENCODE_WEB_NPM_PACKAGE` | `opencode-ai` | npm package installed in the runtime image for the OpenCode CLI. | | `OPENCODE_WEB_BUILD_PLAYWRIGHT` | `0` | Set to `1` in `~/.opencode_web_yolo/config` for durable Playwright enablement; `--playwright` enables it for one run and preinstalls Chromium into `/ms-playwright`. | -| `OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION` | none | Optional exact `@playwright/test` install pin. It remains the Docker build target when `OPENCODE_WEB_SKIP_VERSION_CHECK=1`; that skip suppresses npm lookup and installed-version drift comparison, but does not discard the explicit pin. When no pin is set, an enabled build resolves npm unless checks are skipped, then uses the deterministic `1.62.1` fallback. | | `OPENCODE_WEB_BUILD_WRANGLER` | `0` | Set to `1` to install `wrangler@latest` globally in the runtime image. `--wrangler` enables this and mounts host Wrangler config for the run. | | `OPENCODE_WEB_RETENTION_DAYS` | `0` | Non-negative number of days. After health succeeds, delete inactive root sessions older than this cutoff at most once per seven days. A flag overrides the configured value for that invocation. | | `OPENCODE_WEB_RETENTION_DRY_RUN` | `0` | Safely preview retention candidates without deleting or advancing the success marker. | @@ -131,6 +130,17 @@ Operator-facing settings: | `OPENCODE_WEB_RETENTION_VERIFY_TIMEOUT_MS` | `10000` | Positive bounded deletion-verification timeout in milliseconds. | | `OPENCODE_WEB_RETENTION_POLL_SECONDS` | `3600` | Positive scheduler interval; values below one second are rejected. | +`OPENCODE_WEB_SKIP_VERSION_CHECK=1`, `OPENCODE_WEB_BUILD_PULL=1`, +`OPENCODE_WEB_BUILD_NO_CACHE=1`, `OPENCODE_WEB_DRY_RUN=1`, and +`OPENCODE_WEB_VERBOSE=1` plus `OPENCODE_WEB_RETENTION_DRY_RUN=1` remain supported as environment compatibility/troubleshooting +controls, but are not generated as persistent defaults. `--pull`, `--no-pull`, `--dry-run`, +and `--verbose` are likewise one-shot. A compatibility rebuild for a legacy or missing Node +metadata image always adds Docker `--pull`, including when `--no-pull` is used or persistent +auto-pull is disabled. + +`OPENCODE_WEB_AUTO_PULL` is persistent when set in the generated config file. Use `--pull` or +`--no-pull` when the pull behavior should apply only to one invocation. + ### Playwright runtime Playwright is intentionally opt-in because its Chromium browser and Linux dependencies make the image substantially larger. To keep it enabled across runs, edit the generated config and set: @@ -139,9 +149,9 @@ Playwright is intentionally opt-in because its Chromium browser and Linux depend export OPENCODE_WEB_BUILD_PLAYWRIGHT=1 ``` -Use `--playwright` instead when the build should be enabled only for that invocation. The enabled image installs the global `@playwright/test` package at an explicit version, runs that package's `playwright install --with-deps chromium`, and stores the expected and installed versions in image metadata. The global CLI is a convenience for runtime diagnostics and commands; arbitrary mounted projects should still declare `@playwright/test` locally for normal Node.js imports and project dependency resolution. +Use `--playwright` instead when the build should be enabled only for that invocation. The enabled image installs the wrapper-owned global `@playwright/test` package, resolving the current npm version when checks are enabled and using the release fallback when checks are skipped. It runs that package's `playwright install --with-deps chromium` and stores the expected and installed versions in image metadata; users cannot pin the package through wrapper config. The global CLI is a convenience for runtime diagnostics and commands; arbitrary mounted projects should still declare `@playwright/test` locally for normal Node.js imports and project dependency resolution. -Truthy toggle values such as `true`, `yes`, and `on` are accepted and normalized to `1` before image build arguments and metadata comparisons. `OPENCODE_WEB_SKIP_VERSION_CHECK=1` skips remote version lookup and package-version drift checks only; Playwright build enablement and explicit install pins still apply. +Truthy toggle values such as `true`, `yes`, and `on` are accepted and normalized to `1` before image build arguments and metadata comparisons. `OPENCODE_WEB_SKIP_VERSION_CHECK=1` skips remote version lookup and package-version drift checks only; Playwright build enablement remains effective. ## Persistence Paths @@ -166,7 +176,7 @@ sidecars. ## Weekly session retention -Enable cleanup in the generated config, or override it for one invocation: +Enable retention in the generated config, or override it for one invocation: ```bash export OPENCODE_WEB_RETENTION_DAYS=30 @@ -238,7 +248,7 @@ Force-refresh image to the resolved latest OpenCode and Playwright versions: ```bash OPENCODE_VERSION="$(npm view opencode-ai version)" PLAYWRIGHT_VERSION="$(npm view @playwright/test version)" -docker build --pull --build-arg BASE_IMAGE=node:22-slim --build-arg WRAPPER_VERSION="$(cat VERSION)" --build-arg OPENCODE_NPM_PACKAGE=opencode-ai --build-arg OPENCODE_VERSION="${OPENCODE_VERSION}" --build-arg OPENCODE_WEB_BUILD_PLAYWRIGHT=1 --build-arg PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION}" -t opencode_web_yolo:latest -f .opencode_web_yolo.Dockerfile . +docker build --pull --build-arg WRAPPER_VERSION="$(cat VERSION)" --build-arg OPENCODE_VERSION="${OPENCODE_VERSION}" --build-arg OPENCODE_WEB_BUILD_PLAYWRIGHT=1 --build-arg PLAYWRIGHT_VERSION="${PLAYWRIGHT_VERSION}" -t opencode_web_yolo:latest -f .opencode_web_yolo.Dockerfile . ``` ## Reverse Proxy (Nginx) diff --git a/TECHNICAL.md b/TECHNICAL.md index cf4dfc8..26e415f 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -13,6 +13,10 @@ - detached launch by default (`OPENCODE_WEB_RUN_DETACHED=1`) - Build/update defaults: - pull-on-start by default (`OPENCODE_WEB_AUTO_PULL=1`) +- Runtime release defaults: + - fixed Docker base image `node:22-slim` + - fixed OpenCode npm package `opencode-ai` + - fixed internal serve hostname `0.0.0.0`, runtime home `/home/opencode`, workdir `/workspace`, and cleanup behavior - Reverse proxy is expected in front of localhost bind. - Optional weekly session retention is disabled by default. @@ -90,6 +94,8 @@ Docker image includes: Image metadata files: - `/opt/opencode-web-yolo-version` - `/opt/opencode-version` +- `/opt/opencode-web-yolo-node-version` +- `/opt/opencode-web-yolo-node-major` (must be `22`) - `/opt/opencode-web-yolo-playwright` - `/opt/opencode-web-yolo-playwright-version` (installed package version, or `disabled`) - `/opt/opencode-web-yolo-playwright-expected-version` (Docker build arg version) @@ -146,6 +152,7 @@ On run, unless disabled: - default branch: `main` - if remote version is newer, the complete source is downloaded from `https://github.com/${repo}/archive/refs/heads/${branch}.tar.gz`, extracted to install-home staging, validated against `.opencode_web_yolo.manifest`, promoted with atomic individual renames, and the wrapper re-execs with original args/environment. - if the local managed install is incomplete, the same archive repair runs even when local and remote `VERSION` values are equal. A re-exec marker prevents an update loop, and an incomplete or malformed archive fails closed before Docker build. +- `OPENCODE_WEB_UPDATE_REEXECED=1` prevents an update loop. Explicit and inherited safety controls are preserved across the immediate re-exec, and CLI arguments are parsed afterward and remain authoritative. A stale value exported by a historical config may survive that one re-exec because its provenance is unknowable; a fresh invocation ignores the stale config assignment. Update can be disabled with: - `OPENCODE_WEB_SKIP_UPDATE_CHECK=1` @@ -156,11 +163,17 @@ Image rebuild happens when any trigger is true: - image tag missing locally - wrapper version metadata mismatch - OpenCode version metadata mismatch (unless version check disabled) +- Node version metadata missing or not major 22 (compatibility rebuild forces Docker `--pull`) - Playwright build metadata mismatch - Playwright package version metadata mismatch when the Playwright build is enabled (unless version check disabled) - Wrangler build metadata mismatch - pull/no-cache build flags requested +When `--no-pull` is explicit, the compatibility pull override applies only to a missing image, +wrapper release metadata drift, or missing/malformed/non-22 Node metadata. OpenCode, Playwright, +and Wrangler drift still rebuilds without `--pull`; normal `OPENCODE_WEB_AUTO_PULL` behavior +continues to apply when `--no-pull` is not selected. + OpenCode install target during build: - defaults to `latest` (`OPENCODE_VERSION=latest`) - if `OPENCODE_WEB_EXPECTED_OPENCODE_VERSION` is set, build installs that explicit version @@ -168,10 +181,13 @@ OpenCode install target during build: Controls: - `--pull` or `OPENCODE_WEB_BUILD_PULL=1` - `--playwright` or `OPENCODE_WEB_BUILD_PLAYWRIGHT=1` -- `PLAYWRIGHT_VERSION` is an explicit Docker build arg (default `1.62.1`); the wrapper resolves the current `@playwright/test` version before an enabled build unless `OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION` is set. An explicit expected version remains the install target when version checks are skipped. +- `PLAYWRIGHT_VERSION` is a wrapper-owned Docker build arg (default fallback `1.62.1`); enabled builds resolve the current `@playwright/test` npm version, while skipped checks use that release fallback. User config cannot pin the package version. - `--wrangler` or `OPENCODE_WEB_BUILD_WRANGLER=1` - `OPENCODE_WEB_BUILD_NO_CACHE=1` -- `OPENCODE_WEB_SKIP_VERSION_CHECK=1` skips npm lookup and OpenCode/Playwright package-version drift comparisons, but does not disable enabled builds or discard an explicit Playwright pin. Truthy build toggles (`true`, `yes`, `on`) are normalized to `0`/`1` before Docker args and metadata comparisons. +- `OPENCODE_WEB_AUTO_PULL=1` enables pull-on-start by default and may be set to `0` in the + persistent wrapper config; missing images, wrapper metadata drift, and invalid/missing/non-22 + Node metadata still force Docker `--pull`. +- `OPENCODE_WEB_SKIP_VERSION_CHECK=1` skips npm lookup and OpenCode/Playwright package-version drift comparisons, but does not disable enabled builds. Pull/no-cache, dry-run, verbose, retention dry-run, and skip-version-check settings are one-shot/troubleshooting controls and are not generated as active config defaults. Truthy build toggles (`true`, `yes`, `on`) are normalized to `0`/`1` before Docker args and metadata comparisons. ## Release Checklist diff --git a/VERSION b/VERSION index 0d91a54..1d0ba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.4.0 diff --git a/skills/opencode-web-quality-docs/references/test-acceptance-matrix.md b/skills/opencode-web-quality-docs/references/test-acceptance-matrix.md index 75e3927..17f1afa 100644 --- a/skills/opencode-web-quality-docs/references/test-acceptance-matrix.md +++ b/skills/opencode-web-quality-docs/references/test-acceptance-matrix.md @@ -14,6 +14,13 @@ Use this matrix when authoring tests under `tests/`. - explicit `HOME`, `XDG_CONFIG_HOME`, and `XDG_DATA_HOME` env contract when user mapping is enabled - Password enforcement fails when `OPENCODE_SERVER_PASSWORD` is missing/empty. - Image contains `gh`, `git`, `ssh`, `sqlite3`, and `timeout` binaries; `opencode serve --help` succeeds. +- Docker build asserts Node major 22 and records version metadata; missing, legacy, mismatched, + malformed, and non-22 metadata force a `--pull` rebuild even with `--no-pull`, while matching metadata reuses the image. +- A missing image and wrapper release drift force `--pull`; OpenCode, Playwright, and Wrangler + drift do not override an explicit `--no-pull`. +- Generated config is a mode-0600 override-only commented template; persistent `OPENCODE_WEB_AUTO_PULL=0` + is honored for compatible image reuse while missing/legacy/malformed/non-22 Node metadata still + forces Docker `--pull`. - `-gh` validates host `gh` install/auth and applies gh mount behavior. - `--mount-ssh` warns and mounts only on explicit request. - `--wrangler` requires an existing host `.wrangler` directory, warns about read-write Cloudflare credential exposure, mounts the exact `:rw` path only when requested, and remains absent by default. diff --git a/skills/opencode-web-release/SKILL.md b/skills/opencode-web-release/SKILL.md index 7dd0257..13fb3d3 100644 --- a/skills/opencode-web-release/SKILL.md +++ b/skills/opencode-web-release/SKILL.md @@ -42,6 +42,7 @@ Trigger rebuild when any of these are true: - local image missing - wrapper version metadata mismatch - installed OpenCode version mismatch +- missing/malformed/non-22 Node metadata; compatibility/version-driven rebuilds force Docker `--pull` - `--pull` or no-cache flags requested Record version metadata in the image so checks are deterministic. diff --git a/skills/opencode-web-release/references/rebuild-decision-matrix.md b/skills/opencode-web-release/references/rebuild-decision-matrix.md index bf0e4f4..be5b8f4 100644 --- a/skills/opencode-web-release/references/rebuild-decision-matrix.md +++ b/skills/opencode-web-release/references/rebuild-decision-matrix.md @@ -9,6 +9,7 @@ Rebuild when any trigger is true: - Image tag does not exist locally. - Wrapper version metadata in image does not match local `VERSION`. - OpenCode version in image does not match expected npm-installed version. +- Node runtime metadata is missing, malformed, or not major 22. - Playwright build metadata does not match the requested build toggle. - Playwright package version metadata does not match the expected version when Playwright is enabled. - Wrangler build metadata does not match the requested build toggle. @@ -20,12 +21,17 @@ Rebuild when any trigger is true: - Store OpenCode version in image (for example `/opt/opencode-version`). - Store optional build toggles in image metadata (for example `/opt/opencode-web-yolo-playwright` and `/opt/opencode-web-yolo-wrangler`). - Store Playwright installed and expected package versions in image metadata (for example `/opt/opencode-web-yolo-playwright-version` and `/opt/opencode-web-yolo-playwright-expected-version`). -- Pass an explicit `PLAYWRIGHT_VERSION` build arg; the wrapper resolves `@playwright/test` before an enabled build unless an expected version override is supplied. +- Pass a wrapper-owned `PLAYWRIGHT_VERSION` build arg; enabled builds resolve the current `@playwright/test` npm version, while skipped checks use the release fallback, without a user package-version override. - Use explicit checks in wrapper logic before launch. +- A compatibility/version-driven rebuild must pass Docker `--pull`, regardless of auto-pull or + `--no-pull` controls, so old base images cannot be reused. +- This mandatory override is limited to a missing image, wrapper release drift, or malformed/ + missing/non-22 Node metadata; OpenCode, Playwright, and Wrangler drift do not override an + explicit `--no-pull`. ## Decision Rules - If all metadata checks match and no force flags are set, skip rebuild. - If any check fails, rebuild before running container. -- Compare Playwright package versions only when the requested Playwright build is enabled; `OPENCODE_WEB_SKIP_VERSION_CHECK=1` skips npm lookup and package-version drift checks. An explicit `OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION` remains the Docker install target during the skip. +- Compare Playwright package versions only when the requested Playwright build is enabled; `OPENCODE_WEB_SKIP_VERSION_CHECK=1` skips npm lookup and package-version drift checks. The package version remains release-selected rather than user-configurable. - Log exact reason(s) for rebuild to aid diagnostics. diff --git a/skills/opencode-web-runtime/SKILL.md b/skills/opencode-web-runtime/SKILL.md index 62bf32d..d598903 100644 --- a/skills/opencode-web-runtime/SKILL.md +++ b/skills/opencode-web-runtime/SKILL.md @@ -28,6 +28,8 @@ Enforce these contracts on every runtime change: - Run `opencode serve` with host `0.0.0.0` and configured port unless explicitly overridden. - Install and expose `gh`, `git`, SSH client binaries, and `sqlite3` inside the image. - Persist both OpenCode config and state directories across restarts. +- Build the release-owned `node:22-slim` runtime, assert Node major 22, and record Node + metadata for deterministic legacy-image rebuilds. - Show explicit warnings before enabling sensitive mounts (`-gh`, `--mount-ssh`). - Keep entrypoint ownership setup compatible with read-only sensitive mounts. diff --git a/skills/opencode-web-runtime/references/runtime-checklist.md b/skills/opencode-web-runtime/references/runtime-checklist.md index 74c12be..b30c60f 100644 --- a/skills/opencode-web-runtime/references/runtime-checklist.md +++ b/skills/opencode-web-runtime/references/runtime-checklist.md @@ -37,9 +37,11 @@ Use this checklist for runtime changes in `.opencode_web_yolo.sh`, `.opencode_we - Startup VACUUM is separate from retention: retention remains an API-only, no-raw-SQL worker and must not manually touch SQLite WAL/SHM/journal sidecars. - Map every listed session to a root across directories; block a mapped root when status reports a busy/retrying descendant, fail closed on unmapped active IDs or malformed hierarchies, refresh/recheck immediately before each delete, verify direct 404 before marker advancement, and reject unsafe equal-timestamp page boundaries. The API has no atomic delete-if-idle guarantee. - Validate positive worker fetch and scheduler poll timeouts; use `tini -s -g` for PID1 subreaping/group signal forwarding and preserve SIGINT semantics. -- When enabled, install global `@playwright/test` at an explicit version, run its `playwright install --with-deps chromium`, and use `PLAYWRIGHT_BROWSERS_PATH=/ms-playwright`. +- When enabled, resolve the current global `@playwright/test` version unless checks are skipped (then use the release fallback), run its `playwright install --with-deps chromium`, and use `PLAYWRIGHT_BROWSERS_PATH=/ms-playwright`. - Record installed/expected Playwright versions and rebuild when enabled-image metadata drifts. -- Normalize accepted truthy build toggles to canonical `0`/`1` before Docker arguments and metadata comparisons; version-check skip suppresses lookup/drift comparison but preserves an explicit Playwright install pin. +- Build from fixed `node:22-slim`, assert Node major 22 during the image build, and record + installed Node version/major metadata under `/opt` for compatibility rebuild decisions. +- Normalize accepted truthy build toggles to canonical `0`/`1` before Docker arguments and metadata comparisons; version-check skip suppresses lookup/drift comparison while retaining release-selected Playwright build behavior. ## Exit Criteria diff --git a/tests/run.sh b/tests/run.sh index 0a8819d..a4e2dc9 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -12,6 +12,7 @@ bash "${ROOT_DIR}/tests/test_self_update.sh" bash "${ROOT_DIR}/tests/test_launch_mode_flags.sh" bash "${ROOT_DIR}/tests/test_container_replace.sh" bash "${ROOT_DIR}/tests/test_build_expected_version.sh" +bash "${ROOT_DIR}/tests/test_runtime_defaults.sh" bash "${ROOT_DIR}/tests/test_health.sh" bash "${ROOT_DIR}/tests/test_entrypoint_auth_guard.sh" bash "${ROOT_DIR}/tests/test_entrypoint_home_pin.sh" diff --git a/tests/test_build_expected_version.sh b/tests/test_build_expected_version.sh index 38db3bd..018fe0d 100644 --- a/tests/test_build_expected_version.sh +++ b/tests/test_build_expected_version.sh @@ -35,6 +35,14 @@ case "\$1" in printf '%s\n' "\${FAKE_IMAGE_OPENCODE_VERSION:-1.2.11}" exit 0 fi + if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-node-version" >/dev/null 2>&1; then + printf '%s\n' "\${FAKE_IMAGE_NODE_VERSION:-v22.14.0}" + exit 0 + fi + if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-node-major" >/dev/null 2>&1; then + printf '%s\n' "\${FAKE_IMAGE_NODE_MAJOR:-22}" + exit 0 + fi if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-playwright-version" >/dev/null 2>&1; then printf '%s\n' "\${FAKE_IMAGE_PLAYWRIGHT_VERSION:-1.62.0}" exit 0 @@ -106,9 +114,9 @@ assert_contains "$build_invocation" "--build-arg OPENCODE_WEB_BUILD_WRANGLER=1" : >"${DOCKER_LOG}" export OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION=1.62.2 output_override="$("${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" -assert_contains "$output_override" "Playwright version mismatch (image='1.62.0', expected='1.62.2')" +assert_contains "$output_override" "Playwright version mismatch (image='1.62.0', expected='1.62.1')" build_invocation_override="$(tr -d '\n' <"${DOCKER_LOG}")" -assert_contains "$build_invocation_override" "--build-arg PLAYWRIGHT_VERSION=1.62.2" +assert_contains "$build_invocation_override" "--build-arg PLAYWRIGHT_VERSION=1.62.1" : >"${DOCKER_LOG}" : >"${FAKE_NPM_LOG}" @@ -117,7 +125,7 @@ export OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION=1.62.3 output_skip="$("${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" assert_not_contains "$output_skip" "Playwright version mismatch" build_invocation_skip="$(tr -d '\n' <"${DOCKER_LOG}")" -assert_contains "$build_invocation_skip" "--build-arg PLAYWRIGHT_VERSION=1.62.3" +assert_contains "$build_invocation_skip" "--build-arg PLAYWRIGHT_VERSION=1.62.1" if [ -s "${FAKE_NPM_LOG}" ]; then fail "version checks must not query npm when OPENCODE_WEB_SKIP_VERSION_CHECK=1" fi diff --git a/tests/test_helpers.sh b/tests/test_helpers.sh index 9b56d2c..96d2684 100755 --- a/tests/test_helpers.sh +++ b/tests/test_helpers.sh @@ -72,18 +72,31 @@ case "\$1" in image) shift if [ "\${1:-}" = "inspect" ]; then + if [ "\${FAKE_IMAGE_MISSING:-0}" = 1 ]; then + exit 1 + fi exit 0 fi ;; run) if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-version" >/dev/null 2>&1; then - printf '%s\n' "${wrapper_version}" + printf '%s\n' "\${FAKE_IMAGE_WRAPPER_VERSION:-${wrapper_version}}" exit 0 fi if printf '%s ' "\$@" | grep -F "/opt/opencode-version" >/dev/null 2>&1; then printf '%s\n' "1.2.6" exit 0 fi + if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-node-version" >/dev/null 2>&1; then + if [ "\${FAKE_IMAGE_NODE_VERSION-}" = __missing__ ]; then exit 0; fi + printf '%s\n' "\${FAKE_IMAGE_NODE_VERSION:-v22.14.0}" + exit 0 + fi + if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-node-major" >/dev/null 2>&1; then + if [ "\${FAKE_IMAGE_NODE_MAJOR-}" = __missing__ ]; then exit 0; fi + printf '%s\n' "\${FAKE_IMAGE_NODE_MAJOR:-22}" + exit 0 + fi if printf '%s ' "\$@" | grep -F "/opt/opencode-web-yolo-playwright-version" >/dev/null 2>&1; then printf '%s\n' "disabled" exit 0 @@ -103,6 +116,9 @@ case "\$1" in exit 0 ;; build) + if [ -n "\${FAKE_DOCKER_BUILD_LOG:-}" ]; then + printf '%s\n' "\$*" >>"\${FAKE_DOCKER_BUILD_LOG}" + fi exit 0 ;; esac diff --git a/tests/test_install_bootstrap.sh b/tests/test_install_bootstrap.sh index c887712..451dbf4 100755 --- a/tests/test_install_bootstrap.sh +++ b/tests/test_install_bootstrap.sh @@ -112,6 +112,20 @@ if ! grep -F "[install] Fetching install assets from example/repo@main" "$instal fail "expected bootstrap fetch log line" fi +mkdir -p "${home_dir}/.opencode_web_yolo" +printf '%s\n' 'export OPENCODE_WEB_BASE_IMAGE=node:20-slim' >"${home_dir}/.opencode_web_yolo/config" +chmod 600 "${home_dir}/.opencode_web_yolo/config" +PATH="${fake_bin}:${PATH}" \ + HOME="$home_dir" \ + OPENCODE_WEB_INSTALL_HOME="$install_home" \ + OPENCODE_WEB_BIN_DIR="$bin_dir" \ + OPENCODE_WEB_YOLO_REPO="example/repo" \ + OPENCODE_WEB_YOLO_BRANCH="main" \ + OPENCODE_WEB_TEST_REMOTE_DIR="$remote_dir" \ + bash <"${ROOT_DIR}/install.sh" >/dev/null 2>&1 +assert_contains "$(cat "${home_dir}/.opencode_web_yolo/config")" "OPENCODE_WEB_BASE_IMAGE=node:20-slim" +assert_equals 600 "$(stat -c '%a' "${home_dir}/.opencode_web_yolo/config")" + for archive_mode in traversal symlink hardlink multi-root duplicate-manifest; do bad_install_home="${work_dir}/bad-${archive_mode}" rm -rf "$bad_install_home" diff --git a/tests/test_playwright_dockerfile_contract.sh b/tests/test_playwright_dockerfile_contract.sh index 0e90181..d406e94 100644 --- a/tests/test_playwright_dockerfile_contract.sh +++ b/tests/test_playwright_dockerfile_contract.sh @@ -8,6 +8,15 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" dockerfile_contents="$(cat "${ROOT_DIR}/.opencode_web_yolo.Dockerfile")" assert_contains "$dockerfile_contents" "ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright" +assert_contains "$dockerfile_contents" "FROM node:22-slim" +assert_not_contains "$dockerfile_contents" "ARG BASE_IMAGE" +assert_contains "$dockerfile_contents" "node_version=\"\$(node --version)\"" +assert_contains "$dockerfile_contents" "grep -Eq '^v22\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$'" +assert_contains "$dockerfile_contents" "node_major=\"\${node_major%%.*}\"" +assert_contains "$dockerfile_contents" "/opt/opencode-web-yolo-node-version" +assert_contains "$dockerfile_contents" "/opt/opencode-web-yolo-node-major" +assert_contains "$dockerfile_contents" "npm install -g \"opencode-ai@\${OPENCODE_VERSION}\"" +assert_not_contains "$dockerfile_contents" "ARG OPENCODE_NPM_PACKAGE" assert_contains "$dockerfile_contents" "mkdir -p \"\${PLAYWRIGHT_BROWSERS_PATH}\"" assert_contains "$dockerfile_contents" "chmod 1777 \"\${PLAYWRIGHT_BROWSERS_PATH}\"" assert_contains "$dockerfile_contents" "ARG PLAYWRIGHT_VERSION=1.62.1" @@ -23,9 +32,6 @@ assert_contains "$dockerfile_contents" "/opt/opencode-web-yolo-playwright-expect assert_contains "$dockerfile_contents" "npm install -g wrangler@latest" assert_contains "$dockerfile_contents" "/opt/opencode-web-yolo-wrangler" -line_arg_npm_package="$(grep -n '^ARG OPENCODE_NPM_PACKAGE=' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" -# shellcheck disable=SC2016 # Single quotes intentionally preserve literal Dockerfile shell syntax. -line_npm_install="$(grep -n '^RUN npm install -g \"\${OPENCODE_NPM_PACKAGE}@\${OPENCODE_VERSION}\"$' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" line_arg_playwright="$(grep -n '^ARG OPENCODE_WEB_BUILD_PLAYWRIGHT=' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" line_arg_playwright_version="$(grep -n '^ARG PLAYWRIGHT_VERSION=' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" # shellcheck disable=SC2016 # Single quotes intentionally preserve literal Dockerfile shell syntax. @@ -34,11 +40,11 @@ line_arg_wrangler="$(grep -n '^ARG OPENCODE_WEB_BUILD_WRANGLER=' "${ROOT_DIR}/.o # shellcheck disable=SC2016 # Single quotes intentionally preserve literal Dockerfile shell syntax. line_wrangler_layer="$(grep -n '^RUN if \[ "\${OPENCODE_WEB_BUILD_WRANGLER}" = "1" \]; then \\$' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" line_arg_wrapper_version="$(grep -n '^ARG WRAPPER_VERSION=' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" +line_node_assertion="$(grep -n '^RUN node_version=' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" +line_apt_layer="$(grep -n '^RUN apt-get update' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" # shellcheck disable=SC2016 # Single quotes intentionally preserve literal Dockerfile shell syntax. line_metadata_layer="$(grep -n '^RUN mkdir -p /opt /workspace "\${OPENCODE_WEB_YOLO_HOME}" /app \\$' "${ROOT_DIR}/.opencode_web_yolo.Dockerfile" | cut -d: -f1)" -[ -n "$line_arg_npm_package" ] || fail "missing OPENCODE_NPM_PACKAGE arg declaration" -[ -n "$line_npm_install" ] || fail "missing opencode npm install layer" [ -n "$line_arg_playwright" ] || fail "missing OPENCODE_WEB_BUILD_PLAYWRIGHT arg declaration" [ -n "$line_arg_playwright_version" ] || fail "missing PLAYWRIGHT_VERSION arg declaration" [ -n "$line_playwright_layer" ] || fail "missing playwright layer" @@ -46,11 +52,13 @@ line_metadata_layer="$(grep -n '^RUN mkdir -p /opt /workspace "\${OPENCODE_WEB_Y [ -n "$line_wrangler_layer" ] || fail "missing wrangler layer" [ -n "$line_arg_wrapper_version" ] || fail "missing WRAPPER_VERSION arg declaration" [ -n "$line_metadata_layer" ] || fail "missing metadata layer" +[ -n "$line_node_assertion" ] || fail "missing early Node 22 assertion" +[ -n "$line_apt_layer" ] || fail "missing apt layer" -[ "$line_arg_npm_package" -lt "$line_npm_install" ] || fail "expected OPENCODE_NPM_PACKAGE arg before npm install layer" [ "$line_arg_playwright" -lt "$line_playwright_layer" ] || fail "expected OPENCODE_WEB_BUILD_PLAYWRIGHT arg before playwright layer" [ "$line_arg_playwright_version" -lt "$line_playwright_layer" ] || fail "expected PLAYWRIGHT_VERSION arg before playwright layer" [ "$line_arg_wrangler" -lt "$line_wrangler_layer" ] || fail "expected OPENCODE_WEB_BUILD_WRANGLER arg before wrangler layer" [ "$line_arg_wrapper_version" -lt "$line_metadata_layer" ] || fail "expected WRAPPER_VERSION arg before metadata layer" +[ "$line_node_assertion" -lt "$line_apt_layer" ] || fail "Node 22 assertion must precede apt/npm work" printf '%s\n' "PASS: Playwright Dockerfile runtime contract" diff --git a/tests/test_runtime_defaults.sh b/tests/test_runtime_defaults.sh new file mode 100644 index 0000000..094aaad --- /dev/null +++ b/tests/test_runtime_defaults.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck source=tests/test_helpers.sh +. "${ROOT_DIR}/tests/test_helpers.sh" + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT +FAKE_BIN="${TMP_DIR}/bin" +CONFIG_FILE="${TMP_DIR}/home/.opencode_web_yolo/config" +BUILD_LOG="${TMP_DIR}/docker-build.log" +WRAPPER_VERSION="$(tr -d '[:space:]' <"${ROOT_DIR}/VERSION")" + +setup_fake_docker "$FAKE_BIN" "$WRAPPER_VERSION" +export PATH="${FAKE_BIN}:${PATH}" +export HOME="${TMP_DIR}/home" +export OPENCODE_WEB_SKIP_UPDATE_CHECK=1 +export OPENCODE_WEB_SKIP_VERSION_CHECK=1 +export OPENCODE_SERVER_PASSWORD=secret +export FAKE_DOCKER_BUILD_LOG="$BUILD_LOG" +mkdir -p "${HOME}" + +"${ROOT_DIR}/.opencode_web_yolo.sh" config >/dev/null +assert_equals 600 "$(stat -c '%a' "${CONFIG_FILE}")" +if grep -Eq '^export ' "${CONFIG_FILE}"; then + fail "generated config must contain only commented overrides" +fi +assert_contains "$(cat "${CONFIG_FILE}")" "# export OPENCODE_WEB_AUTO_PULL=0" +assert_not_contains "$(cat "${CONFIG_FILE}")" "OPENCODE_WEB_BASE_IMAGE" +assert_not_contains "$(cat "${CONFIG_FILE}")" "OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION" + +set +e +overwrite_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" config 2>&1)" +overwrite_status=$? +set -e +assert_equals 1 "$overwrite_status" +assert_contains "$overwrite_output" "Refusing to overwrite" + +rm -f "${CONFIG_FILE}" +ln -s "${TMP_DIR}/not-created" "${CONFIG_FILE}" +set +e +symlink_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" config 2>&1)" +symlink_status=$? +set -e +assert_equals 1 "$symlink_status" +assert_contains "$symlink_output" "Refusing to overwrite" +rm -f "${CONFIG_FILE}" + +cat >"${CONFIG_FILE}" <<'EOF' +export OPENCODE_WEB_BASE_IMAGE=node:20-slim +export OPENCODE_WEB_NPM_PACKAGE=old-package +export OPENCODE_WEB_HOSTNAME=127.0.0.1 +export OPENCODE_WEB_YOLO_HOME=/legacy-home +export OPENCODE_WEB_YOLO_WORKDIR=/legacy-workdir +export OPENCODE_WEB_YOLO_CLEANUP=0 +export OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION=9.9.9 +export OPENCODE_WEB_BUILD_PLAYWRIGHT=0 +export OPENCODE_WEB_AUTO_PULL=0 +EOF +chmod 600 "${CONFIG_FILE}" + +export FAKE_IMAGE_NODE_VERSION=v20.11.1 +export FAKE_IMAGE_NODE_MAJOR=20 +unset OPENCODE_WEB_AUTO_PULL +: >"${BUILD_LOG}" +legacy_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" +assert_contains "$legacy_output" "hostname=0.0.0.0" +assert_contains "$legacy_output" "command=opencode serve --hostname 0.0.0.0" +assert_contains "$legacy_output" "runtime_env_home=/home/opencode" +assert_contains "$legacy_output" "-w /workspace" +assert_contains "$legacy_output" "Node runtime metadata mismatch" +assert_contains "$(cat "${BUILD_LOG}")" "--pull" +assert_not_contains "$(cat "${BUILD_LOG}")" "BASE_IMAGE" +assert_not_contains "$(cat "${BUILD_LOG}")" "OPENCODE_NPM_PACKAGE" +assert_not_contains "$(cat "${BUILD_LOG}")" "PLAYWRIGHT_VERSION=9.9.9" + +for metadata_case in missing non22 major-mismatch; do + : >"${BUILD_LOG}" + case "$metadata_case" in + missing) + export FAKE_IMAGE_NODE_VERSION=__missing__ FAKE_IMAGE_NODE_MAJOR=__missing__ + ;; + non22) + export FAKE_IMAGE_NODE_VERSION=v20.11.1 FAKE_IMAGE_NODE_MAJOR=20 + ;; + major-mismatch) + export FAKE_IMAGE_NODE_VERSION=v22.14.0 FAKE_IMAGE_NODE_MAJOR=20 + ;; + esac + output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" + assert_contains "$output" "Node runtime metadata mismatch" + assert_contains "$(cat "${BUILD_LOG}")" "--pull" +done + +for malformed_version in v22 v22.14 v22.x.0 22.14.0 v22.01.0 v22.14.0-extra; do + : >"${BUILD_LOG}" + export FAKE_IMAGE_NODE_VERSION="$malformed_version" FAKE_IMAGE_NODE_MAJOR=22 + output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" + assert_contains "$output" "Node runtime metadata mismatch" + assert_contains "$(cat "${BUILD_LOG}")" "--pull" +done + +unset FAKE_IMAGE_NODE_VERSION FAKE_IMAGE_NODE_MAJOR +export FAKE_IMAGE_MISSING=1 +: >"${BUILD_LOG}" +missing_image_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" +assert_contains "$missing_image_output" "image 'opencode_web_yolo:latest' is missing" +assert_contains "$(cat "${BUILD_LOG}")" "--pull" +unset FAKE_IMAGE_MISSING + +export FAKE_IMAGE_WRAPPER_VERSION=0.3.0 +: >"${BUILD_LOG}" +version_drift_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" +assert_contains "$version_drift_output" "wrapper version metadata mismatch" +assert_contains "$(cat "${BUILD_LOG}")" "--pull" +unset FAKE_IMAGE_WRAPPER_VERSION +: >"${BUILD_LOG}" +matching_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" +assert_not_contains "$matching_output" "Node runtime metadata mismatch" +if [ -s "${BUILD_LOG}" ]; then + fail "matching Node 22 metadata must allow image reuse" +fi +: >"${BUILD_LOG}" +persistent_matching_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" +assert_contains "$persistent_matching_output" "auto_pull=0" +if [ -s "${BUILD_LOG}" ]; then + fail "persistent OPENCODE_WEB_AUTO_PULL=0 must allow matching image reuse" +fi + +cat >"${CONFIG_FILE}" <<'EOF' +export OPENCODE_WEB_BUILD_NO_CACHE=0 +export OPENCODE_WEB_BUILD_PULL=0 +export OPENCODE_WEB_AUTO_PULL=1 +export OPENCODE_WEB_DRY_RUN=0 +export OPENCODE_WEB_RETENTION_DRY_RUN=0 +EOF +: >"${BUILD_LOG}" +marker_cli_output="$(OPENCODE_WEB_UPDATE_REEXECED=1 OPENCODE_WEB_DRY_RUN=1 OPENCODE_WEB_RETENTION_DRY_RUN=1 OPENCODE_WEB_BUILD_PULL=1 OPENCODE_WEB_BUILD_NO_CACHE=1 OPENCODE_WEB_AUTO_PULL=1 OPENCODE_WEB_EXPECTED_OPENCODE_VERSION=1.2.7 "${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull 2>&1)" +assert_contains "$marker_cli_output" "DRY RUN" +assert_contains "$marker_cli_output" "retention_dry_run=1" +assert_contains "$marker_cli_output" "auto_pull=0" +assert_contains "$marker_cli_output" "build_pull=0" +assert_not_contains "$marker_cli_output" "OpenCode version mismatch" +assert_contains "$(cat "${BUILD_LOG}")" "--no-cache" +assert_not_contains "$(cat "${BUILD_LOG}")" "--pull" + +export FAKE_IMAGE_NODE_VERSION=v20.11.1 +export FAKE_IMAGE_NODE_MAJOR=20 +: >"${BUILD_LOG}" +marker_compatibility_output="$(OPENCODE_WEB_UPDATE_REEXECED=1 OPENCODE_WEB_DRY_RUN=1 OPENCODE_WEB_RETENTION_DRY_RUN=1 OPENCODE_WEB_BUILD_PULL=0 OPENCODE_WEB_BUILD_NO_CACHE=1 OPENCODE_WEB_AUTO_PULL=0 "${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull 2>&1)" +assert_contains "$marker_compatibility_output" "DRY RUN" +assert_contains "$marker_compatibility_output" "retention_dry_run=1" +assert_contains "$marker_compatibility_output" "auto_pull=0" +assert_contains "$marker_compatibility_output" "build_pull=1" +assert_contains "$(cat "${BUILD_LOG}")" "--pull" +assert_contains "$(cat "${BUILD_LOG}")" "--no-cache" +unset FAKE_IMAGE_NODE_VERSION FAKE_IMAGE_NODE_MAJOR + +cat >"${CONFIG_FILE}" <<'EOF' +export OPENCODE_WEB_BUILD_PLAYWRIGHT=0 +export OPENCODE_WEB_BUILD_WRANGLER=0 +EOF +export OPENCODE_WEB_EXPECTED_OPENCODE_VERSION=1.2.7 +export OPENCODE_WEB_SKIP_VERSION_CHECK=0 +: >"${BUILD_LOG}" +opencode_drift_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" +assert_contains "$opencode_drift_output" "OpenCode version mismatch" +assert_not_contains "$(cat "${BUILD_LOG}")" "--pull" + +cat >"${CONFIG_FILE}" <<'EOF' +export OPENCODE_WEB_BUILD_PLAYWRIGHT=1 +export OPENCODE_WEB_BUILD_WRANGLER=1 +EOF +unset OPENCODE_WEB_EXPECTED_OPENCODE_VERSION +export OPENCODE_WEB_SKIP_VERSION_CHECK=1 +: >"${BUILD_LOG}" +feature_drift_output="$("${ROOT_DIR}/.opencode_web_yolo.sh" --no-pull --dry-run 2>&1)" +assert_contains "$feature_drift_output" "Playwright build mismatch" +assert_contains "$feature_drift_output" "Wrangler build mismatch" +assert_not_contains "$(cat "${BUILD_LOG}")" "--pull" + +printf '%s\n' "PASS: runtime-owned defaults and Node compatibility rebuilds" diff --git a/tests/test_self_update.sh b/tests/test_self_update.sh index 2d3e8f3..fc4a758 100644 --- a/tests/test_self_update.sh +++ b/tests/test_self_update.sh @@ -152,6 +152,10 @@ fi reset_install_home prepare_remote_release "${REMOTE_VERSION}" "updated" +mkdir -p "${HOME_DIR}/.opencode_web_yolo" +printf '%s\n' 'export OPENCODE_WEB_BASE_IMAGE=node:20-slim' >"${HOME_DIR}/.opencode_web_yolo/config" +printf '%s\n' 'export OPENCODE_WEB_AUTO_PULL=0' >>"${HOME_DIR}/.opencode_web_yolo/config" +chmod 600 "${HOME_DIR}/.opencode_web_yolo/config" chmod -x "${REMOTE_DIR}/.opencode_web_yolo.sh" chmod -x "${REMOTE_DIR}/.opencode_web_yolo_entrypoint.sh" chmod -x "${REMOTE_DIR}/install.sh" @@ -161,6 +165,7 @@ assert_contains "$output_update" "Updating wrapper from ${LOCAL_VERSION} to ${RE assert_contains "$output_update" "Update complete, re-executing wrapper." assert_contains "$output_update" "DRY RUN" assert_contains "$output_update" "run_detached=0" +assert_contains "$output_update" "auto_pull=0" assert_contains "$output_update" "--model local" assert_equals "${REMOTE_VERSION}" "$(tr -d '[:space:]' <"${INSTALL_HOME}/VERSION")" if ! grep -F -- "remote-updated" "${INSTALL_HOME}/README.md" >/dev/null 2>&1; then @@ -169,6 +174,7 @@ fi assert_file_executable "${INSTALL_HOME}/.opencode_web_yolo.sh" assert_file_executable "${INSTALL_HOME}/.opencode_web_yolo_entrypoint.sh" assert_file_executable "${INSTALL_HOME}/install.sh" +assert_contains "$(cat "${HOME_DIR}/.opencode_web_yolo/config")" "OPENCODE_WEB_BASE_IMAGE=node:20-slim" update_calls="$(cat "${CURL_LOG}")" assert_contains "$update_calls" "/VERSION" assert_contains "$update_calls" "/archive/refs/heads/main.tar.gz"