#!/bin/bash set -euo pipefail readonly image="node:24-bookworm-slim@sha256:6f7b03f7c2c8e2e784dcf9295400527b9b1270fd37b7e9a7285cf83b6951452d" readonly worktree_root_file="/etc/openclaw-mantis-sut-worktrees" readonly revisions_file="/etc/openclaw-mantis-sut-revisions" readonly runtime_root_file="/etc/openclaw-mantis-sut-runtime-root" readonly docker_bin="/usr/bin/docker" readonly flock_bin="/usr/bin/flock" readonly iptables_bin="/usr/sbin/iptables" readonly timeout_bin="/usr/bin/timeout" readonly network_lock_file="/run/lock/openclaw-mantis-sut-network.lock" readonly network_state_root="/run/openclaw-mantis-sut-networks" die() { echo "mantis SUT container: $*" >&2 exit 64 } run_cleanup_with_deadline() { local action="$1" shift # timeout owns a separate process group, so escalation reaches Docker and # network-cleanup descendants instead of killing only the caller's sudo. exec "$timeout_bin" --signal=TERM --kill-after=5s 30s /bin/bash "$0" "__${action}" "$@" } require_cleanup_timeout_parent() { [[ "$(readlink -f "/proc/$PPID/exe")" == "$timeout_bin" ]] \ || die "internal cleanup action requires the timeout supervisor" } require_container_name() { [[ "$1" =~ ^openclaw-telegram-sut-[0-9a-f-]+$ ]] || die "invalid container name" } require_port() { if [[ ! "$1" =~ ^[0-9]+$ ]] || ((10#$1 < 1 || 10#$1 > 65535)); then die "invalid port" fi } require_positive_integer() { [[ "$1" =~ ^[1-9][0-9]*$ ]] || die "invalid positive integer" } runtime_parent_path() { realpath -e "$(<"$runtime_root_file")" } runtime_claim_path() { printf '%s/claims/%s.claim\n' "$(runtime_parent_path)" "$1" } runtime_cancel_path() { printf '%s/claims/%s.cancelled\n' "$(runtime_parent_path)" "$1" } process_start_time() { local stat_text stat_text="$(<"/proc/$1/stat")" || return 1 local after_comm="${stat_text##*) }" local fields read -r -a fields <<<"$after_comm" [[ "${fields[19]:-}" =~ ^[0-9]+$ ]] || return 1 printf '%s\n' "${fields[19]}" } read_runtime_claim() { local claim_path claim_path="$(runtime_claim_path "$1")" [[ -f "$claim_path" && ! -L "$claim_path" ]] || return 1 [[ "$(stat -c %u "$claim_path")" == "0" ]] || return 1 [[ "$(stat -c %a "$claim_path")" == "400" ]] || return 1 [[ "$(stat -c %h "$claim_path")" == "1" ]] || return 1 IFS=$'\t' read -r claimed_runtime claimed_pid claimed_pgid claimed_start <"$claim_path" [[ "$claimed_runtime" =~ ^/tmp/openclaw-tg-crabbox-sut-[A-Za-z0-9]+$ ]] || return 1 [[ "$claimed_pid" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$claimed_pgid" =~ ^[1-9][0-9]*$ ]] || return 1 [[ "$claimed_start" =~ ^[1-9][0-9]*$ ]] || return 1 } claim_process_is_active() { [[ -r "/proc/$claimed_pid/stat" ]] || return 1 [[ "$(process_start_time "$claimed_pid")" == "$claimed_start" ]] || return 1 local current_pgid current_pgid="$(/usr/bin/ps -o pgid= -p "$claimed_pid" | tr -d ' ')" [[ "$current_pgid" == "$claimed_pgid" ]] } create_runtime_claim() { local container_name="$1" local runtime_source="$2" local runtime_parent runtime_parent="$(runtime_parent_path)" local claim_root="$runtime_parent/claims" install -d -o root -g root -m 0700 "$claim_root" local claim_path="$claim_root/$container_name.claim" local cancel_path="$claim_root/$container_name.cancelled" [[ ! -e "$claim_path" && ! -L "$claim_path" && ! -e "$cancel_path" && ! -L "$cancel_path" ]] \ || die "runtime claim already exists" local pgid pgid="$(/usr/bin/ps -o pgid= -p $$ | tr -d ' ')" [[ "$pgid" =~ ^[1-9][0-9]*$ ]] || die "invalid runtime process group" local start_time start_time="$(process_start_time $$)" || die "could not read runtime process identity" local temp temp="$(mktemp -p "$claim_root" .claim.XXXXXX)" printf '%s\t%s\t%s\t%s\n' "$runtime_source" "$$" "$pgid" "$start_time" >"$temp" chmod 0400 "$temp" mv -T "$temp" "$claim_path" } cancel_runtime_claim() { local container_name="$1" local runtime_source="$2" read_runtime_claim "$container_name" || die "missing or invalid runtime claim" [[ "$claimed_runtime" == "$runtime_source" ]] || die "runtime claim path mismatch" local cancel_path cancel_path="$(runtime_cancel_path "$container_name")" if [[ ! -e "$cancel_path" && ! -L "$cancel_path" ]]; then install -o root -g root -m 0400 /dev/null "$cancel_path" fi } terminate_runtime_claim() { # cancel_runtime_claim already captured the root-owned PID/PGID/start tuple. # Never reread by name here: delayed cleanup must not target a replacement claim. if claim_process_is_active; then kill -TERM -- "-$claimed_pgid" 2>/dev/null || true fi } require_runtime_claim_active() { [[ ! -e "$(runtime_cancel_path "$1")" && ! -L "$(runtime_cancel_path "$1")" ]] \ || die "runtime startup was cancelled" } container_security_args=( --read-only --cap-drop ALL --log-driver none --security-opt no-new-privileges --pids-limit 512 --sysctl net.ipv6.conf.all.disable_ipv6=1 --tmpfs "/tmp:rw,nosuid,nodev,size=536870912" ) build_resource_args=( --cpus 4 --memory 16g --memory-swap 16g ) runtime_resource_args=( --cpus 4 --memory 8g --memory-swap 8g ) blocked_networks=( 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 198.18.0.0/15 224.0.0.0/4 240.0.0.0/4 ) network_subnet() { "$docker_bin" network inspect "$1" --format '{{(index .IPAM.Config 0).Subnet}}' } network_exists() { local network_name="$1" local names if ! names="$("$docker_bin" network ls --format '{{.Name}}')"; then return 2 fi grep -Fxq "$network_name" <<<"$names" && return 0 return 1 } network_state_path() { [[ "$1" =~ ^[A-Za-z0-9_.-]+$ ]] || die "invalid proof network name" printf '%s/%s.subnet\n' "$network_state_root" "$1" } write_network_state() { local network_name="$1" local subnet="$2" local state_path state_path="$(network_state_path "$network_name")" install -d -o root -g root -m 0700 "$network_state_root" local temp temp="$(mktemp -p "$network_state_root" .subnet.XXXXXX)" printf '%s\n' "$subnet" >"$temp" chmod 0400 "$temp" mv -T "$temp" "$state_path" } remove_iptables_rule() { while true; do if "$iptables_bin" -C "$@" 2>/dev/null; then if ! "$iptables_bin" -D "$@"; then if "$iptables_bin" -C "$@" 2>/dev/null; then return 1 else local recheck_result=$? ((recheck_result == 1)) && return 0 return "$recheck_result" fi fi else local result=$? ((result == 1)) && return 0 return "$result" fi done } with_network_lock() { local operation="$1" shift local lock_fd exec {lock_fd}>"$network_lock_file" "$flock_bin" "$lock_fd" local result=0 "$operation" "$@" || result=$? exec {lock_fd}>&- return "$result" } cleanup_network_unlocked() { local network_name="$1" local state_path state_path="$(network_state_path "$network_name")" local subnet="" if [[ -e "$state_path" || -L "$state_path" ]]; then [[ -f "$state_path" && ! -L "$state_path" ]] || return 1 [[ "$(stat -c %u "$state_path")" == "0" ]] || return 1 [[ "$(stat -c %a "$state_path")" == "400" ]] || return 1 subnet="$(<"$state_path")" [[ "$subnet" =~ ^[0-9.]+/[0-9]+$ ]] || return 1 fi local exists_result if network_exists "$network_name"; then exists_result=0 else exists_result=$? fi if ((exists_result == 1)); then [[ -n "$subnet" ]] || return 0 elif ((exists_result != 0)); then return "$exists_result" fi if ((exists_result == 0)); then local inspected_subnet inspected_subnet="$(network_subnet "$network_name")" || return 1 [[ "$inspected_subnet" =~ ^[0-9.]+/[0-9]+$ ]] || return 1 [[ -z "$subnet" || "$subnet" == "$inspected_subnet" ]] || return 1 subnet="$inspected_subnet" write_network_state "$network_name" "$subnet" || return 1 if ! "$docker_bin" network rm "$network_name" >/dev/null 2>&1; then if network_exists "$network_name"; then return 1 else exists_result=$? ((exists_result == 1)) || return "$exists_result" fi fi fi remove_iptables_rule INPUT -s "$subnet" -j REJECT || return 1 for destination in "${blocked_networks[@]}"; do remove_iptables_rule DOCKER-USER -s "$subnet" -d "$destination" -j REJECT || return 1 done rm -f "$state_path" } cleanup_network() { with_network_lock cleanup_network_unlocked "$1" } container_exists() { local container_name="$1" local names if ! names="$("$docker_bin" container ls --all --format '{{.Names}}')"; then return 2 fi grep -Fxq "$container_name" <<<"$names" && return 0 return 1 } remove_container_or_fail() { local container_name="$1" local exists_result if container_exists "$container_name"; then exists_result=0 else exists_result=$? fi if ((exists_result == 0)); then if ! "$docker_bin" rm --force "$container_name" >/dev/null 2>&1; then if container_exists "$container_name"; then return 1 else exists_result=$? ((exists_result == 1)) || return "$exists_result" fi fi elif ((exists_result != 1)); then return "$exists_result" fi if container_exists "$container_name"; then die "candidate container is still running" else exists_result=$? ((exists_result == 1)) || return "$exists_result" fi } create_bounded_filesystem() { local name="$1" local size="$2" local runtime_parent runtime_parent="$(realpath -e "$(<"$runtime_root_file")")" local image_path="$runtime_parent/$name.ext4" local mount_path="$runtime_parent/$name" [[ ! -e "$image_path" && ! -e "$mount_path" ]] || die "bounded filesystem already exists" /usr/bin/truncate -s "$size" "$image_path" if ! /usr/sbin/mkfs.ext4 -q -F "$image_path"; then rm -f "$image_path" return 1 fi if ! mkdir "$mount_path"; then rm -f "$image_path" return 1 fi if ! /usr/bin/mount -o loop,nodev,nosuid "$image_path" "$mount_path"; then rmdir "$mount_path" rm -f "$image_path" return 1 fi printf '%s\t%s\n' "$mount_path" "$image_path" } destroy_bounded_filesystem() { local mount_path="$1" local image_path="$2" if /usr/bin/mountpoint -q "$mount_path"; then /usr/bin/umount "$mount_path" fi rm -rf --one-file-system "$mount_path" rm -f "$image_path" } remove_claimed_runtime_input() { local input_path="$1" local runtime_parent="$2" if [[ -L "$input_path" ]]; then rm -f "$input_path" return fi [[ -e "$input_path" ]] || return 0 [[ -d "$input_path" ]] || die "claimed runtime input is not a directory" [[ "$(stat -c %u "$input_path")" == "$(id -u codex)" ]] \ || die "claimed runtime input owner mismatch" [[ "$(stat -c %d "$input_path")" == "$(stat -c %d "$runtime_parent")" ]] \ || die "claimed runtime input filesystem mismatch" rm -rf --one-file-system "$input_path" [[ ! -e "$input_path" && ! -L "$input_path" ]] || die "failed to remove claimed runtime input" } create_public_only_network_unlocked() { local network_name="$1" cleanup_network_unlocked "$network_name" || return 1 if ! "$docker_bin" network create --driver bridge \ --opt com.docker.network.bridge.enable_icc=false "$network_name" >/dev/null; then return 1 fi local subnet if ! subnet="$(network_subnet "$network_name")"; then cleanup_network_unlocked "$network_name" || true return 1 fi if [[ ! "$subnet" =~ ^[0-9.]+/[0-9]+$ ]]; then cleanup_network_unlocked "$network_name" || true return 1 fi if ! write_network_state "$network_name" "$subnet"; then cleanup_network_unlocked "$network_name" || true return 1 fi if ! "$iptables_bin" -I INPUT 1 -s "$subnet" -j REJECT; then cleanup_network_unlocked "$network_name" || true return 1 fi for destination in "${blocked_networks[@]}"; do if ! "$iptables_bin" -I DOCKER-USER 1 -s "$subnet" -d "$destination" -j REJECT; then cleanup_network_unlocked "$network_name" || true return 1 fi done } create_public_only_network() { with_network_lock create_public_only_network_unlocked "$1" } require_locked_worktree() { local repo_root="$1" local lane="$2" local worktree_root worktree_root="$(realpath -e "$(<"$worktree_root_file")")" [[ "$(stat -c %u "$worktree_root")" == "0" ]] || die "worktree root is not root-owned" [[ "$(stat -c %a "$worktree_root")" == "700" ]] || die "worktree root mode mismatch" [[ "$lane" == "baseline" || "$lane" == "candidate" ]] || die "invalid proof lane" [[ "$repo_root" == "$worktree_root/$lane" ]] || die "repo root does not match the proof lane" [[ "$(stat -c %u "$repo_root")" == "0" ]] || die "prepared worktree is not root-owned" [[ -z "$(find "$repo_root" -xdev ! -type l -perm /222 -print -quit)" ]] \ || die "prepared worktree is writable" } expected_sha_for_lane() { local lane="$1" local expected_sha expected_sha="$(awk -v lane="$lane" '$1 == lane { print $2 }' "$revisions_file")" [[ "$expected_sha" =~ ^[0-9a-f]{40}$ ]] || die "invalid configured proof revision" printf '%s\n' "$expected_sha" } attest_worktree() { local repo_root="$1" local lane="$2" local expected_sha expected_sha="$(expected_sha_for_lane "$lane")" local actual_sha actual_sha="$(/usr/bin/git -c safe.directory="$repo_root" -C "$repo_root" rev-parse HEAD)" [[ "$actual_sha" == "$expected_sha" ]] || die "prepared worktree revision mismatch" printf '%s\n' "$actual_sha" } write_root_attestation() { local destination="$1" local lane="$2" local sha="$3" if [[ -e "$destination" || -L "$destination" ]]; then jq -e --arg lane "$lane" --arg sha "$sha" \ '.lane == $lane and .sha == $sha' "$destination" >/dev/null \ || die "conflicting SUT attestation" return fi local temp temp="$(mktemp -p "$(dirname "$destination")" .sut-attestation.XXXXXX)" jq -n --arg lane "$lane" --arg sha "$sha" '{lane: $lane, sha: $sha}' >"$temp" chmod 0444 "$temp" mv -T "$temp" "$destination" } lock_runtime_root() { local runtime_source="$1" local container_name="$2" [[ "$runtime_source" =~ ^/tmp/openclaw-tg-crabbox-sut-[A-Za-z0-9]+$ ]] \ || die "invalid runtime root" [[ -d "$runtime_source" && ! -L "$runtime_source" ]] || die "runtime root is not a directory" [[ "$(stat -c %u "$runtime_source")" == "$(id -u codex)" ]] || die "runtime root owner mismatch" local runtime_parent runtime_parent="$(realpath -e "$(<"$runtime_root_file")")" [[ "$(stat -c %u "$runtime_parent")" == "0" ]] || die "runtime parent is not root-owned" [[ "$(stat -c %a "$runtime_parent")" == "711" ]] || die "runtime parent mode mismatch" [[ "$(stat -c %d "$runtime_source")" == "$(stat -c %d "$runtime_parent")" ]] \ || die "runtime input and quarantine must share a filesystem" local quarantine="$runtime_parent/$container_name-input" [[ ! -e "$quarantine" && ! -L "$quarantine" ]] || die "runtime quarantine already exists" mv -T "$runtime_source" "$quarantine" if [[ ! -d "$quarantine" || -L "$quarantine" ]]; then rm -f "$quarantine" die "quarantined runtime is not a directory" fi if [[ "$(stat -c %u "$quarantine")" != "$(id -u codex)" ]]; then rm -rf --one-file-system "$quarantine" die "quarantined runtime owner mismatch" fi local filesystem if ! filesystem="$(create_bounded_filesystem "$container_name" 2G)"; then rm -rf --one-file-system "$quarantine" die "failed to create the bounded runtime filesystem" fi local safe_runtime="${filesystem%%$'\t'*}" local image_path="${filesystem#*$'\t'}" chown codex:codex "$safe_runtime" chmod 0700 "$safe_runtime" if ! /usr/sbin/runuser -u codex -- \ /bin/cp -a --no-dereference "$quarantine/." "$safe_runtime/"; then destroy_bounded_filesystem "$safe_runtime" "$image_path" rm -rf --one-file-system "$quarantine" die "failed to stage the bounded runtime root" fi if ! rm -rf --one-file-system "$quarantine"; then destroy_bounded_filesystem "$safe_runtime" "$image_path" die "failed to remove the quarantined runtime input" fi chown root:root "$safe_runtime" chmod 0755 "$safe_runtime" if ! ln -s "$safe_runtime" "$runtime_source"; then destroy_bounded_filesystem "$safe_runtime" "$image_path" die "failed to publish locked runtime root" fi printf '%s\n' "$safe_runtime" } # The probe must reach Telegram's public edge while every host/private/metadata # target remains unreachable through the exact network used by candidate code. # shellcheck disable=SC2016 readonly network_probe_script=' const dns = require("node:dns").promises; const net = require("node:net"); const connects = (host, port) => new Promise((resolve) => { const socket = net.connect({ host, port }); socket.setTimeout(1500); socket.on("connect", () => { socket.destroy(); resolve(true); }); socket.on("error", () => resolve(false)); socket.on("timeout", () => { socket.destroy(); resolve(false); }); }); (async () => { const blocked = await Promise.all([ connects("codex-host", Number(process.env.PROXY_PORT)), connects("10.0.0.1", 80), connects("100.100.100.200", 80), connects("169.254.169.254", 80), connects("192.168.0.1", 80), ]); if (blocked.some(Boolean)) process.exit(41); const [telegramIp] = await dns.resolve4("api.telegram.org"); if (!telegramIp || !(await connects(telegramIp, 443))) process.exit(42); })().catch(() => process.exit(42)); ' # Candidate lifecycle scripts run only inside the isolated build container. # shellcheck disable=SC2016 readonly build_command=' set -eu store=.mantis-pnpm-store cleanup() { rm -rf "$store" } trap cleanup EXIT INT TERM corepack pnpm install --frozen-lockfile --store-dir "$store" corepack pnpm build ' run_network_probe() { local network_name="$1" local proxy_port="$2" "$docker_bin" run --rm --network "$network_name" "${container_security_args[@]}" \ "${runtime_resource_args[@]}" \ --add-host codex-host:host-gateway \ --env PROXY_PORT="$proxy_port" \ "$image" node -e "$network_probe_script" local subnet subnet="$(network_subnet "$network_name")" local input_packets input_packets="$( "$iptables_bin" -L INPUT -v -n -x \ | awk -v source="$subnet" '$3 == "REJECT" && $8 == source { total += $1 } END { print total + 0 }' )" ((input_packets > 0)) || die "host isolation rule did not observe the probe" local private_packets=0 for destination in 10.0.0.0/8 100.64.0.0/10 169.254.0.0/16 192.168.0.0/16; do local destination_packets destination_packets="$( "$iptables_bin" -L DOCKER-USER -v -n -x \ | awk -v source="$subnet" -v destination="$destination" \ '$3 == "REJECT" && $8 == source && $9 == destination { total += $1 } END { print total + 0 }' )" private_packets=$((private_packets + destination_packets)) done ((private_packets > 0)) || die "private-network isolation rules did not observe the probe" } # The variables in this program are expanded inside the container, not here. # shellcheck disable=SC2016 readonly sut_command=' set -eu mock_pid="" gateway_pid="" cleanup() { exit_code=$? trap - EXIT INT TERM if [ -n "$gateway_pid" ]; then kill "$gateway_pid" 2>/dev/null || true; fi if [ -n "$mock_pid" ]; then kill "$mock_pid" 2>/dev/null || true; fi wait 2>/dev/null || true exit "$exit_code" } trap cleanup EXIT INT TERM node scripts/e2e/mock-openai-server.mjs >"$MOCK_LOG" 2>&1 & mock_pid=$! attempt=0 until grep -q "mock-openai listening" "$MOCK_LOG" 2>/dev/null; do kill -0 "$mock_pid" 2>/dev/null || exit 1 attempt=$((attempt + 1)) [ "$attempt" -lt 100 ] || exit 1 sleep 0.1 done node openclaw.mjs gateway --port "$OPENCLAW_GATEWAY_PORT" >"$GATEWAY_LOG" 2>&1 & gateway_pid=$! wait "$gateway_pid" ' command="${1:-}" shift || true case "$command" in build) [[ "${SUDO_USER:-}" == "runner" ]] || die "build is restricted to the workflow runner" [[ $# -eq 1 ]] || die "build expects the candidate worktree" worktree_root="$(realpath -e "$(<"$worktree_root_file")")" candidate_root="$(realpath -e "$1")" [[ "$candidate_root" == "$worktree_root/candidate" ]] || die "unexpected candidate worktree" [[ "$(stat -c %u "$candidate_root")" == "$(id -u mantis-builder)" ]] || die "candidate owner mismatch" [[ -f "$candidate_root/.git" && ! -L "$candidate_root/.git" ]] \ || die "candidate Git link is not a regular file" [[ "$(stat -c %h "$candidate_root/.git")" == "1" ]] \ || die "candidate Git link must not be hard-linked" (("$(stat -c %s "$candidate_root/.git")" <= 4096)) || die "candidate Git link is too large" candidate_git_link="$(<"$candidate_root/.git")" [[ "$candidate_git_link" == "gitdir: "* ]] || die "candidate Git link is invalid" candidate_sha="$(attest_worktree "$candidate_root" candidate)" container_name="openclaw-mantis-build-$$" runtime_parent="$(realpath -e "$(<"$runtime_root_file")")" build_mount="$runtime_parent/${container_name}-fs" build_image="$runtime_parent/${container_name}-fs.ext4" network_name="${container_name}-net" published_root="$worktree_root/.candidate-built-$$" [[ ! -e "$published_root" && ! -L "$published_root" ]] \ || die "candidate publish directory already exists" # shellcheck disable=SC2329 cleanup_build() { local result=0 remove_container_or_fail "$container_name" || result=$? cleanup_network "$network_name" || result=$? destroy_bounded_filesystem "$build_mount" "$build_image" || result=$? if [[ -n "${published_root:-}" && ( -e "$published_root" || -L "$published_root" ) ]]; then rm -rf --one-file-system "$published_root" || result=$? fi return "$result" } trap cleanup_build EXIT INT TERM create_bounded_filesystem "${container_name}-fs" 10G >/dev/null isolated_root="$build_mount/repo" mkdir "$isolated_root" /bin/cp -a "$candidate_root/." "$isolated_root/" chown -R mantis-builder:mantis-builder "$isolated_root" create_public_only_network "$network_name" run_network_probe "$network_name" 9 /usr/bin/timeout --signal=TERM --kill-after=30s 30m \ "$docker_bin" run --rm --init --name "$container_name" --network "$network_name" \ "${container_security_args[@]}" "${build_resource_args[@]}" \ --mount "type=bind,src=$isolated_root,dst=$candidate_root" \ --workdir "$candidate_root" \ --user "$(id -u mantis-builder):$(id -g mantis-builder)" \ --env CI=1 \ --env COREPACK_HOME=/tmp/corepack \ --env GIT_COMMIT="$candidate_sha" \ --env HOME=/tmp/home \ --env OPENCLAW_BUILD_PRIVATE_QA=1 \ --env OPENCLAW_ENABLE_PRIVATE_QA_CLI=1 \ "$image" sh -c "$build_command" build_result=$? remove_container_or_fail "$container_name" cleanup_network "$network_name" build_size_mb="$(du -sm "$isolated_root" | awk '{print $1}')" ((build_size_mb <= 8192)) || die "candidate build output exceeds 8 GiB" [[ -f "$isolated_root/.git" && ! -L "$isolated_root/.git" ]] \ || die "isolated Git link is not a regular file" [[ "$(stat -c %h "$isolated_root/.git")" == "1" ]] \ || die "isolated Git link must not be hard-linked" (("$(stat -c %s "$isolated_root/.git")" <= 4096)) || die "isolated Git link is too large" [[ "$(<"$isolated_root/.git")" == "$candidate_git_link" ]] \ || die "isolated Git link changed during build" mkdir -m 0755 "$published_root" /bin/cp -a --no-dereference "$isolated_root/." "$published_root/" [[ -f "$published_root/.git" && ! -L "$published_root/.git" ]] \ || die "published Git link is not a regular file" [[ "$(<"$published_root/.git")" == "$candidate_git_link" ]] \ || die "published Git link mismatch" rm -rf --one-file-system "$candidate_root" mv -T "$published_root" "$candidate_root" published_root="" destroy_bounded_filesystem "$build_mount" "$build_image" trap - EXIT INT TERM exit "$build_result" ;; check) [[ $# -eq 1 ]] || die "check expects a proxy port" require_port "$1" network_name="openclaw-mantis-check-$$" create_public_only_network "$network_name" # shellcheck disable=SC2329 cleanup_check() { cleanup_network "$network_name" } trap cleanup_check EXIT INT TERM run_network_probe "$network_name" "$1" check_result=$? cleanup_network "$network_name" trap - EXIT INT TERM exit "$check_result" ;; run) [[ $# -eq 7 ]] \ || die "run expects name, lane, repo root, runtime root, gateway port, mock port, and proxy port" container_name="$1" lane="$2" repo_root="$(realpath -e "$3")" runtime_source="$4" gateway_port="$5" mock_port="$6" proxy_port="$7" require_container_name "$container_name" require_port "$gateway_port" require_port "$mock_port" require_port "$proxy_port" [[ "$runtime_source" =~ ^/tmp/openclaw-tg-crabbox-sut-[A-Za-z0-9]+$ ]] \ || die "invalid runtime root" create_runtime_claim "$container_name" "$runtime_source" require_locked_worktree "$repo_root" "$lane" attested_sha="$(attest_worktree "$repo_root" "$lane")" require_runtime_claim_active "$container_name" safe_runtime="$(lock_runtime_root "$runtime_source" "$container_name")" require_runtime_claim_active "$container_name" input_file="$safe_runtime/container-input.json" trap 'rm -f "${input_file:-}"' EXIT [[ -f "$input_file" && ! -L "$input_file" ]] || die "invalid container input" [[ "$(stat -c %u "$input_file")" == "$(id -u codex)" ]] || die "container input owner mismatch" [[ "$(stat -c %a "$input_file")" == "600" ]] || die "container input mode mismatch" [[ "$(stat -c %h "$input_file")" == "1" ]] || die "container input must not be hard-linked" for name in gateway.log mock-openai.log mock-openai-requests.ndjson sut-attestation.json; do [[ ! -e "$safe_runtime/$name" && ! -L "$safe_runtime/$name" ]] \ || die "runtime output was pre-created" done write_root_attestation "$safe_runtime/sut-attestation.json" "$lane" "$attested_sha" runtime_parent="$(realpath -e "$(<"$runtime_root_file")")" write_root_attestation "$runtime_parent/attestations/$lane.json" "$lane" "$attested_sha" success_marker="$(jq -er '.mockResponseText | strings' "$input_file")" telegram_bot_token="$(jq -er '.telegramBotToken | strings' "$input_file")" export SUCCESS_MARKER="$success_marker" export TELEGRAM_BOT_TOKEN="$telegram_bot_token" mock_response_chunk_delay_ms="$(jq -r '.mockResponseChunkDelayMs // ""' "$input_file")" gateway_password="$(jq -r '.gatewayPassword // ""' "$input_file")" rm -f "$input_file" trap - EXIT gateway_log="$runtime_source/gateway.log" mock_log="$runtime_source/mock-openai.log" request_log="$runtime_source/mock-openai-requests.ndjson" install -T -o codex -g codex -m 0600 /dev/null "$safe_runtime/gateway.log" install -T -o codex -g codex -m 0600 /dev/null "$safe_runtime/mock-openai.log" install -T -o codex -g codex -m 0600 /dev/null "$safe_runtime/mock-openai-requests.ndjson" export CI=1 export GATEWAY_LOG="$gateway_log" export GIT_COMMIT="$attested_sha" export HOME="$runtime_source/container-home" export MOCK_LOG="$mock_log" export MOCK_PORT="$mock_port" export MOCK_REQUEST_LOG="$request_log" export NODE_DISABLE_COMPILE_CACHE=1 export OPENAI_API_KEY=sk-openclaw-e2e-mock export OPENCLAW_BUILD_PRIVATE_QA=1 export OPENCLAW_CONFIG_PATH="$runtime_source/openclaw.json" export OPENCLAW_ENABLE_PRIVATE_QA_CLI=1 export OPENCLAW_GATEWAY_PORT="$gateway_port" export OPENCLAW_STATE_DIR="$runtime_source/state" if [[ -n "$mock_response_chunk_delay_ms" ]]; then require_positive_integer "$mock_response_chunk_delay_ms" export MOCK_RESPONSE_CHUNK_DELAY_MS="$mock_response_chunk_delay_ms" fi if [[ -n "$gateway_password" ]]; then export OPENCLAW_GATEWAY_PASSWORD="$gateway_password" fi forwarded_env=( CI GATEWAY_LOG GIT_COMMIT HOME MOCK_LOG MOCK_PORT MOCK_REQUEST_LOG NODE_DISABLE_COMPILE_CACHE OPENAI_API_KEY OPENCLAW_BUILD_PRIVATE_QA OPENCLAW_CONFIG_PATH OPENCLAW_ENABLE_PRIVATE_QA_CLI OPENCLAW_GATEWAY_PORT OPENCLAW_STATE_DIR SUCCESS_MARKER TELEGRAM_BOT_TOKEN ) [[ -z "${MOCK_RESPONSE_CHUNK_DELAY_MS:-}" ]] || forwarded_env+=(MOCK_RESPONSE_CHUNK_DELAY_MS) [[ -z "${OPENCLAW_GATEWAY_PASSWORD:-}" ]] || forwarded_env+=(OPENCLAW_GATEWAY_PASSWORD) docker_env=() for name in "${forwarded_env[@]}"; do docker_env+=(--env "$name") done network_name="${container_name}-net" create_public_only_network "$network_name" # shellcheck disable=SC2329 cleanup_run() { local result=0 remove_container_or_fail "$container_name" || result=$? cleanup_network "$network_name" || result=$? return "$result" } trap cleanup_run EXIT INT TERM run_network_probe "$network_name" "$proxy_port" require_runtime_claim_active "$container_name" "$docker_bin" run --rm --init --name "$container_name" --network "$network_name" \ "${container_security_args[@]}" "${runtime_resource_args[@]}" \ --mount "type=bind,src=$repo_root,dst=$repo_root,readonly" \ --mount "type=bind,src=$safe_runtime,dst=$runtime_source" \ --workdir "$repo_root" \ --user "$(id -u codex):$(id -g codex)" \ "${docker_env[@]}" \ "$image" sh -c "$sut_command" run_result=$? cleanup_network "$network_name" trap - EXIT INT TERM exit "$run_result" ;; stop) run_cleanup_with_deadline stop "$@" ;; __stop) require_cleanup_timeout_parent [[ $# -eq 2 ]] || die "stop expects a container name and runtime root" require_container_name "$1" runtime_source="$2" [[ "$runtime_source" =~ ^/tmp/openclaw-tg-crabbox-sut-[A-Za-z0-9]+$ ]] \ || die "invalid runtime source" cancel_runtime_claim "$1" "$runtime_source" stop_result=0 remove_container_or_fail "$1" || stop_result=1 cleanup_network "${1}-net" || stop_result=1 terminate_runtime_claim exit "$stop_result" ;; destroy) run_cleanup_with_deadline destroy "$@" ;; __destroy) require_cleanup_timeout_parent [[ $# -eq 2 ]] || die "destroy expects a container name and runtime root" require_container_name "$1" runtime_source="$2" [[ "$runtime_source" =~ ^/tmp/openclaw-tg-crabbox-sut-[A-Za-z0-9]+$ ]] \ || die "invalid runtime source" read_runtime_claim "$1" || die "missing or invalid runtime claim" [[ "$claimed_runtime" == "$runtime_source" ]] || die "runtime claim path mismatch" claim_process_is_active && die "refusing to destroy an active runtime claim" runtime_parent="$(runtime_parent_path)" runtime_root="$runtime_parent/$1" if container_exists "$1"; then die "refusing to destroy a running SUT container" else exists_result=$? ((exists_result == 1)) || exit "$exists_result" fi if network_exists "${1}-net"; then die "refusing to destroy an active SUT network" else exists_result=$? ((exists_result == 1)) || exit "$exists_result" fi network_state="$(network_state_path "${1}-net")" [[ ! -e "$network_state" && ! -L "$network_state" ]] \ || die "refusing to destroy runtime with pending network cleanup" if [[ -L "$runtime_source" ]]; then [[ "$(readlink "$runtime_source")" == "$runtime_root" ]] \ || die "invalid locked runtime symlink" rm -f "$runtime_source" else remove_claimed_runtime_input "$runtime_source" "$runtime_parent" fi remove_claimed_runtime_input "$runtime_parent/$1-input" "$runtime_parent" if [[ -e "$runtime_root" || -L "$runtime_root" || -e "$runtime_parent/$1.ext4" ]]; then destroy_bounded_filesystem "$runtime_root" "$runtime_parent/$1.ext4" fi rm -f "$(runtime_cancel_path "$1")" "$(runtime_claim_path "$1")" ;; *) die "expected build, check, run, stop, or destroy" ;; esac