mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-29 18:05:16 +00:00
* ci: support native Windows Crabbox hydration * ci: use Windows PowerShell for Crabbox hydrate * ci: reduce Windows pnpm install handle pressure * ci: narrow native Windows hydrate install scope * ci: keep Windows hydrate workspace-complete * ci: persist Windows hydrate tool paths * ci: split native Windows Crabbox hydration job * ci: scope native Windows hydrate to daemon proof * ci: use PowerShell for Windows hydrate fetch * ci: write Windows hydrate command files as UTF-8 * ci: document Windows hydrate runner mode * ci: preserve custom Crabbox hydrate markers * ci: avoid forced copy imports in Windows hydrate
693 lines
28 KiB
YAML
693 lines
28 KiB
YAML
name: Crabbox Hydrate
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
crabbox_id:
|
|
description: "Crabbox lease ID"
|
|
required: true
|
|
type: string
|
|
ref:
|
|
description: "Git ref to hydrate"
|
|
required: false
|
|
type: string
|
|
crabbox_runner_label:
|
|
description: "Dynamic Crabbox runner label"
|
|
required: true
|
|
type: string
|
|
crabbox_job:
|
|
description: "Hydration job identifier expected by Crabbox"
|
|
required: false
|
|
default: "hydrate"
|
|
type: string
|
|
crabbox_keep_alive_minutes:
|
|
description: "Minutes to keep the hydrated job alive"
|
|
required: false
|
|
default: "90"
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
PNPM_CONFIG_CHILD_CONCURRENCY: "1"
|
|
PNPM_CONFIG_MODULES_DIR: "/tmp/openclaw-pnpm-node-modules"
|
|
PNPM_CONFIG_NETWORK_CONCURRENCY: "1"
|
|
PNPM_CONFIG_STORE_DIR: "/tmp/openclaw-pnpm-store"
|
|
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
|
|
PNPM_CONFIG_VIRTUAL_STORE_DIR: "/tmp/openclaw-pnpm-virtual-store"
|
|
|
|
jobs:
|
|
hydrate:
|
|
name: hydrate
|
|
if: ${{ inputs.crabbox_job != 'hydrate-github' && inputs.crabbox_job != 'hydrate-windows-daemon' }}
|
|
runs-on: [self-hosted, "${{ inputs.crabbox_runner_label }}"]
|
|
timeout-minutes: 120
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: "24"
|
|
|
|
- name: Setup pnpm and dependencies
|
|
shell: bash
|
|
env:
|
|
CI: "true"
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$RUNNER_TEMP/cache}"
|
|
export COREPACK_HOME="${COREPACK_HOME:-$XDG_CACHE_HOME/corepack}"
|
|
export PNPM_HOME="${PNPM_HOME:-$RUNNER_TEMP/pnpm-home}"
|
|
mkdir -p "$XDG_CACHE_HOME" "$COREPACK_HOME" "$PNPM_HOME"
|
|
export PATH="$PNPM_HOME:$PATH"
|
|
{
|
|
echo "XDG_CACHE_HOME=$XDG_CACHE_HOME"
|
|
echo "COREPACK_HOME=$COREPACK_HOME"
|
|
echo "PNPM_HOME=$PNPM_HOME"
|
|
} >> "$GITHUB_ENV"
|
|
|
|
package_manager="$(node -e "const fs = require('node:fs'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); process.stdout.write(pkg.packageManager || '')")"
|
|
case "$package_manager" in
|
|
pnpm@*) ;;
|
|
*)
|
|
echo "::error::Expected packageManager to pin pnpm, got '${package_manager:-<empty>}'"
|
|
exit 1
|
|
;;
|
|
esac
|
|
corepack enable --install-directory "$PNPM_HOME"
|
|
for attempt in 1 2 3; do
|
|
if corepack prepare "$package_manager" --activate; then
|
|
break
|
|
fi
|
|
if [ "$attempt" = 3 ]; then
|
|
corepack prepare "$package_manager" --activate
|
|
fi
|
|
sleep $((attempt * 5))
|
|
done
|
|
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
|
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
|
|
echo "$node_bin" >> "$GITHUB_PATH"
|
|
export PATH="$node_bin:$PATH"
|
|
|
|
node -v
|
|
npm -v
|
|
pnpm -v
|
|
|
|
install_args=(
|
|
install
|
|
--prefer-offline
|
|
--ignore-scripts=false
|
|
--config.engine-strict=false
|
|
--config.enable-pre-post-scripts=true
|
|
--config.side-effects-cache=true
|
|
--frozen-lockfile
|
|
)
|
|
append_pnpm_option_arg() {
|
|
local env_name="$1"
|
|
local option_name="$2"
|
|
local value="${!env_name-}"
|
|
if [ -n "$value" ]; then
|
|
install_args+=("--${option_name}=${value}")
|
|
fi
|
|
}
|
|
append_pnpm_option_arg PNPM_CONFIG_CHILD_CONCURRENCY child-concurrency
|
|
append_pnpm_option_arg PNPM_CONFIG_MODULES_DIR modules-dir
|
|
append_pnpm_option_arg PNPM_CONFIG_NETWORK_CONCURRENCY network-concurrency
|
|
append_pnpm_option_arg PNPM_CONFIG_VIRTUAL_STORE_DIR virtual-store-dir
|
|
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
|
|
mkdir -p "$PNPM_CONFIG_MODULES_DIR"
|
|
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
|
|
fi
|
|
pnpm "${install_args[@]}" || pnpm "${install_args[@]}"
|
|
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
|
|
rm -rf node_modules
|
|
ln -sfn "$PNPM_CONFIG_MODULES_DIR" node_modules
|
|
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
|
|
fi
|
|
|
|
- name: Fetch main ref
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
|
|
fi
|
|
|
|
- name: Prepare Crabbox shell
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
|
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
|
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
|
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
|
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
|
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
|
#!/usr/bin/env bash
|
|
exec /usr/local/bin/corepack pnpm "$@"
|
|
PNPM
|
|
sudo chmod 0755 /usr/local/bin/pnpm
|
|
|
|
- name: Ensure Docker is running
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "docker not found; installing fallback engine"
|
|
curl --fail --show-error --location \
|
|
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
|
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
|
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
|
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
|
--retry-all-errors \
|
|
https://get.docker.com | sudo sh
|
|
fi
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
sudo systemctl start docker || true
|
|
elif command -v service >/dev/null 2>&1; then
|
|
sudo service docker start || true
|
|
fi
|
|
|
|
if [ -S /var/run/docker.sock ]; then
|
|
sudo usermod -aG docker "$USER" || true
|
|
# The runner process keeps its original groups; grant this
|
|
# ephemeral runner session access without requiring a relogin.
|
|
sudo chmod 666 /var/run/docker.sock
|
|
fi
|
|
|
|
if ! docker buildx version >/dev/null 2>&1; then
|
|
arch="$(uname -m)"
|
|
case "$arch" in
|
|
aarch64|arm64) buildx_arch=arm64 ;;
|
|
x86_64|amd64) buildx_arch=amd64 ;;
|
|
*) echo "unsupported buildx arch: $arch" >&2; exit 2 ;;
|
|
esac
|
|
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
|
|
mkdir -p "$HOME/.docker/cli-plugins"
|
|
curl --fail --show-error --location \
|
|
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
|
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
|
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
|
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
|
--retry-all-errors \
|
|
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
|
|
-o "$HOME/.docker/cli-plugins/docker-buildx"
|
|
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
|
|
fi
|
|
|
|
docker version
|
|
docker buildx version
|
|
docker compose version || true
|
|
|
|
- name: Ensure SSH is available
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
sudo systemctl start ssh || sudo systemctl start sshd || true
|
|
elif command -v service >/dev/null 2>&1; then
|
|
sudo service ssh start || sudo service sshd start || true
|
|
fi
|
|
|
|
- name: Hydrate provider env helper
|
|
shell: bash
|
|
run: bash scripts/ci-hydrate-testbox-env.sh
|
|
|
|
- name: Mark Crabbox ready
|
|
shell: bash
|
|
env:
|
|
CRABBOX_ID: ${{ inputs.crabbox_id }}
|
|
CRABBOX_JOB: ${{ inputs.crabbox_job }}
|
|
run: |
|
|
set -euo pipefail
|
|
job="${CRABBOX_JOB}"
|
|
if [ -z "$job" ]; then job=hydrate; fi
|
|
case "$CRABBOX_ID" in
|
|
''|*[!A-Za-z0-9._-]*)
|
|
echo "Invalid crabbox_id" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
mkdir -p "$HOME/.crabbox/actions"
|
|
state="$HOME/.crabbox/actions/${CRABBOX_ID}.env"
|
|
env_file="$HOME/.crabbox/actions/${CRABBOX_ID}.env.sh"
|
|
services_file="$HOME/.crabbox/actions/${CRABBOX_ID}.services"
|
|
write_export() {
|
|
key="$1"
|
|
value="${!key-}"
|
|
if [ -n "$value" ]; then
|
|
printf 'export %s=%q\n' "$key" "$value"
|
|
fi
|
|
}
|
|
{
|
|
for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE XDG_CACHE_HOME COREPACK_HOME NODE_BIN PNPM_HOME PNPM_CONFIG_CHILD_CONCURRENCY PNPM_CONFIG_MODULES_DIR PNPM_CONFIG_NETWORK_CONCURRENCY PNPM_CONFIG_STORE_DIR PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN PNPM_CONFIG_VIRTUAL_STORE_DIR PATH; do
|
|
write_export "$key"
|
|
done
|
|
} > "${env_file}.tmp"
|
|
mv "${env_file}.tmp" "$env_file"
|
|
{
|
|
echo "# Docker containers visible from the hydrated runner"
|
|
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' 2>/dev/null || true
|
|
} > "${services_file}.tmp"
|
|
mv "${services_file}.tmp" "$services_file"
|
|
tmp="${state}.tmp"
|
|
{
|
|
echo "WORKSPACE=${GITHUB_WORKSPACE}"
|
|
echo "RUN_ID=${GITHUB_RUN_ID}"
|
|
echo "JOB=${job}"
|
|
echo "ENV_FILE=${env_file}"
|
|
echo "SERVICES_FILE=${services_file}"
|
|
echo "READY_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
} > "$tmp"
|
|
mv "$tmp" "$state"
|
|
|
|
- name: Keep Crabbox job alive
|
|
shell: bash
|
|
env:
|
|
CRABBOX_ID: ${{ inputs.crabbox_id }}
|
|
CRABBOX_KEEP_ALIVE_MINUTES: ${{ inputs.crabbox_keep_alive_minutes }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$CRABBOX_ID" in
|
|
''|*[!A-Za-z0-9._-]*)
|
|
echo "Invalid crabbox_id" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
minutes="${CRABBOX_KEEP_ALIVE_MINUTES}"
|
|
case "$minutes" in
|
|
''|*[!0-9]*) minutes=90 ;;
|
|
esac
|
|
stop="$HOME/.crabbox/actions/${CRABBOX_ID}.stop"
|
|
deadline=$(( $(date +%s) + minutes * 60 ))
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
if [ -f "$stop" ]; then
|
|
exit 0
|
|
fi
|
|
sleep 15
|
|
done
|
|
|
|
hydrate-windows-daemon:
|
|
name: hydrate-windows-daemon
|
|
if: ${{ inputs.crabbox_job == 'hydrate-windows-daemon' }}
|
|
runs-on: [self-hosted, "${{ inputs.crabbox_runner_label }}"]
|
|
timeout-minutes: 120
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: "24"
|
|
|
|
- name: Fetch main ref
|
|
shell: powershell
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if (git rev-parse --is-inside-work-tree 2>$null) {
|
|
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
|
|
}
|
|
|
|
- name: Setup pnpm and dependencies
|
|
shell: powershell
|
|
env:
|
|
CI: "true"
|
|
COREPACK_ENABLE_DOWNLOAD_PROMPT: "0"
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$workspace = (Get-Location).Path
|
|
$cacheRoot = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { [System.IO.Path]::GetTempPath() }
|
|
$env:XDG_CACHE_HOME = Join-Path $cacheRoot "cache"
|
|
$env:COREPACK_HOME = Join-Path $env:XDG_CACHE_HOME "corepack"
|
|
$env:PNPM_HOME = Join-Path $cacheRoot "pnpm-home"
|
|
$env:PNPM_CONFIG_STORE_DIR = Join-Path $cacheRoot "openclaw-pnpm-store"
|
|
$env:PNPM_CONFIG_MODULES_DIR = Join-Path $workspace "node_modules"
|
|
$env:PNPM_CONFIG_VIRTUAL_STORE_DIR = Join-Path $workspace "node_modules\.pnpm"
|
|
$env:PNPM_CONFIG_CHILD_CONCURRENCY = "4"
|
|
$env:PNPM_CONFIG_NETWORK_CONCURRENCY = "8"
|
|
$env:PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN = "false"
|
|
$env:PNPM_CONFIG_SIDE_EFFECTS_CACHE = "false"
|
|
function Add-GitHubCommandLine([string]$Path, [string]$Value) {
|
|
$Value | Out-File -FilePath $Path -Encoding utf8 -Append
|
|
}
|
|
New-Item -ItemType Directory -Force `
|
|
$env:XDG_CACHE_HOME, `
|
|
$env:COREPACK_HOME, `
|
|
$env:PNPM_HOME, `
|
|
$env:PNPM_CONFIG_STORE_DIR | Out-Null
|
|
$env:PATH = "$env:PNPM_HOME;$env:PATH"
|
|
@(
|
|
"XDG_CACHE_HOME=$env:XDG_CACHE_HOME"
|
|
"COREPACK_HOME=$env:COREPACK_HOME"
|
|
"PNPM_HOME=$env:PNPM_HOME"
|
|
"PNPM_CONFIG_STORE_DIR=$env:PNPM_CONFIG_STORE_DIR"
|
|
"PNPM_CONFIG_MODULES_DIR=$env:PNPM_CONFIG_MODULES_DIR"
|
|
"PNPM_CONFIG_VIRTUAL_STORE_DIR=$env:PNPM_CONFIG_VIRTUAL_STORE_DIR"
|
|
"PNPM_CONFIG_CHILD_CONCURRENCY=$env:PNPM_CONFIG_CHILD_CONCURRENCY"
|
|
"PNPM_CONFIG_NETWORK_CONCURRENCY=$env:PNPM_CONFIG_NETWORK_CONCURRENCY"
|
|
"PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=$env:PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN"
|
|
"PNPM_CONFIG_SIDE_EFFECTS_CACHE=$env:PNPM_CONFIG_SIDE_EFFECTS_CACHE"
|
|
) | ForEach-Object { Add-GitHubCommandLine $env:GITHUB_ENV $_ }
|
|
Add-GitHubCommandLine $env:GITHUB_PATH $env:PNPM_HOME
|
|
|
|
$packageManager = (Get-Content package.json -Raw | ConvertFrom-Json).packageManager
|
|
if (-not $packageManager -or -not $packageManager.StartsWith("pnpm@")) {
|
|
Write-Error "Expected packageManager to pin pnpm, got '$packageManager'"
|
|
}
|
|
corepack enable --install-directory $env:PNPM_HOME
|
|
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
|
corepack prepare $packageManager --activate
|
|
if ($LASTEXITCODE -eq 0) {
|
|
break
|
|
}
|
|
if ($attempt -eq 3) {
|
|
exit $LASTEXITCODE
|
|
}
|
|
Start-Sleep -Seconds ($attempt * 5)
|
|
}
|
|
$nodeBin = Split-Path -Parent (node -p "process.execPath")
|
|
Add-GitHubCommandLine $env:GITHUB_ENV "NODE_BIN=$nodeBin"
|
|
Add-GitHubCommandLine $env:GITHUB_PATH $nodeBin
|
|
$env:PATH = "$nodeBin;$env:PATH"
|
|
|
|
node -v
|
|
npm -v
|
|
pnpm -v
|
|
|
|
$installArgs = @(
|
|
"install",
|
|
"--filter",
|
|
"openclaw",
|
|
"--prefer-offline",
|
|
"--ignore-scripts=true",
|
|
"--config.engine-strict=false",
|
|
"--config.enable-pre-post-scripts=false",
|
|
"--config.side-effects-cache=false",
|
|
"--frozen-lockfile",
|
|
"--child-concurrency=$env:PNPM_CONFIG_CHILD_CONCURRENCY",
|
|
"--modules-dir=$env:PNPM_CONFIG_MODULES_DIR",
|
|
"--network-concurrency=$env:PNPM_CONFIG_NETWORK_CONCURRENCY",
|
|
"--store-dir=$env:PNPM_CONFIG_STORE_DIR",
|
|
"--virtual-store-dir=$env:PNPM_CONFIG_VIRTUAL_STORE_DIR"
|
|
)
|
|
pnpm @installArgs
|
|
if ($LASTEXITCODE -ne 0) {
|
|
exit $LASTEXITCODE
|
|
}
|
|
$corepackShimDir = Join-Path $nodeBin "node_modules\corepack\shims"
|
|
if (Test-Path $corepackShimDir) {
|
|
$env:PNPM_HOME = $corepackShimDir
|
|
Add-GitHubCommandLine $env:GITHUB_ENV "PNPM_HOME=$env:PNPM_HOME"
|
|
Add-GitHubCommandLine $env:GITHUB_PATH $env:PNPM_HOME
|
|
}
|
|
|
|
- name: Mark Crabbox ready
|
|
shell: powershell
|
|
env:
|
|
CRABBOX_ID: ${{ inputs.crabbox_id }}
|
|
CRABBOX_JOB: ${{ inputs.crabbox_job }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
$job = if ($env:CRABBOX_JOB) { $env:CRABBOX_JOB } else { "hydrate-windows-daemon" }
|
|
if (-not $env:CRABBOX_ID -or $env:CRABBOX_ID -notmatch '^[A-Za-z0-9._-]+$') {
|
|
Write-Error "Invalid crabbox_id"
|
|
}
|
|
$actionsRoot = Join-Path $HOME ".crabbox\actions"
|
|
New-Item -ItemType Directory -Force $actionsRoot | Out-Null
|
|
$state = Join-Path $actionsRoot "$env:CRABBOX_ID.env"
|
|
$envFile = Join-Path $actionsRoot "$env:CRABBOX_ID.env.ps1"
|
|
$servicesFile = Join-Path $actionsRoot "$env:CRABBOX_ID.services"
|
|
$keys = @(
|
|
"CI", "GITHUB_ACTIONS", "GITHUB_WORKSPACE", "GITHUB_REPOSITORY",
|
|
"GITHUB_RUN_ID", "GITHUB_RUN_NUMBER", "GITHUB_RUN_ATTEMPT",
|
|
"GITHUB_REF", "GITHUB_REF_NAME", "GITHUB_SHA", "GITHUB_EVENT_NAME",
|
|
"GITHUB_ACTOR", "RUNNER_OS", "RUNNER_ARCH", "RUNNER_TEMP",
|
|
"RUNNER_TOOL_CACHE", "XDG_CACHE_HOME", "COREPACK_HOME", "NODE_BIN",
|
|
"PNPM_HOME", "PNPM_CONFIG_CHILD_CONCURRENCY", "PNPM_CONFIG_MODULES_DIR",
|
|
"PNPM_CONFIG_NETWORK_CONCURRENCY", "PNPM_CONFIG_STORE_DIR",
|
|
"PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN", "PNPM_CONFIG_VIRTUAL_STORE_DIR",
|
|
"PNPM_CONFIG_SIDE_EFFECTS_CACHE", "PATH"
|
|
)
|
|
$envLines = foreach ($key in $keys) {
|
|
$value = [Environment]::GetEnvironmentVariable($key)
|
|
if ($value) {
|
|
"$key=$value"
|
|
}
|
|
}
|
|
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
|
[System.IO.File]::WriteAllLines("$envFile.tmp", $envLines, $utf8NoBom)
|
|
Move-Item -Force "$envFile.tmp" $envFile
|
|
[System.IO.File]::WriteAllLines(
|
|
"$servicesFile.tmp",
|
|
@("# Docker containers visible from the hydrated runner", "docker not available on native Windows hydration"),
|
|
$utf8NoBom
|
|
)
|
|
Move-Item -Force "$servicesFile.tmp" $servicesFile
|
|
$stateLines = @(
|
|
"WORKSPACE=$env:GITHUB_WORKSPACE",
|
|
"RUN_ID=$env:GITHUB_RUN_ID",
|
|
"JOB=$job",
|
|
"ENV_FILE=$envFile",
|
|
"SERVICES_FILE=$servicesFile",
|
|
"READY_AT=$((Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))"
|
|
)
|
|
[System.IO.File]::WriteAllLines("$state.tmp", $stateLines, $utf8NoBom)
|
|
Move-Item -Force "$state.tmp" $state
|
|
|
|
- name: Keep Crabbox job alive
|
|
shell: powershell
|
|
env:
|
|
CRABBOX_ID: ${{ inputs.crabbox_id }}
|
|
CRABBOX_KEEP_ALIVE_MINUTES: ${{ inputs.crabbox_keep_alive_minutes }}
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
if (-not $env:CRABBOX_ID -or $env:CRABBOX_ID -notmatch '^[A-Za-z0-9._-]+$') {
|
|
Write-Error "Invalid crabbox_id"
|
|
}
|
|
$minutes = 90
|
|
if ($env:CRABBOX_KEEP_ALIVE_MINUTES -match '^[0-9]+$') {
|
|
$minutes = [int]$env:CRABBOX_KEEP_ALIVE_MINUTES
|
|
}
|
|
$stop = Join-Path $HOME ".crabbox\actions\$env:CRABBOX_ID.stop"
|
|
$deadline = (Get-Date).AddMinutes($minutes)
|
|
while ((Get-Date) -lt $deadline) {
|
|
if (Test-Path $stop) {
|
|
exit 0
|
|
}
|
|
Start-Sleep -Seconds 15
|
|
}
|
|
|
|
hydrate-github:
|
|
name: hydrate-github
|
|
if: ${{ inputs.crabbox_job == 'hydrate-github' }}
|
|
runs-on: [self-hosted, "${{ inputs.crabbox_runner_label }}"]
|
|
timeout-minutes: 120
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "false"
|
|
use-actions-cache: "false"
|
|
|
|
- name: Prepare Crabbox shell
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
|
|
fi
|
|
|
|
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
|
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
|
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
|
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
|
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
|
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
|
#!/usr/bin/env bash
|
|
exec /usr/local/bin/corepack pnpm "$@"
|
|
PNPM
|
|
sudo chmod 0755 /usr/local/bin/pnpm
|
|
|
|
- name: Ensure Docker is running
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "docker not found; installing fallback engine"
|
|
curl --fail --show-error --location \
|
|
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
|
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
|
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
|
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
|
--retry-all-errors \
|
|
https://get.docker.com | sudo sh
|
|
fi
|
|
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
sudo systemctl start docker || true
|
|
elif command -v service >/dev/null 2>&1; then
|
|
sudo service docker start || true
|
|
fi
|
|
|
|
if [ -S /var/run/docker.sock ]; then
|
|
sudo usermod -aG docker "$USER" || true
|
|
# The runner process keeps its original groups; grant this
|
|
# ephemeral runner session access without requiring a relogin.
|
|
sudo chmod 666 /var/run/docker.sock
|
|
fi
|
|
|
|
if ! docker buildx version >/dev/null 2>&1; then
|
|
arch="$(uname -m)"
|
|
case "$arch" in
|
|
aarch64|arm64) buildx_arch=arm64 ;;
|
|
x86_64|amd64) buildx_arch=amd64 ;;
|
|
*) echo "unsupported buildx arch: $arch" >&2; exit 2 ;;
|
|
esac
|
|
buildx_version="${DOCKER_BUILDX_VERSION:-v0.15.1}"
|
|
mkdir -p "$HOME/.docker/cli-plugins"
|
|
curl --fail --show-error --location \
|
|
--connect-timeout "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_CONNECT_TIMEOUT_SECONDS:-15}" \
|
|
--max-time "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_TIMEOUT_SECONDS:-300}" \
|
|
--retry "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRIES:-3}" \
|
|
--retry-delay "${OPENCLAW_CRABBOX_HYDRATE_DOWNLOAD_RETRY_DELAY_SECONDS:-5}" \
|
|
--retry-all-errors \
|
|
"https://github.com/docker/buildx/releases/download/${buildx_version}/buildx-${buildx_version}.linux-${buildx_arch}" \
|
|
-o "$HOME/.docker/cli-plugins/docker-buildx"
|
|
chmod 0755 "$HOME/.docker/cli-plugins/docker-buildx"
|
|
fi
|
|
|
|
docker version
|
|
docker buildx version
|
|
docker compose version || true
|
|
|
|
- name: Ensure SSH is available
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if command -v systemctl >/dev/null 2>&1; then
|
|
sudo systemctl start ssh || sudo systemctl start sshd || true
|
|
elif command -v service >/dev/null 2>&1; then
|
|
sudo service ssh start || sudo service sshd start || true
|
|
fi
|
|
|
|
- name: Hydrate provider env helper
|
|
shell: bash
|
|
env:
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
|
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
|
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
|
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
|
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
|
|
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
|
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
|
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
|
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
|
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
|
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
|
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
|
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
|
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
|
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
|
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
|
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
|
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
|
run: bash scripts/ci-hydrate-testbox-env.sh
|
|
|
|
- name: Mark Crabbox ready
|
|
shell: bash
|
|
env:
|
|
CRABBOX_ID: ${{ inputs.crabbox_id }}
|
|
CRABBOX_JOB: ${{ inputs.crabbox_job }}
|
|
run: |
|
|
set -euo pipefail
|
|
job="${CRABBOX_JOB}"
|
|
if [ -z "$job" ]; then job=hydrate-github; fi
|
|
case "$CRABBOX_ID" in
|
|
''|*[!A-Za-z0-9._-]*)
|
|
echo "Invalid crabbox_id" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
mkdir -p "$HOME/.crabbox/actions"
|
|
state="$HOME/.crabbox/actions/${CRABBOX_ID}.env"
|
|
env_file="$HOME/.crabbox/actions/${CRABBOX_ID}.env.sh"
|
|
services_file="$HOME/.crabbox/actions/${CRABBOX_ID}.services"
|
|
write_export() {
|
|
key="$1"
|
|
value="${!key-}"
|
|
if [ -n "$value" ]; then
|
|
printf 'export %s=%q\n' "$key" "$value"
|
|
fi
|
|
}
|
|
{
|
|
for key in CI GITHUB_ACTIONS GITHUB_WORKSPACE GITHUB_REPOSITORY GITHUB_RUN_ID GITHUB_RUN_NUMBER GITHUB_RUN_ATTEMPT GITHUB_REF GITHUB_REF_NAME GITHUB_SHA GITHUB_EVENT_NAME GITHUB_ACTOR RUNNER_OS RUNNER_ARCH RUNNER_TEMP RUNNER_TOOL_CACHE NODE_BIN PNPM_HOME PNPM_CONFIG_CHILD_CONCURRENCY PNPM_CONFIG_MODULES_DIR PNPM_CONFIG_NETWORK_CONCURRENCY PNPM_CONFIG_STORE_DIR PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN PNPM_CONFIG_VIRTUAL_STORE_DIR PATH; do
|
|
write_export "$key"
|
|
done
|
|
} > "${env_file}.tmp"
|
|
mv "${env_file}.tmp" "$env_file"
|
|
{
|
|
echo "# Docker containers visible from the hydrated runner"
|
|
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' 2>/dev/null || true
|
|
} > "${services_file}.tmp"
|
|
mv "${services_file}.tmp" "$services_file"
|
|
tmp="${state}.tmp"
|
|
{
|
|
echo "WORKSPACE=${GITHUB_WORKSPACE}"
|
|
echo "RUN_ID=${GITHUB_RUN_ID}"
|
|
echo "JOB=${job}"
|
|
echo "ENV_FILE=${env_file}"
|
|
echo "SERVICES_FILE=${services_file}"
|
|
echo "READY_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
} > "$tmp"
|
|
mv "$tmp" "$state"
|
|
|
|
- name: Keep Crabbox job alive
|
|
shell: bash
|
|
env:
|
|
CRABBOX_ID: ${{ inputs.crabbox_id }}
|
|
CRABBOX_KEEP_ALIVE_MINUTES: ${{ inputs.crabbox_keep_alive_minutes }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$CRABBOX_ID" in
|
|
''|*[!A-Za-z0-9._-]*)
|
|
echo "Invalid crabbox_id" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
minutes="${CRABBOX_KEEP_ALIVE_MINUTES}"
|
|
case "$minutes" in
|
|
''|*[!0-9]*) minutes=90 ;;
|
|
esac
|
|
stop="$HOME/.crabbox/actions/${CRABBOX_ID}.stop"
|
|
deadline=$(( $(date +%s) + minutes * 60 ))
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
if [ -f "$stop" ]; then
|
|
exit 0
|
|
fi
|
|
sleep 15
|
|
done
|