From 7769f32bcb29faf9e578f84151764662866511ab Mon Sep 17 00:00:00 2001 From: laurenceputra Date: Sun, 20 Sep 2026 06:57:07 +0000 Subject: [PATCH] feat: configure startup vacuum timeout --- .opencode_web_yolo.sh | 5 +++ .opencode_web_yolo_config.sh | 3 ++ .opencode_web_yolo_entrypoint.sh | 20 ++++++++++-- CHANGELOG.md | 4 +++ README.md | 11 +++++-- TECHNICAL.md | 4 +-- VERSION | 2 +- .../references/test-acceptance-matrix.md | 2 +- skills/opencode-web-runtime/SKILL.md | 2 +- .../references/runtime-checklist.md | 2 +- tests/test_dry_run.sh | 18 +++++++++++ tests/test_health.sh | 1 + tests/test_runtime_defaults.sh | 3 ++ tests/test_startup_vacuum.sh | 31 +++++++++++++++++++ 14 files changed, 96 insertions(+), 12 deletions(-) diff --git a/.opencode_web_yolo.sh b/.opencode_web_yolo.sh index e96cc81..2b32479 100755 --- a/.opencode_web_yolo.sh +++ b/.opencode_web_yolo.sh @@ -571,6 +571,7 @@ write_default_config() { # export OPENCODE_WEB_RESTART_POLICY=unless-stopped # export OPENCODE_WEB_RUN_DETACHED=1 # export OPENCODE_WEB_AUTO_PULL=0 +# export OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=300 # 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 @@ -616,6 +617,7 @@ show_health() { printf '%s\n' " restart_policy=${OPENCODE_WEB_RESTART_POLICY}" printf '%s\n' " run_detached=${OPENCODE_WEB_RUN_DETACHED}" printf '%s\n' " auto_pull=${OPENCODE_WEB_AUTO_PULL}" + printf '%s\n' " startup_vacuum_term_timeout_seconds=${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" printf '%s\n' " build_pull=${OPENCODE_WEB_BUILD_PULL}" printf '%s\n' " build_playwright=${OPENCODE_WEB_BUILD_PLAYWRIGHT}" printf '%s\n' " build_wrangler=${OPENCODE_WEB_BUILD_WRANGLER}" @@ -1045,6 +1047,7 @@ main() { OPENCODE_WEB_SKIP_UPDATE_CHECK="$(normalize_bool "${OPENCODE_WEB_SKIP_UPDATE_CHECK}")" OPENCODE_WEB_SKIP_VERSION_CHECK="$(normalize_bool "${OPENCODE_WEB_SKIP_VERSION_CHECK}")" OPENCODE_WEB_RETENTION_DRY_RUN="$(normalize_bool "${OPENCODE_WEB_RETENTION_DRY_RUN}")" + validate_positive_integer OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS "${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" validate_retention_days if [ "$OPENCODE_WEB_RETENTION_DAYS" != "0" ]; then validate_positive_integer OPENCODE_WEB_RETENTION_POLL_SECONDS "${OPENCODE_WEB_RETENTION_POLL_SECONDS}" @@ -1114,6 +1117,7 @@ main() { -e "OPENCODE_WEB_RETENTION_POLL_SECONDS=${OPENCODE_WEB_RETENTION_POLL_SECONDS}" -e "OPENCODE_WEB_RETENTION_FETCH_TIMEOUT_MS=${OPENCODE_WEB_RETENTION_FETCH_TIMEOUT_MS}" -e "OPENCODE_WEB_RETENTION_VERIFY_TIMEOUT_MS=${OPENCODE_WEB_RETENTION_VERIFY_TIMEOUT_MS}" + -e "OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" -e "HOME=${runtime_home}" -e "XDG_CONFIG_HOME=${runtime_xdg_config}" -e "XDG_DATA_HOME=${runtime_xdg_data}" @@ -1219,6 +1223,7 @@ main() { printf '%s\n' "restart_policy=${OPENCODE_WEB_RESTART_POLICY}" printf '%s\n' "run_detached=${OPENCODE_WEB_RUN_DETACHED}" printf '%s\n' "auto_pull=${OPENCODE_WEB_AUTO_PULL}" + printf '%s\n' "startup_vacuum_term_timeout_seconds=${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" printf '%s\n' "build_pull=${OPENCODE_WEB_BUILD_PULL}" printf '%s\n' "build_playwright=${OPENCODE_WEB_BUILD_PLAYWRIGHT}" printf '%s\n' "build_wrangler=${OPENCODE_WEB_BUILD_WRANGLER}" diff --git a/.opencode_web_yolo_config.sh b/.opencode_web_yolo_config.sh index 32843ca..557ead7 100755 --- a/.opencode_web_yolo_config.sh +++ b/.opencode_web_yolo_config.sh @@ -41,6 +41,9 @@ _opencode_web_retention_dry_run_env="${OPENCODE_WEB_RETENTION_DRY_RUN-}" : "${OPENCODE_WEB_RUN_DETACHED:=1}" : "${OPENCODE_WEB_DRY_RUN:=0}" : "${OPENCODE_WEB_VERBOSE:=0}" +if [ "${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS+x}" != x ]; then + OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=300 +fi : "${OPENCODE_WEB_YOLO_REPO:=laurenceputra/opencode_web_yolo}" : "${OPENCODE_WEB_YOLO_BRANCH:=main}" : "${OPENCODE_SERVER_USERNAME:=opencode}" diff --git a/.opencode_web_yolo_entrypoint.sh b/.opencode_web_yolo_entrypoint.sh index e0e71b5..11ae7d2 100755 --- a/.opencode_web_yolo_entrypoint.sh +++ b/.opencode_web_yolo_entrypoint.sh @@ -10,8 +10,8 @@ XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${OPENCODE_WEB_YOLO_HOME}/.config}" XDG_DATA_HOME="${XDG_DATA_HOME:-${OPENCODE_WEB_YOLO_HOME}/.local/share}" XDG_STATE_HOME="${XDG_STATE_HOME:-${XDG_DATA_HOME}/opencode/state}" OPENCODE_WEB_RETENTION_DAYS="${OPENCODE_WEB_RETENTION_DAYS-0}" +OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS="${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS-300}" STARTUP_VACUUM_BUSY_TIMEOUT_MS=5000 -STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=300 STARTUP_VACUUM_KILL_AFTER_SECONDS=5 case "$OPENCODE_WEB_RETENTION_DAYS" in @@ -25,6 +25,20 @@ while [ "${OPENCODE_WEB_RETENTION_DAYS#0}" != "$OPENCODE_WEB_RETENTION_DAYS" ]; done OPENCODE_WEB_RETENTION_DAYS="${OPENCODE_WEB_RETENTION_DAYS:-0}" +case "$OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS" in + ''|0*|*[!0-9]*) + printf '%s\n' "[opencode_web_yolo] ERROR: OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS must be a positive integer." >&2 + exit 1 + ;; +esac +if [ "${#OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" -gt 10 ] || { + [ "${#OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" -eq 10 ] && + (( OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS > 2147483647 )) +}; then + printf '%s\n' "[opencode_web_yolo] ERROR: OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS is outside the supported positive integer range." >&2 + exit 1 +fi + if [ -z "${OPENCODE_SERVER_PASSWORD:-}" ]; then printf '%s\n' "[opencode_web_yolo] ERROR: OPENCODE_SERVER_PASSWORD must be set and non-empty." >&2 exit 1 @@ -88,7 +102,7 @@ if [ -f "${opencode_database}" ]; then printf '%s\n' "[opencode_web_yolo] VACUUM: compacting OpenCode database at ${opencode_database}." if gosu "${runtime_user}" timeout \ --kill-after="${STARTUP_VACUUM_KILL_AFTER_SECONDS}" \ - "${STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" \ + "${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}" \ sqlite3 \ -cmd ".timeout ${STARTUP_VACUUM_BUSY_TIMEOUT_MS}" \ "${opencode_database}" 'VACUUM;'; then @@ -96,7 +110,7 @@ if [ -f "${opencode_database}" ]; then else vacuum_status=$? if [ "$vacuum_status" -eq 124 ] || [ "$vacuum_status" -eq 137 ]; then - printf '%s\n' "[opencode_web_yolo] WARNING: startup VACUUM timed out after the ${STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}-second TERM deadline (KILL escalation after ${STARTUP_VACUUM_KILL_AFTER_SECONDS} additional seconds); continuing startup." >&2 + printf '%s\n' "[opencode_web_yolo] WARNING: startup VACUUM timed out after the ${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS}-second TERM deadline (KILL escalation after ${STARTUP_VACUUM_KILL_AFTER_SECONDS} additional seconds); continuing startup." >&2 else printf '%s\n' "[opencode_web_yolo] WARNING: startup VACUUM failed for ${opencode_database}; continuing startup." >&2 fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a03671..78c0c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project are documented here. +## [0.5.0] - 2026-09-20 + +- Added persistent `OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` configuration, defaulting to 300 seconds, with bounded positive-integer validation in the wrapper and entrypoint, Docker propagation, and health/dry-run visibility. SQLite's 5000 ms busy timeout and fixed 5-second KILL escalation remain unchanged. + ## [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. diff --git a/README.md b/README.md index e424626..7c644d6 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Defaults: - Launch mode: background (`-d`) - Pull behavior: pull-on-start enabled - Session retention: disabled by default (`OPENCODE_WEB_RETENTION_DAYS=0`) +- Startup VACUUM TERM timeout: `300` seconds (KILL escalation remains fixed at 5 seconds) ## Authentication Requirement @@ -116,6 +117,7 @@ Operator-facing settings: | `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` | 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_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` | `300` | Persistent positive integer timeout for startup SQLite VACUUM's GNU `timeout` TERM deadline; values above `2147483647` are rejected. The SQLite busy timeout remains 5000 ms and KILL escalation remains fixed at 5 seconds. | | `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. | @@ -164,11 +166,14 @@ The wrapper also pins runtime env (`HOME`, `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, ` On every container startup, after the mapped-user ownership and XDG setup, the entrypoint checks `$XDG_DATA_HOME/opencode/opencode.db`. If that database exists, it runs `VACUUM;` with `sqlite3` as -the mapped runtime user, waiting up to 5000 ms for a lock. GNU `timeout` sends TERM after 300 -seconds and sends KILL 5 seconds later if VACUUM is still running. A missing database is skipped +the mapped runtime user, waiting up to 5000 ms for a lock. GNU `timeout` sends TERM after the +configured `OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` deadline (300 seconds by default) +and sends KILL 5 seconds later if VACUUM is still running. A missing database is skipped without creating one. Vacuum can add startup latency and temporarily require additional disk space while SQLite rewrites the database. If it cannot vacuum because of a lock, permissions, corruption, -disk space, timeout, or another error, startup prints a warning and continues. +disk space, or another non-timeout error, startup prints a concise warning and continues. Timeout- +expiry warnings include the effective TERM deadline. `health` and `--dry-run` show the effective TERM +timeout, and the wrapper passes it into Docker. Startup VACUUM is separate from weekly retention. The retention worker remains an authenticated OpenCode API worker: it does not use raw SQL or manually modify SQLite WAL, SHM, or journal diff --git a/TECHNICAL.md b/TECHNICAL.md index 26e415f..7b4e284 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -111,7 +111,7 @@ Entrypoint behavior: - avoids recursive ownership operations across read-only mount boundaries. - installs passwordless sudo policy for mapped user. - executes command via `gosu`. -- after ownership and HOME/XDG setup, checks `${XDG_DATA_HOME}/opencode/opencode.db`; when present, runs `VACUUM;` through `sqlite3` via `gosu` as the mapped user with a 5000 ms busy timeout. GNU `timeout` sends TERM after 300 seconds and KILL 5 seconds later if the command remains alive. Missing databases are skipped without creation. Vacuum failures or timeout expiry warn to stderr and do not block either direct or retention-supervised OpenCode launch. This startup maintenance is separate from retention, whose worker never uses raw SQL or mutates SQLite WAL/SHM files. +- after ownership and HOME/XDG setup, checks `${XDG_DATA_HOME}/opencode/opencode.db`; when present, runs `VACUUM;` through `sqlite3` via `gosu` as the mapped user with a 5000 ms busy timeout. `OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` is a persistent positive integer bounded to `2147483647`, defaults to 300, and controls GNU `timeout`'s TERM deadline; KILL escalation remains fixed at 5 seconds. Missing databases are skipped without creation. Ordinary vacuum failures warn concisely, while timeout-expiry warnings include the effective TERM deadline; neither blocks direct or retention-supervised OpenCode launch. This startup maintenance is separate from retention, whose worker never uses raw SQL or mutates SQLite WAL/SHM files. - when retention is enabled, starts OpenCode, waits for authenticated `/global/health`, and supervises a mapped-user scheduler; TERM/INT are forwarded and the app exit status is returned. - Docker starts `tini -s -g` so orphaned descendants are reaped and TERM/INT are forwarded to the child process group. The supervisor preserves the received signal when forwarding it to the app. - does not inject unsupported OpenCode CLI flags for instruction loading. @@ -211,5 +211,5 @@ Tests and CI assert: - retention configuration, marker path, API schedule, and dry-run state. - retention API compatibility, active-session skipping, serial deletion, marker retry semantics, and supervisor signal/exit behavior. - Docker image build and runtime binary presence (`gh`, `git`, `ssh`, `sqlite3`), including a successful `opencode serve --help` check. -- startup VACUUM behavior for existing and missing databases, custom XDG data paths, mapped-user invocation, SQLite timeout plus TERM/KILL escalation, warning-only failures, and continued application execution. +- startup VACUUM behavior for existing and missing databases, custom XDG data paths, mapped-user invocation, the configurable bounded TERM timeout plus fixed 5-second KILL escalation, warning-only failures, and continued application execution. - `VERSION` semver format and runtime-file/version drift guard. diff --git a/VERSION b/VERSION index 1d0ba9e..8f0916f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.0 +0.5.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 17f1afa..4963cd2 100644 --- a/skills/opencode-web-quality-docs/references/test-acceptance-matrix.md +++ b/skills/opencode-web-quality-docs/references/test-acceptance-matrix.md @@ -29,7 +29,7 @@ Use this matrix when authoring tests under `tests/`. - Persistence assertions verify state files are written to mounted host path, not only any in-container path. - Docs contract check ensures Apache stream endpoints include `/event` and `/global/event`, and excludes stale `/session/event`. - Health/diagnostics command reports key prerequisites and failures clearly. -- Startup VACUUM tests cover existing and missing databases, custom XDG data paths, mapped-user execution, the 5000 ms SQLite busy timeout, TERM at 300 seconds with KILL escalation 5 seconds later, warning-and-continue failures, and no manual WAL/SHM/journal sidecar mutation. +- Startup VACUUM tests cover existing and missing databases, custom XDG data paths, mapped-user execution, the 5000 ms SQLite busy timeout, the configurable positive `OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` TERM deadline bounded to `2147483647` (default 300) with fixed KILL escalation 5 seconds later, concise ordinary-failure warnings, timeout-expiry warnings containing the effective TERM deadline, and no manual WAL/SHM/journal sidecar mutation. - Retention accepts both flag forms, honors config/flag precedence, rejects invalid values, and propagates dry-run state. - Retention tests cover cutoff/pagination/root filtering, active-session skips, serial deletion, API incompatibility/failure, marker success/failure/retry, and scheduler signal/exit behavior. - Retention tests cover complete cross-directory hierarchy mapping, active descendants/unknown IDs, stale root refreshes, status changes before deletion, DELETE-true-but-still-present direct-GET verification, request timeout, supported-version gating, future markers, invalid poll intervals, exact query/cursor use, and malformed responses. diff --git a/skills/opencode-web-runtime/SKILL.md b/skills/opencode-web-runtime/SKILL.md index d598903..b59f71d 100644 --- a/skills/opencode-web-runtime/SKILL.md +++ b/skills/opencode-web-runtime/SKILL.md @@ -45,7 +45,7 @@ Enforce these contracts on every runtime change: 4. Emit identical run args for normal run and dry-run previews. 5. Keep diagnostics independent of container startup. 6. In entrypoint, map UID/GID, ensure writable runtime dirs, avoid recursive chown on read-only mounts, then exec via `gosu`. -7. After ownership and HOME/XDG exports, inspect `${XDG_DATA_HOME}/opencode/opencode.db`. If present, run startup `VACUUM;` via `sqlite3` and `gosu` as the mapped user with a 5000 ms busy timeout; GNU `timeout` sends TERM after 300 seconds and KILL 5 seconds later if needed. Skip missing databases without creating them and warn/continue on failures or timeout expiry. This is separate from retention's no-raw-SQL worker guarantee. +7. After ownership and HOME/XDG exports, inspect `${XDG_DATA_HOME}/opencode/opencode.db`. If present, run startup `VACUUM;` via `sqlite3` and `gosu` as the mapped user with a 5000 ms busy timeout; the persistent `OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` setting defaults to 300, is a positive integer bounded to `2147483647`, and controls GNU `timeout`'s TERM deadline. KILL escalation remains fixed at 5 seconds. Skip missing databases without creating them; ordinary failures warn concisely, while timeout-expiry warnings include the effective TERM deadline. All failures warn/continue without blocking launch. This is separate from retention's no-raw-SQL worker guarantee. # Retention lifecycle diff --git a/skills/opencode-web-runtime/references/runtime-checklist.md b/skills/opencode-web-runtime/references/runtime-checklist.md index b30c60f..bc42be9 100644 --- a/skills/opencode-web-runtime/references/runtime-checklist.md +++ b/skills/opencode-web-runtime/references/runtime-checklist.md @@ -29,7 +29,7 @@ Use this checklist for runtime changes in `.opencode_web_yolo.sh`, `.opencode_we - Avoid recursive ownership operations on paths that can contain read-only mounts. - Use `gosu` handoff for final command execution. - Run `opencode serve` with configured host and port. -- Provide `sqlite3` and Debian coreutils `timeout` in the image and, after mapped-user ownership plus HOME/XDG exports, best-effort VACUUM an existing `${XDG_DATA_HOME}/opencode/opencode.db` as the mapped user via `gosu` with a 5000 ms busy timeout. GNU `timeout` must send TERM after 300 seconds and KILL 5 seconds later if needed. Missing databases must not be created; failures or timeout expiry warn and do not block direct or retention-supervised launch. +- Provide `sqlite3` and Debian coreutils `timeout` in the image and, after mapped-user ownership plus HOME/XDG exports, best-effort VACUUM an existing `${XDG_DATA_HOME}/opencode/opencode.db` as the mapped user via `gosu` with a 5000 ms busy timeout. The persistent `OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS` setting defaults to 300, must be a positive integer bounded to `2147483647`, and controls GNU `timeout`'s TERM deadline; KILL escalation stays fixed at 5 seconds. Missing databases must not be created; ordinary failures warn concisely, while timeout-expiry warnings include the effective TERM deadline. Failures do not block direct or retention-supervised launch. - Preserve provider/auth state across restart by mounting host OpenCode data directory. - Keep Playwright opt-in: `OPENCODE_WEB_BUILD_PLAYWRIGHT=1` in the persistent config is durable, while `--playwright` is one-shot. - Retention is opt-in with non-negative `OPENCODE_WEB_RETENTION_DAYS`; its scheduler starts only after authenticated health, runs as the mapped user, and persists a success marker under `XDG_STATE_HOME`. diff --git a/tests/test_dry_run.sh b/tests/test_dry_run.sh index 9689e8c..fe568fe 100755 --- a/tests/test_dry_run.sh +++ b/tests/test_dry_run.sh @@ -34,6 +34,7 @@ assert_contains "$output" "container_name=opencode_web_yolo" assert_contains "$output" "restart_policy=unless-stopped" assert_contains "$output" "run_detached=1" assert_contains "$output" "auto_pull=1" +assert_contains "$output" "startup_vacuum_term_timeout_seconds=300" assert_contains "$output" "build_pull=1" assert_contains "$output" "build_playwright=0" assert_contains "$output" "opencode_config_dir=${HOME}/.config/opencode" @@ -50,6 +51,7 @@ assert_contains "$output" "-e HOME=/home/opencode" assert_contains "$output" "-e XDG_CONFIG_HOME=/home/opencode/.config" assert_contains "$output" "-e XDG_DATA_HOME=/home/opencode/.local/share" assert_contains "$output" "-e XDG_STATE_HOME=/home/opencode/.local/share/opencode/state" +assert_contains "$output" "-e OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=300" assert_contains "$output" ".config/opencode" assert_contains "$output" ".local/share/opencode" assert_contains "$output" "--model local" @@ -68,6 +70,22 @@ for truthy_value in true yes on; do done unset OPENCODE_WEB_BUILD_PLAYWRIGHT +for invalid_timeout in '' 0 01 2147483648 invalid; do + set +e + invalid_output="$(OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS="$invalid_timeout" "${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" + invalid_status=$? + set -e + assert_equals 1 "$invalid_status" + assert_contains "$invalid_output" "OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS" +done + +custom_output="$(OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=42 "${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" +assert_contains "$custom_output" "startup_vacuum_term_timeout_seconds=42" +assert_contains "$custom_output" "-e OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=42" + +max_timeout_output="$(OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=2147483647 "${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" +assert_contains "$max_timeout_output" "startup_vacuum_term_timeout_seconds=2147483647" + mkdir -p "${HOME}/.opencode_web_yolo" printf '%s\n' 'export OPENCODE_WEB_BUILD_PLAYWRIGHT=true' >"${HOME}/.opencode_web_yolo/config" output_durable="$("${ROOT_DIR}/.opencode_web_yolo.sh" --dry-run 2>&1)" diff --git a/tests/test_health.sh b/tests/test_health.sh index 0b61bf9..c694e61 100755 --- a/tests/test_health.sh +++ b/tests/test_health.sh @@ -28,6 +28,7 @@ assert_contains "$output" "container_name=opencode_web_yolo" assert_contains "$output" "restart_policy=unless-stopped" assert_contains "$output" "run_detached=1" assert_contains "$output" "auto_pull=1" +assert_contains "$output" "startup_vacuum_term_timeout_seconds=300" assert_contains "$output" "build_pull=0" assert_contains "$output" "build_playwright=0" assert_contains "$output" "build_wrangler=0" diff --git a/tests/test_runtime_defaults.sh b/tests/test_runtime_defaults.sh index 094aaad..707c09b 100644 --- a/tests/test_runtime_defaults.sh +++ b/tests/test_runtime_defaults.sh @@ -27,6 +27,7 @@ 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_contains "$(cat "${CONFIG_FILE}")" "# export OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=300" assert_not_contains "$(cat "${CONFIG_FILE}")" "OPENCODE_WEB_BASE_IMAGE" assert_not_contains "$(cat "${CONFIG_FILE}")" "OPENCODE_WEB_EXPECTED_PLAYWRIGHT_VERSION" @@ -57,6 +58,7 @@ 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 +export OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=42 EOF chmod 600 "${CONFIG_FILE}" @@ -68,6 +70,7 @@ 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" "startup_vacuum_term_timeout_seconds=42" assert_contains "$legacy_output" "-w /workspace" assert_contains "$legacy_output" "Node runtime metadata mismatch" assert_contains "$(cat "${BUILD_LOG}")" "--pull" diff --git a/tests/test_startup_vacuum.sh b/tests/test_startup_vacuum.sh index 1e79f42..fa766d5 100644 --- a/tests/test_startup_vacuum.sh +++ b/tests/test_startup_vacuum.sh @@ -96,6 +96,7 @@ run_entrypoint() { XDG_DATA_HOME="$data_home" \ XDG_STATE_HOME="${data_home}/opencode/state" \ OPENCODE_WEB_RETENTION_DAYS=0 \ + OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS="${OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS-300}" \ "$ENTRYPOINT_COPY" "$@" } @@ -109,6 +110,15 @@ assert_contains "$existing_output" "VACUUM: compacting OpenCode database at ${ex assert_contains "$(cat "$GOSU_LOG")" "user=${LOCAL_USER} command=timeout --kill-after=5 300 sqlite3 -cmd .timeout 5000 ${existing_db} VACUUM;" assert_contains "$(cat "$SQLITE_LOG")" "-cmd .timeout 5000 ${existing_db} VACUUM;" +custom_home="${TMP_DIR}/custom-timeout-home" +custom_data="${TMP_DIR}/custom-timeout-data" +custom_db="${custom_data}/opencode/opencode.db" +mkdir -p "$(dirname "$custom_db")" +: >"$custom_db" +custom_output="$(OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=42 run_entrypoint "$custom_home" "$custom_data" true 2>&1)" +assert_contains "$custom_output" "VACUUM: compacting OpenCode database at ${custom_db}" +assert_contains "$(cat "$GOSU_LOG")" "user=${LOCAL_USER} command=timeout --kill-after=5 42 sqlite3 -cmd .timeout 5000 ${custom_db} VACUUM;" + missing_home="${TMP_DIR}/missing-home" missing_data="${TMP_DIR}/missing-data" missing_output="$(run_entrypoint "$missing_home" "$missing_data" touch "${TMP_DIR}/app-ran" 2>&1)" @@ -153,4 +163,25 @@ if [ ! -e "${TMP_DIR}/app-ran-after-timeout" ]; then fail "application did not continue after startup VACUUM timeout" fi +custom_timeout_home="${TMP_DIR}/custom-timeout-warning-home" +custom_timeout_data="${TMP_DIR}/custom-timeout-warning-data" +custom_timeout_db="${custom_timeout_data}/opencode/opencode.db" +mkdir -p "$(dirname "$custom_timeout_db")" +: >"$custom_timeout_db" +set +e +custom_timeout_output="$(OPENCODE_WEB_TEST_TIMEOUT_EXIT=124 OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS=42 run_entrypoint "$custom_timeout_home" "$custom_timeout_data" true 2>&1)" +custom_timeout_status=$? +set -e +assert_equals "0" "$custom_timeout_status" +assert_contains "$custom_timeout_output" "WARNING: startup VACUUM timed out after the 42-second TERM deadline (KILL escalation after 5 additional seconds); continuing startup." + +for invalid_timeout in '' 0 01 2147483648 invalid; do + set +e + invalid_output="$(OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS="$invalid_timeout" run_entrypoint "${TMP_DIR}/invalid-home-${invalid_timeout}" "${TMP_DIR}/invalid-data-${invalid_timeout}" true 2>&1)" + invalid_status=$? + set -e + assert_equals 1 "$invalid_status" + assert_contains "$invalid_output" "OPENCODE_WEB_STARTUP_VACUUM_TERM_TIMEOUT_SECONDS" +done + printf '%s\n' "PASS: startup VACUUM behavior"