From d833c1317d752ee435061980fc18b671d6816f2a Mon Sep 17 00:00:00 2001 From: JR Morgan Date: Fri, 9 Oct 2026 19:37:50 -0500 Subject: [PATCH 1/5] Fix development secure-link origins and configured gateway startup - Use current Brev secure-link domains with explicit origin overrides. - Retain legacy origins and token authentication. - Restart configured gateways when bootstrap is rerun. - Add regression coverage and CI checks; preserve the OpenClaw release pin. --- .github/workflows/bootstrap-tests.yml | 20 ++++++++ README.md | 28 ++++++---- configure.sh | 13 +++-- launch.sh | 21 +++++--- tests/bootstrap-regression.sh | 73 +++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/bootstrap-tests.yml create mode 100644 tests/bootstrap-regression.sh diff --git a/.github/workflows/bootstrap-tests.yml b/.github/workflows/bootstrap-tests.yml new file mode 100644 index 0000000..eb12c3d --- /dev/null +++ b/.github/workflows/bootstrap-tests.yml @@ -0,0 +1,20 @@ +name: Bootstrap regression tests + +on: + pull_request: + push: + branches: [main, codex/openclaw-development-recovery] + +permissions: + contents: read + +jobs: + regression: + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - name: Validate shell syntax + run: bash -n launch.sh configure.sh tests/bootstrap-regression.sh + - name: Test bootstrap URL and restart behavior + run: bash tests/bootstrap-regression.sh diff --git a/README.md b/README.md index 7d3cc41..5398d9f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ To generate an API key, visit [build.nvidia.com/settings/api-keys](https://build ======================== URL: - https://openclaw-xxxxxxxx.apps.run.brev.nvidia.com/#token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + https://openclaw-xxxxxxxx.gobrev.dev/#token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx API Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -47,10 +47,10 @@ To generate an API key, visit [build.nvidia.com/settings/api-keys](https://build brev-xxxxxxxx Origin: - https://openclaw-xxxxxxxx.apps.run.brev.nvidia.com + https://openclaw-xxxxxxxx.gobrev.dev ``` -1. Copy the full URL (looks like `https://openclaw-xxxxxxxx.apps.run.brev.nvidia.com/#token=xxx...`) and open it in a new tab. +1. Copy the full URL (looks like `https://openclaw-xxxxxxxx.gobrev.dev/#token=xxx...`) and open it in a new tab. 1. Click the "Connect" button to open the OpenClaw chat interface. You may need to click this button more than once. 1. Start chatting with the agent. @@ -98,17 +98,27 @@ The launchable is split into two stages: On hosts named like `brev-`, the launchable derives: ```text -OpenClaw: https://openclaw-.apps.run.brev.nvidia.com/chat?session=main -code-server: https://code-server0-.apps.run.brev.nvidia.com +OpenClaw: https://openclaw-.gobrev.dev/chat?session=main +code-server: https://code-server-.gobrev.dev ``` If the hostname does not match the Brev naming pattern, it falls back to: ```text -OpenClaw: http://localhost:3000/chat?session=main +OpenClaw: http://localhost:18789/chat?session=main code-server: http://localhost:13337 ``` +The Brev console (or `brev ports ls --json`) is authoritative for +secure-link URLs. For custom service names or domains, set `OPENCLAW_ORIGIN` +and `CODE_SERVER_ORIGIN` to the exact HTTPS origins before running the scripts. +The Control UI retains the previous Brev and legacy LaunchPad origins alongside +the current origin; it does not enable wildcard origins or disable token auth. + +After configuration, re-running `launch.sh` restarts the gateway using the saved +credentials instead of displaying the first-run configuration prompt. This is +a bootstrap re-run guarantee, not an automatic gateway restart after a VM reboot. + ### Re-run Safety The bootstrap is designed to be safe to run multiple times: @@ -160,7 +170,7 @@ OpenClaw Gateway Started ======================== URL: -https://openclaw-.apps.run.brev.nvidia.com/chat?session=main +https://openclaw-.gobrev.dev/chat?session=main API Token: @@ -169,10 +179,10 @@ Hostname: brev- Origin: -https://openclaw-.apps.run.brev.nvidia.com +https://openclaw-.gobrev.dev code-server: -https://code-server0-.apps.run.brev.nvidia.com +https://code-server-.gobrev.dev ``` ### Logs and State diff --git a/configure.sh b/configure.sh index 5a9c40d..7cf2495 100755 --- a/configure.sh +++ b/configure.sh @@ -214,18 +214,22 @@ start_auto_approve_loop() { derive_openclaw_origin() { local host_name env_id + if [[ -n "${OPENCLAW_ORIGIN:-}" ]]; then + printf '%s\n' "${OPENCLAW_ORIGIN%/}" + return + fi host_name="$(hostname 2>/dev/null || true)" env_id="$(printf '%s\n' "$host_name" | sed -E 's/^brev-([[:alnum:]]+)$/\1/')" if [[ -n "$env_id" && "$env_id" != "$host_name" ]]; then - printf 'https://openclaw-%s.apps.run.brev.nvidia.com\n' "$env_id" + printf 'https://openclaw-%s.gobrev.dev\n' "$env_id" else - printf 'http://localhost:3000\n' + printf 'http://localhost:18789\n' fi } configure_control_ui_origin() { - local origin legacy_origin env_id host_name + local origin legacy_origin previous_origin env_id host_name local origins_json origin="$(derive_openclaw_origin)" @@ -235,8 +239,9 @@ configure_control_ui_origin() { # Include the legacy Launchpad-hosted Skybridge origin if [[ -n "$env_id" && "$env_id" != "$host_name" ]]; then legacy_origin="https://openclaw-${env_id}.stg.apps.launchpad.nvidia.com" + previous_origin="https://openclaw-${env_id}.apps.run.brev.nvidia.com" origins_json="$(cat < Date: Fri, 9 Oct 2026 20:28:06 -0500 Subject: [PATCH 2/5] Target stable OpenClaw 2026.9.9 with compatible bootstrap - Upgrade the default release pin and install a supported Node 24 LTS runtime. - Preserve private config backups and apply safe upstream schema migrations. - Support explicit reverse-proxy IP attribution without changing token authentication. - Expand regression coverage for runtime bounds, migration safety, and proxy restrictions. --- README.md | 12 +++--- configure.sh | 34 +++++++++++++++- launch.sh | 29 ++++++++----- tests/bootstrap-regression.sh | 77 ++++++++++++++++++++++++++++++++++- 4 files changed, 135 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 5398d9f..615b41f 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ The launchable is split into two stages: `launch.sh`: -1. Ensures Node.js 22 or newer is installed. -2. Installs the pinned OpenClaw npm package version `2026.3.13`, which corresponds to GitHub release [`v2026.3.13-1`](https://github.com/openclaw/openclaw/releases/tag/v2026.3.13-1). +1. Ensures a supported Node.js runtime is installed (`>=24.16.0 <25` or `>=26.1.0`); installs Node.js 24 LTS when needed. +2. Installs the pinned OpenClaw npm package version `2026.9.9`, which corresponds to the stable GitHub release [`v2026.9.9`](https://github.com/openclaw/openclaw/releases/tag/v2026.9.9). 3. Verifies the `openclaw` CLI is available. 4. Clones or refreshes `https://github.com/liveaverage/launch-openclaw.git` into `~/launch-openclaw` by default. 5. Installs `code-server`, the custom NV Theme, and the `fabiospampinato.vscode-terminals` extension. @@ -125,6 +125,8 @@ The bootstrap is designed to be safe to run multiple times: - It skips Node installation when a compatible version is already installed. - It keeps OpenClaw pinned to the npm package version resolved from `OPENCLAW_RELEASE_TAG` and reinstalls if another version is found on PATH. +- OpenClaw 2026.9.9 requires explicit reverse-proxy attribution. Set `OPENCLAW_TRUSTED_PROXY_IPS` to a JSON array of verified Brev proxy IPs (for example, `'["100.73.87.46"]'` on the tested Development route). Proxy addresses are deployment-specific; do not assume this example applies to another region or route. Only individual IPs are accepted, not CIDRs or wildcards. Ensure the proxy safely overwrites/rebuilds forwarded headers. Gateway token authentication remains enabled; this setting does not enable proxy-only authentication. +- Before restarting an existing gateway, it validates the config and uses safe, non-interactive upstream Doctor migrations when required, retaining a private rollback copy. If Doctor cannot repair it (for example, an obsolete gateway lock blocks migration), bootstrap fails with the backup location rather than starting an incompatible gateway. Inspect and resolve the reported stale process/lock before retrying; do not remove locks belonging to a running gateway. - It refreshes the local `~/launch-openclaw` checkout if it already exists. - It skips the first-run configure terminal after both `~/.openclaw/.env` and `~/.openclaw/openclaw.json` exist. - It reuses a running gateway if a previously started process is still alive. @@ -133,19 +135,19 @@ The bootstrap is designed to be safe to run multiple times: Target a GitHub release tag: ```bash -OPENCLAW_RELEASE_TAG=v2026.3.24 ./launch.sh +OPENCLAW_RELEASE_TAG=v2026.9.9 ./launch.sh ``` For piped execution: ```bash -curl -fsSL https://raw.githubusercontent.com/liveaverage/launch-openclaw/refs/heads/main/launch.sh | OPENCLAW_RELEASE_TAG=v2026.3.24 bash +curl -fsSL https://raw.githubusercontent.com/liveaverage/launch-openclaw/refs/heads/main/launch.sh | OPENCLAW_RELEASE_TAG=v2026.9.9 bash ``` Override the npm package version directly if needed: ```bash -OPENCLAW_VERSION=2026.3.13 ./launch.sh +OPENCLAW_VERSION=2026.9.9 ./launch.sh ``` When `OPENCLAW_VERSION` is unset, `launch.sh` now resolves it from `OPENCLAW_RELEASE_TAG`. It first tries the exact tag without the leading `v`, then falls back to removing a trailing GitHub-only `-N` suffix such as `v2026.3.13-1 -> 2026.3.13`. diff --git a/configure.sh b/configure.sh index 7cf2495..6df5383 100755 --- a/configure.sh +++ b/configure.sh @@ -347,6 +347,36 @@ set_model_context_window() { mv "$tmp_file" "$config_file" } +ensure_config_compatible() { + local config_file backup_file + if openclaw config validate >/dev/null 2>&1; then + return + fi + + config_file="$HOME/.openclaw/openclaw.json" + backup_file="$(mktemp "$config_file.pre-migration.XXXXXX")" + chmod 600 "$backup_file" + (umask 077; cp "$config_file" "$backup_file") + chmod 600 "$backup_file" + log "Migrating legacy OpenClaw config; rollback copy: $backup_file" + # Safe, non-interactive upstream migrations only; never force a service repair. + openclaw doctor --fix --non-interactive || fail "Config migration failed. Original config retained at $backup_file; inspect doctor output before retrying." + openclaw config validate >/dev/null 2>&1 || fail "Migrated config is still invalid. Original config retained at $backup_file." +} + +configure_trusted_proxies() { + [[ -n "${OPENCLAW_TRUSTED_PROXY_IPS:-}" ]] || return 0 + node -e ' + const {isIP}=require("net"); + try { + const ips=JSON.parse(process.argv[1]); + if(!Array.isArray(ips) || !ips.length || ips.some(ip=>typeof ip!=="string" || !isIP(ip) || ip==="0.0.0.0" || ip==="::")) process.exit(1); + } catch { process.exit(1); } + ' "$OPENCLAW_TRUSTED_PROXY_IPS" || fail "OPENCLAW_TRUSTED_PROXY_IPS must be a JSON array of explicit proxy IPs, not wildcards or network ranges." + log "Configuring explicitly trusted reverse proxy IPs (gateway token authentication is unchanged)" + openclaw config set gateway.trustedProxies "$OPENCLAW_TRUSTED_PROXY_IPS" --strict-json +} + main() { local gateway_log approval_log pid_file token gateway_pid @@ -377,7 +407,10 @@ main() { load_saved_env fi + stop_existing_gateway "$pid_file" + ensure_config_compatible configure_control_ui_origin + configure_trusted_proxies token="$(get_gateway_token || true)" if [[ -z "$token" ]]; then @@ -386,7 +419,6 @@ main() { token="$(get_gateway_token || true)" fi - stop_existing_gateway "$pid_file" log "Starting OpenClaw gateway" gateway_pid="$(start_gateway "$gateway_log" "$pid_file")" log "Gateway is running with PID $gateway_pid" diff --git a/launch.sh b/launch.sh index b51136c..d95b461 100755 --- a/launch.sh +++ b/launch.sh @@ -15,8 +15,8 @@ fi CODE_SERVER_VERSION="${CODE_SERVER_VERSION:-4.89.1}" CODE_SERVER_PORT="${CODE_SERVER_PORT:-13337}" -DEFAULT_OPENCLAW_VERSION="2026.3.13" -DEFAULT_OPENCLAW_RELEASE_TAG="v2026.3.13-1" +DEFAULT_OPENCLAW_VERSION="2026.9.9" +DEFAULT_OPENCLAW_RELEASE_TAG="v2026.9.9" OPENCLAW_VERSION="${OPENCLAW_VERSION:-}" OPENCLAW_RELEASE_TAG="${OPENCLAW_RELEASE_TAG:-$DEFAULT_OPENCLAW_RELEASE_TAG}" OPENCLAW_ENV_FILE="${OPENCLAW_ENV_FILE:-$HOME/.openclaw/.env}" @@ -233,24 +233,33 @@ derive_code_server_origin() { fi } -ensure_node() { - local node_major - node_major="$(get_node_major)" +node_runtime_supported() { + local version major minor + version="$(node --version 2>/dev/null || true)" + [[ "$version" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] || return 1 + major="${BASH_REMATCH[1]}" + minor="${BASH_REMATCH[2]}" + # OpenClaw 2026.9.9 npm engines: >=24.16.0 <25 || >=26.1.0. + (( (major == 24 && minor >= 16) || (major == 26 && minor >= 1) || major > 26 )) +} - if [[ "$node_major" -ge 22 ]]; then - log "Node.js $(node --version) already satisfies the >=22 requirement" +ensure_node() { + if node_runtime_supported; then + log "Node.js $(node --version) satisfies the OpenClaw runtime requirement" return fi - log "Installing Node.js 22 from NodeSource" + log "Installing Node.js 24 LTS from NodeSource" require_cmd curl command -v apt-get >/dev/null 2>&1 || fail "This script currently supports Ubuntu/Debian environments with apt-get" run_as_root apt-get update run_as_root apt-get install -y ca-certificates curl gnupg - curl -fsSL https://deb.nodesource.com/setup_22.x | run_as_root bash - + curl -fsSL https://deb.nodesource.com/setup_24.x | run_as_root bash - run_as_root apt-get install -y nodejs + node_runtime_supported || fail "Node.js $(node --version) does not satisfy >=24.16.0 <25 or >=26.1.0" + log "Installed Node.js $(node --version)" } @@ -468,7 +477,7 @@ main() { TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6)" fi - log "Step 1/6: Ensuring Node.js >= 22" + log "Step 1/6: Ensuring a supported Node.js runtime" ensure_node log "Confirmed Node version: $(node --version)" diff --git a/tests/bootstrap-regression.sh b/tests/bootstrap-regression.sh index 2faf36f..9239bd1 100644 --- a/tests/bootstrap-regression.sh +++ b/tests/bootstrap-regression.sh @@ -3,6 +3,81 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" passes=0 +for runtime in v22.23.3 v24.15.9 v24.16.0 v24.21.0 v24.16.0-rc.1 v25.0.0 v26.0.9 v26.1.0 v27.0.0 unavailable; do + ( + eval "$(sed '$d' "$ROOT/launch.sh")" + node() { printf '%s\n' "$runtime"; } + case "$runtime" in + v24.16.0|v24.21.0|v26.1.0|v27.0.0) node_runtime_supported ;; + *) ! node_runtime_supported ;; + esac + ) + passes=$((passes + 1)) +done + +( + unset OPENCLAW_VERSION OPENCLAW_RELEASE_TAG + eval "$(sed '$d' "$ROOT/launch.sh")" + [[ "$DEFAULT_OPENCLAW_VERSION" == 2026.9.9 && "$OPENCLAW_RELEASE_TAG" == v2026.9.9 ]] +) +passes=$((passes + 1)) + +for migration in valid repaired unrepairable; do + ( + eval "$(sed '$d' "$ROOT/configure.sh")" + fixture="$(mktemp -d)" + trap 'rm -rf "$fixture"' EXIT + HOME="$fixture" + mkdir -p "$HOME/.openclaw" + cp "$ROOT/README.md" "$HOME/.openclaw/openclaw.json" + validates=0 + repairs=0 + openclaw() { + if [[ "$1 $2" == 'config validate' ]]; then + validates=$((validates + 1)) + [[ "$migration" == valid || ( "$migration" == repaired && "$validates" -gt 1 ) ]] + else + [[ "$*" == 'doctor --fix --non-interactive' ]] + repairs=$((repairs + 1)) + fi + } + if [[ "$migration" == unrepairable ]]; then + if (ensure_config_compatible); then + exit 1 + fi + else + ensure_config_compatible + if [[ "$migration" == valid ]]; then + [[ "$repairs" -eq 0 ]] + else + [[ "$repairs" -eq 1 && "$validates" -eq 2 ]] + fi + fi + if [[ "$migration" != valid ]]; then + for backup in "$HOME"/.openclaw/openclaw.json.pre-migration.*; do + cmp -s "$backup" "$HOME/.openclaw/openclaw.json" + [[ "$(ls -l "$backup" | awk '{print substr($1,1,10)}')" == '-rw-------' ]] + done + fi + ) + passes=$((passes + 1)) +done + +for proxies in '["100.73.87.46"]' '["127.0.0.1","::1"]' '["0.0.0.0/0"]' '["100.64.0.0/10"]' '["*"]' '["0.0.0.0"]' '["::"]' '[]' 'not-json'; do + ( + eval "$(sed '$d' "$ROOT/configure.sh")" + OPENCLAW_TRUSTED_PROXY_IPS="$proxies" + openclaw() { + [[ "$1 $2 $3" == 'config set gateway.trustedProxies' && "$4" == "$proxies" && "$5" == --strict-json ]] + } + case "$proxies" in + '["100.73.87.46"]'|'["127.0.0.1","::1"]') configure_trusted_proxies ;; + *) if (configure_trusted_proxies >/dev/null 2>&1); then exit 1; fi ;; + esac + ) + passes=$((passes + 1)) +done + check_origin() ( local script="$1" function="$2" host="$3" expected="$4" override="${5:-}" # Load function definitions without invoking the script's final main call. @@ -56,7 +131,7 @@ for configured in yes no; do for function in require_non_root require_cmd ensure_node ensure_openclaw_installed verify_openclaw_cli clone_or_refresh_launch_repo install_code_server install_code_server_extensions configure_code_server enable_code_server_service; do eval "$function() { :; }" done - node() { printf 'v22.23.3\n'; } + node() { printf 'v24.21.0\n'; } is_openclaw_configured() { [[ "$configured" == yes ]]; } bash() { calls="${calls}gateway "; [[ "$1" == "$LAUNCH_REPO_DIR/configure.sh" ]]; } print_configuration_pending() { calls="${calls}pending "; } From 8b2a638e8d86d8510e9b704090a5d74c67617257 Mon Sep 17 00:00:00 2001 From: JR Morgan Date: Fri, 9 Oct 2026 20:31:02 -0500 Subject: [PATCH 3/5] Handle modern OpenClaw dashboard secret references - Keep stored gateway credential references intact. - Use official temporary dashboard links and rebase their WebSocket target to Brev. - Add credential reference and dashboard URL regression tests. --- README.md | 1 + configure.sh | 51 ++++++++++++++++++++++++++++++----- tests/bootstrap-regression.sh | 42 +++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 615b41f..7960e59 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ The bootstrap is designed to be safe to run multiple times: - It skips Node installation when a compatible version is already installed. - It keeps OpenClaw pinned to the npm package version resolved from `OPENCLAW_RELEASE_TAG` and reinstalls if another version is found on PATH. - OpenClaw 2026.9.9 requires explicit reverse-proxy attribution. Set `OPENCLAW_TRUSTED_PROXY_IPS` to a JSON array of verified Brev proxy IPs (for example, `'["100.73.87.46"]'` on the tested Development route). Proxy addresses are deployment-specific; do not assume this example applies to another region or route. Only individual IPs are accepted, not CIDRs or wildcards. Ensure the proxy safely overwrites/rebuilds forwarded headers. Gateway token authentication remains enabled; this setting does not enable proxy-only authentication. +- Fresh releases may store the gateway token as a SecretRef. The configuration terminal uses OpenClaw's official dashboard bootstrap link and rewrites both its browser origin and WebSocket target to the Brev secure link. The temporary dashboard link can expire; rerun configuration to generate another. SecretRef objects are never presented as plaintext tokens or replaced simply because they are references. - Before restarting an existing gateway, it validates the config and uses safe, non-interactive upstream Doctor migrations when required, retaining a private rollback copy. If Doctor cannot repair it (for example, an obsolete gateway lock blocks migration), bootstrap fails with the backup location rather than starting an incompatible gateway. Inspect and resolve the reported stale process/lock before retrying; do not remove locks belonging to a running gateway. - It refreshes the local `~/launch-openclaw` checkout if it already exists. - It skips the first-run configure terminal after both `~/.openclaw/.env` and `~/.openclaw/openclaw.json` exist. diff --git a/configure.sh b/configure.sh index 6df5383..5b470d8 100755 --- a/configure.sh +++ b/configure.sh @@ -74,9 +74,9 @@ get_gateway_token_from_config_file() { [[ -f "$config_file" ]] || return 1 if command -v jq >/dev/null 2>&1; then - token="$(jq -r '.gateway.auth.token // empty' "$config_file" 2>/dev/null | sed -n '1p')" || true + token="$(jq -r '.gateway.auth.token | select(type == "string")' "$config_file" 2>/dev/null | sed -n '1p')" || true else - token="$(node -e 'const fs=require("fs");const p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));if(j?.gateway?.auth?.token)process.stdout.write(j.gateway.auth.token);' "$config_file" 2>/dev/null)" || true + token="$(node -e 'const fs=require("fs");const p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));if(typeof j?.gateway?.auth?.token==="string")process.stdout.write(j.gateway.auth.token);' "$config_file" 2>/dev/null)" || true fi [[ -n "$token" && "$token" != "__OPENCLAW_REDACTED__" && "$token" != "null" ]] || return 1 @@ -115,7 +115,7 @@ get_gateway_token() { local status=$? set -e - if [[ "$status" -eq 0 && -n "$token" && "$token" != "null" && "$token" != "undefined" && "$token" != "__OPENCLAW_REDACTED__" ]]; then + if [[ "$status" -eq 0 && "$token" =~ ^[[:alnum:]_-]+$ && "$token" != "null" && "$token" != "undefined" && "$token" != "__OPENCLAW_REDACTED__" ]]; then printf '%s\n' "$token" return 0 fi @@ -123,6 +123,43 @@ get_gateway_token() { return 1 } +gateway_token_is_configured() { + [[ -n "${OPENCLAW_GATEWAY_TOKEN:-}" ]] && return 0 + node -e ' + const fs=require("fs"); + try { + const token=JSON.parse(fs.readFileSync(process.argv[1],"utf8"))?.gateway?.auth?.token; + if(typeof token==="string" ? token.trim().length : token && typeof token==="object" && token.source && token.id) process.exit(0); + } catch {} + process.exit(1); + ' "$HOME/.openclaw/openclaw.json" +} + +get_dashboard_connection_url() { + local details origin + origin="$(derive_openclaw_origin)" + details="$(openclaw dashboard --no-open --json 2>/dev/null)" || return 1 + printf '%s' "$details" | node -e ' + const fs=require("fs"); + try { + const details=JSON.parse(fs.readFileSync(0,"utf8")); + if(!details.ok) process.exit(1); + const generated=new URL(details.browserUrl || details.url); + const external=new URL(process.argv[1]); + const url=new URL(generated.pathname+generated.search+generated.hash,external); + const params=new URLSearchParams(url.hash.slice(1)); + if(params.has("gatewayUrl")) { + const gateway=new URL(external); + gateway.protocol=external.protocol==="https:" ? "wss:" : "ws:"; + gateway.pathname="/"; gateway.search=""; gateway.hash=""; + params.set("gatewayUrl",gateway.toString()); + url.hash=params.toString(); + } + process.stdout.write(url.toString()); + } catch { process.exit(1); } + ' "$origin" +} + start_gateway() { local gateway_log="$1" local pid_file="$2" @@ -262,7 +299,9 @@ print_gateway_info() { host_name="$(hostname 2>/dev/null || true)" origin="$(derive_openclaw_origin)" - if [[ -n "$token" ]]; then + if url="$(get_dashboard_connection_url)"; then + : + elif [[ -n "$token" ]]; then url="${origin}/#token=${token}" else url="${origin}/chat?session=main" @@ -271,7 +310,7 @@ print_gateway_info() { printf '\nOpenClaw Gateway Started\n' printf '========================\n\n' printf 'URL:\n%s\n\n' "$url" - printf 'API Token:\n%s\n\n' "${token:-Unavailable - review $OPENCLAW_STATE_DIR/gateway.log}" + printf 'API Token:\n%s\n\n' "${token:-Stored as a SecretRef; use the dashboard URL above. Generate a new dashboard link if it expires.}" printf 'Hostname:\n%s\n\n' "${host_name:-unknown}" printf 'Origin:\n%s\n' "$origin" } @@ -413,7 +452,7 @@ main() { configure_trusted_proxies token="$(get_gateway_token || true)" - if [[ -z "$token" ]]; then + if [[ -z "$token" ]] && ! gateway_token_is_configured; then log "No configured gateway token found; generating one with OpenClaw doctor" openclaw doctor --generate-gateway-token >/dev/null token="$(get_gateway_token || true)" diff --git a/tests/bootstrap-regression.sh b/tests/bootstrap-regression.sh index 9239bd1..22bd818 100644 --- a/tests/bootstrap-regression.sh +++ b/tests/bootstrap-regression.sh @@ -78,6 +78,48 @@ for proxies in '["100.73.87.46"]' '["127.0.0.1","::1"]' '["0.0.0.0/0"]' '["100.6 passes=$((passes + 1)) done +for credential in literal reference missing; do + ( + eval "$(sed '$d' "$ROOT/configure.sh")" + fixture="$(mktemp -d)" + trap 'rm -rf "$fixture"' EXIT + HOME="$fixture" + unset OPENCLAW_GATEWAY_TOKEN + mkdir -p "$HOME/.openclaw" + case "$credential" in + literal) printf '%s' '{"gateway":{"auth":{"token":"qa-fake-token"}}}' > "$HOME/.openclaw/openclaw.json" ;; + reference) printf '%s' '{"gateway":{"auth":{"token":{"source":"store","provider":"default","id":"gateway-token"}}}}' > "$HOME/.openclaw/openclaw.json" ;; + missing) printf '%s' '{"gateway":{"auth":{}}}' > "$HOME/.openclaw/openclaw.json" ;; + esac + if [[ "$credential" == missing ]]; then + ! gateway_token_is_configured + else + gateway_token_is_configured + fi + if [[ "$credential" == literal ]]; then + [[ "$(get_gateway_token_from_config_file)" == qa-fake-token ]] + else + ! get_gateway_token_from_config_file + fi + ) + passes=$((passes + 1)) +done + +( + eval "$(sed '$d' "$ROOT/configure.sh")" + hostname() { printf 'brev-u1x29ihj3\n'; } + openclaw() { + [[ "$*" == 'dashboard --no-open --json' ]] + printf '%s' '{"ok":true,"browserUrl":"http://127.0.0.1:18789/#bootstrapToken=qa-fake-bootstrap&bootstrapProfile=main&gatewayUrl=ws%3A%2F%2F127.0.0.1%3A18789"}' + } + url="$(get_dashboard_connection_url)" + node -e ' + const url=new URL(process.argv[1]); const params=new URLSearchParams(url.hash.slice(1)); + if(url.origin!=="https://openclaw-u1x29ihj3.gobrev.dev" || params.get("bootstrapToken")!=="qa-fake-bootstrap" || params.get("gatewayUrl")!=="wss://openclaw-u1x29ihj3.gobrev.dev/") process.exit(1); + ' "$url" +) +passes=$((passes + 1)) + check_origin() ( local script="$1" function="$2" host="$3" expected="$4" override="${5:-}" # Load function definitions without invoking the script's final main call. From 8bc304219bfd53262e44468deab25cf06f835f65 Mon Sep 17 00:00:00 2001 From: JR Morgan Date: Fri, 9 Oct 2026 20:31:33 -0500 Subject: [PATCH 4/5] Canonicalize the secure dashboard WebSocket origin - Avoid a misleading same-host gateway-switch prompt caused by a trailing slash. - Retain explicit origin and bootstrap credential regression coverage. --- configure.sh | 2 +- tests/bootstrap-regression.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.sh b/configure.sh index 5b470d8..1bd1112 100755 --- a/configure.sh +++ b/configure.sh @@ -152,7 +152,7 @@ get_dashboard_connection_url() { const gateway=new URL(external); gateway.protocol=external.protocol==="https:" ? "wss:" : "ws:"; gateway.pathname="/"; gateway.search=""; gateway.hash=""; - params.set("gatewayUrl",gateway.toString()); + params.set("gatewayUrl",gateway.origin); url.hash=params.toString(); } process.stdout.write(url.toString()); diff --git a/tests/bootstrap-regression.sh b/tests/bootstrap-regression.sh index 22bd818..7f4a381 100644 --- a/tests/bootstrap-regression.sh +++ b/tests/bootstrap-regression.sh @@ -115,7 +115,7 @@ done url="$(get_dashboard_connection_url)" node -e ' const url=new URL(process.argv[1]); const params=new URLSearchParams(url.hash.slice(1)); - if(url.origin!=="https://openclaw-u1x29ihj3.gobrev.dev" || params.get("bootstrapToken")!=="qa-fake-bootstrap" || params.get("gatewayUrl")!=="wss://openclaw-u1x29ihj3.gobrev.dev/") process.exit(1); + if(url.origin!=="https://openclaw-u1x29ihj3.gobrev.dev" || params.get("bootstrapToken")!=="qa-fake-bootstrap" || params.get("gatewayUrl")!=="wss://openclaw-u1x29ihj3.gobrev.dev") process.exit(1); ' "$url" ) passes=$((passes + 1)) From c1eea518ed91575f72842aa0b0fcf6f07276b5e0 Mon Sep 17 00:00:00 2001 From: JR Morgan Date: Fri, 9 Oct 2026 20:50:46 -0500 Subject: [PATCH 5/5] fix: preserve first-run onboarding settings in code-server - Pass only non-secret bootstrap options into the generated configuration terminal. - Cover shell-safe option handoff and credential exclusion with six regression cases. --- launch.sh | 17 +++++++++++++++-- tests/bootstrap-regression.sh | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/launch.sh b/launch.sh index d95b461..4e652f7 100755 --- a/launch.sh +++ b/launch.sh @@ -345,10 +345,22 @@ install_code_server_extensions() { run_as_root -H -u "$TARGET_USER" env HOME="$TARGET_HOME" code-server --install-extension fabiospampinato.vscode-terminals --force >/dev/null } +configure_environment_prefix() { + local name value + # Only non-secret onboarding options belong in the generated terminal command. + # The code-server service does not inherit the one-shot bootstrap environment. + printf 'env ' + for name in OPENCLAW_TRUSTED_PROXY_IPS OPENCLAW_ORIGIN OPENCLAW_BASE_URL OPENCLAW_MODEL OPENCLAW_CONTEXT_WINDOW; do + value="${!name:-}" + [[ -n "$value" ]] || continue + printf '%s=%q ' "$name" "$value" + done +} + configure_code_server() { local config_dir settings_dir settings_user_dir workspaces_dir workspace_path home_workspace_path local terminals_target code_server_origin terminal_cmd run_once_marker wrapper_cmd - local home_readme + local home_readme configure_env config_dir="$TARGET_HOME/.config/code-server" settings_dir="$TARGET_HOME/.local/share/code-server" @@ -360,12 +372,13 @@ configure_code_server() { terminals_target="$TARGET_HOME/.vscode/terminals.json" code_server_origin="$(derive_code_server_origin)" run_once_marker="$TARGET_HOME/.cache/openclaw-launchable/configure-ran" + configure_env="$(configure_environment_prefix)" log "Configuring code-server" run_as_root -u "$TARGET_USER" mkdir -p "$config_dir" "$settings_user_dir" "$workspaces_dir" "$TARGET_HOME/.vscode" "$TARGET_HOME/.cache/openclaw-launchable" run_as_root -u "$TARGET_USER" install -m 644 "$LAUNCH_REPO_DIR/README.md" "$home_readme" - wrapper_cmd="mkdir -p \"${TARGET_HOME}/.cache/openclaw-launchable\" && if [[ -f \"${run_once_marker}\" ]]; then printf 'OpenClaw configure autorun already ran. Opening a fresh login shell.\\n\\n'; source ~/.profile >/dev/null 2>&1 || true; source ~/.bashrc >/dev/null 2>&1 || true; exec bash -l; fi; cd $(printf '%q' "$LAUNCH_REPO_DIR") && bash $(printf '%q' "$LAUNCH_REPO_DIR/configure.sh") && touch \"${run_once_marker}\"; source ~/.profile >/dev/null 2>&1 || true; source ~/.bashrc >/dev/null 2>&1 || true; exec bash -l" + wrapper_cmd="mkdir -p \"${TARGET_HOME}/.cache/openclaw-launchable\" && if [[ -f \"${run_once_marker}\" ]]; then printf 'OpenClaw configure autorun already ran. Opening a fresh login shell.\\n\\n'; source ~/.profile >/dev/null 2>&1 || true; source ~/.bashrc >/dev/null 2>&1 || true; exec bash -l; fi; cd $(printf '%q' "$LAUNCH_REPO_DIR") && ${configure_env}bash $(printf '%q' "$LAUNCH_REPO_DIR/configure.sh") && touch \"${run_once_marker}\"; source ~/.profile >/dev/null 2>&1 || true; source ~/.bashrc >/dev/null 2>&1 || true; exec bash -l" terminal_cmd="$wrapper_cmd" run_as_root -u "$TARGET_USER" tee "$terminals_target" >/dev/null <