mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
ca0eb62c87
Ensure the Windows Testbox workflow runs its lifecycle loop after setup failures and guard the shared Testbox finalization invariant.
244 lines
9.4 KiB
YAML
244 lines
9.4 KiB
YAML
name: Windows Blacksmith Testbox
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
testbox_id:
|
|
type: string
|
|
description: "Testbox session ID"
|
|
required: true
|
|
runner_label:
|
|
type: string
|
|
description: "Windows runner label"
|
|
required: false
|
|
default: "blacksmith-16vcpu-windows-2025"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
|
|
jobs:
|
|
windows:
|
|
name: windows
|
|
runs-on: ${{ inputs.runner_label }}
|
|
timeout-minutes: 75
|
|
defaults:
|
|
run:
|
|
shell: pwsh
|
|
steps:
|
|
- name: Begin Testbox
|
|
shell: bash
|
|
env:
|
|
TESTBOX_ID: ${{ inputs.testbox_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
metadata_port="${METADATA_PORT:-}"
|
|
if [ -z "$metadata_port" ]; then
|
|
metadata_port="$(cat /proc/cmdline | tr ' ' '\n' | grep '^metadata_port=' | cut -d= -f2)"
|
|
fi
|
|
if [ -z "$metadata_port" ]; then
|
|
echo "metadata_port not found in kernel cmdline" >&2
|
|
exit 1
|
|
fi
|
|
|
|
metadata_addr="192.168.127.1:${metadata_port}"
|
|
state=/tmp/.testbox
|
|
mkdir -p "$state"
|
|
chmod 700 "$state"
|
|
|
|
installation_model_id="$(curl -s --connect-timeout 2 --max-time 5 "http://${metadata_addr}/installationModelID")"
|
|
api_url="$(curl -s --connect-timeout 2 --max-time 5 "http://${metadata_addr}/backendURL")"
|
|
auth_token="$(curl -s --connect-timeout 2 --max-time 5 "http://${metadata_addr}/stickyDiskToken")"
|
|
|
|
if [ -z "$api_url" ] || [ -z "$installation_model_id" ] || [ -z "$auth_token" ]; then
|
|
echo "could not read required Blacksmith metadata" >&2
|
|
exit 1
|
|
fi
|
|
if ! jq -e 'type == "number"' <<<"$installation_model_id" >/dev/null; then
|
|
echo "invalid Blacksmith installation model id: ${installation_model_id}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -n "${BLACKSMITH_HOSTNAME:-}" ]; then
|
|
runner_host="$BLACKSMITH_HOSTNAME"
|
|
else
|
|
runner_host="${BLACKSMITH_HOST_PUBLIC_IP:-}"
|
|
fi
|
|
runner_ssh_port="${BLACKSMITH_SSH_PORT:-22}"
|
|
|
|
hydrating_body="$RUNNER_TEMP/testbox-hydrating.json"
|
|
hydrating_response="$RUNNER_TEMP/testbox-hydrating.response"
|
|
jq -n \
|
|
--arg testbox_id "$TESTBOX_ID" \
|
|
--argjson installation_model_id "$installation_model_id" \
|
|
--arg status "hydrating" \
|
|
--arg ip_address "$runner_host" \
|
|
--arg ssh_port "$runner_ssh_port" \
|
|
--arg working_directory "$GITHUB_WORKSPACE" \
|
|
--arg adopted_run_id "$GITHUB_RUN_ID" \
|
|
'{
|
|
testbox_id: $testbox_id,
|
|
installation_model_id: $installation_model_id,
|
|
status: $status,
|
|
ip_address: $ip_address,
|
|
ssh_port: $ssh_port,
|
|
working_directory: $working_directory,
|
|
adopted_run_id: $adopted_run_id,
|
|
metadata: {}
|
|
}' > "$hydrating_body"
|
|
|
|
hydrating_http_code="$(curl -sS -L --post302 --post303 -o "$hydrating_response" -w '%{http_code}' \
|
|
-X POST "${api_url}/api/testbox/phone-home" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer ${auth_token}" \
|
|
--data-binary @"$hydrating_body" || true)"
|
|
|
|
echo "phone_home_hydrating_http=${hydrating_http_code}"
|
|
if [[ ! "$hydrating_http_code" =~ ^2 ]]; then
|
|
echo "Blacksmith phone-home hydrating failed; response body:" >&2
|
|
cat "$hydrating_response" >&2 || true
|
|
exit 1
|
|
fi
|
|
response="$(cat "$hydrating_response")"
|
|
|
|
echo "$TESTBOX_ID" > "$state/testbox_id"
|
|
echo "$installation_model_id" > "$state/installation_model_id"
|
|
echo "$auth_token" > "$state/auth_token"
|
|
echo "$api_url" > "$state/api_url"
|
|
echo "$runner_host" > "$state/runner_host"
|
|
echo "$runner_ssh_port" > "$state/runner_ssh_port"
|
|
echo "$GITHUB_WORKSPACE" > "$state/working_directory"
|
|
echo "$GITHUB_RUN_ID" > "$state/adopted_run_id"
|
|
|
|
if [ -n "$response" ] && echo "$response" | jq -e . >/dev/null 2>&1; then
|
|
echo "$response" | jq -r '.ssh_public_key // empty' > "$state/ssh_public_key"
|
|
idle_timeout="$(echo "$response" | jq -r '.idle_timeout // empty')"
|
|
echo "${idle_timeout:-10}" > "$state/idle_timeout"
|
|
echo "phone-home response=json"
|
|
else
|
|
printf '%s\n' "$response" > "$state/ssh_public_key"
|
|
echo "10" > "$state/idle_timeout"
|
|
echo "phone-home response=raw"
|
|
fi
|
|
|
|
ssh_public_key="$(cat "$state/ssh_public_key" 2>/dev/null || true)"
|
|
if [ -z "$ssh_public_key" ]; then
|
|
echo "Blacksmith phone-home did not return an SSH public key; testbox cannot accept CLI connections." >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p ~/.ssh
|
|
printf '%s\n' "$ssh_public_key" >> ~/.ssh/authorized_keys
|
|
chmod 700 ~/.ssh
|
|
chmod 600 ~/.ssh/authorized_keys
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Prepare Windows shell
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
Write-Host "runner=$env:RUNNER_NAME"
|
|
Write-Host "machine=$env:COMPUTERNAME"
|
|
Write-Host ("os=" + [System.Environment]::OSVersion.VersionString)
|
|
Write-Host ("powershell=" + $PSVersionTable.PSVersion.ToString())
|
|
git --version
|
|
|
|
- name: Run Testbox
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
state=/tmp/.testbox
|
|
test -d "$state"
|
|
|
|
testbox_id="$(cat "$state/testbox_id")"
|
|
installation_model_id="$(cat "$state/installation_model_id")"
|
|
auth_token="$(cat "$state/auth_token")"
|
|
idle_timeout="$(cat "$state/idle_timeout" 2>/dev/null || true)"
|
|
idle_timeout="${idle_timeout:-10}"
|
|
api_url="$(cat "$state/api_url")"
|
|
runner_host="$(cat "$state/runner_host")"
|
|
runner_ssh_port="$(cat "$state/runner_ssh_port")"
|
|
working_directory="$(cat "$state/working_directory")"
|
|
adopted_run_id="$(cat "$state/adopted_run_id")"
|
|
if ! jq -e 'type == "number"' <<<"$installation_model_id" >/dev/null; then
|
|
echo "invalid Blacksmith installation model id: ${installation_model_id}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ready_body="$RUNNER_TEMP/testbox-ready.json"
|
|
jq -n \
|
|
--arg testbox_id "$testbox_id" \
|
|
--argjson installation_model_id "$installation_model_id" \
|
|
--arg status "ready" \
|
|
--arg ip_address "$runner_host" \
|
|
--arg ssh_port "$runner_ssh_port" \
|
|
--arg working_directory "$working_directory" \
|
|
--arg adopted_run_id "$adopted_run_id" \
|
|
'{
|
|
testbox_id: $testbox_id,
|
|
installation_model_id: $installation_model_id,
|
|
status: $status,
|
|
ip_address: $ip_address,
|
|
ssh_port: $ssh_port,
|
|
working_directory: $working_directory,
|
|
adopted_run_id: $adopted_run_id,
|
|
metadata: {}
|
|
}' > "$ready_body"
|
|
|
|
http_code="$(curl -sS -L --post302 --post303 -o "$RUNNER_TEMP/testbox-ready.response" -w '%{http_code}' \
|
|
-X POST "${api_url}/api/testbox/phone-home" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer ${auth_token}" \
|
|
--data-binary @"$ready_body" || true)"
|
|
echo "phone_home_ready_http=${http_code}"
|
|
if [[ ! "$http_code" =~ ^2 ]]; then
|
|
echo "Blacksmith phone-home ready failed; response body:" >&2
|
|
cat "$RUNNER_TEMP/testbox-ready.response" >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "============================================"
|
|
echo "Testbox ready!"
|
|
echo " Testbox ID: ${testbox_id}"
|
|
echo " Runner host: ${runner_host}"
|
|
echo " SSH port: ${runner_ssh_port}"
|
|
echo " Working directory: ${working_directory}"
|
|
echo " Run ID: ${adopted_run_id}"
|
|
echo " SSH: ssh -p ${runner_ssh_port} runner@${runner_host}"
|
|
echo "============================================"
|
|
|
|
last_activity="$(date +%s)"
|
|
idle_timeout_seconds=$(( idle_timeout * 60 ))
|
|
|
|
while true; do
|
|
sleep 30
|
|
now="$(date +%s)"
|
|
|
|
if netstat -na 2>/dev/null | grep ":${runner_ssh_port}" | grep -q ESTABLISHED; then
|
|
last_activity="$now"
|
|
elif [ -f ~/.testbox-last-activity ]; then
|
|
file_mtime="$(stat -c %Y ~/.testbox-last-activity 2>/dev/null || stat -f %m ~/.testbox-last-activity)"
|
|
if [ "$file_mtime" -gt "$last_activity" ]; then
|
|
last_activity="$file_mtime"
|
|
fi
|
|
fi
|
|
|
|
idle_seconds=$(( now - last_activity ))
|
|
if [ "$idle_seconds" -ge "$idle_timeout_seconds" ]; then
|
|
echo "Idle timeout reached (${idle_timeout} minutes). Shutting down."
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
- name: Testbox action marker
|
|
if: ${{ false }}
|
|
uses: useblacksmith/run-testbox@3f60ff9ceb2c10c3feefa87dc0c6490cffae059d
|