openclaw/scripts/e2e/docker-package-install.sh
Vincent Koc 3746991752
refactor(docker): share proof wait loop (#158291)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-09-26 13:21:45 +08:00

180 lines
8.2 KiB
Bash
Executable file

#!/usr/bin/env bash
# Bash 5.3+ can deadlock writing heredoc pipes on macOS before the reader starts.
if [[ ${OSTYPE:-} == darwin* && $BASH != /bin/bash ]] && ((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3))); then
exec /bin/bash "$0" "$@"
fi
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
# This mixed-platform lane owns its images; shared bare/functional tags belong to other lanes.
IMAGE_NAME="openclaw-package-install-bare:$$"
MUSL_IMAGE_NAME="openclaw-package-install-musl:$$"
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz docker-package-install "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
IDENTITY_PATH="${OPENCLAW_DOCKER_ARTIFACT_IDENTITY_PATH:-$ROOT_DIR/.artifacts/docker-tests/docker-package-install-identities.json}"
NPM_PROOF_CONTAINER="openclaw-package-npm-proof-$$"
PNPM_PROOF_CONTAINER="openclaw-package-pnpm-proof-$$"
BUN_PROOF_CONTAINER="openclaw-package-bun-proof-$$"
MUSL_PROOF_CONTAINER="openclaw-package-musl-proof-$$"
DOCKER_RUN_TIMEOUT="${OPENCLAW_DOCKER_PACKAGE_INSTALL_RUN_TIMEOUT:-120s}"
PACKAGE_HARNESS_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-package-harness.XXXXXX")"
docker_e2e_package_mount_args "$PACKAGE_TGZ"
cleanup() {
docker_e2e_docker_cmd rm -f \
"$NPM_PROOF_CONTAINER" \
"$PNPM_PROOF_CONTAINER" \
"$BUN_PROOF_CONTAINER" \
"$MUSL_PROOF_CONTAINER" >/dev/null 2>&1 || true
docker_e2e_docker_cmd image rm "$IMAGE_NAME" "$MUSL_IMAGE_NAME" >/dev/null 2>&1 || true
docker_e2e_cleanup_package_tgz "$PACKAGE_TGZ"
rm -rf "$PACKAGE_HARNESS_DIR"
}
trap cleanup EXIT
docker_e2e_build_or_reuse "$IMAGE_NAME" docker-package-install "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" bare
docker_e2e_build_or_reuse "$MUSL_IMAGE_NAME" docker-package-install-musl "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" musl
# The package proofs share the registry and lifecycle harness. Copy its complete
# script roots so all three managers install the same candidate dependency bytes.
for harness_path in \
packages/normalization-core/src \
scripts; do
mkdir -p "$PACKAGE_HARNESS_DIR/$(dirname "$harness_path")"
cp -R "$ROOT_DIR/$harness_path" "$PACKAGE_HARNESS_DIR/$harness_path"
done
chmod -R a+rX "$PACKAGE_HARNESS_DIR"
echo "Installing the real OpenClaw package artifact with npm as root..."
DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d \
--name "$NPM_PROOF_CONTAINER" \
--user root \
-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
-v "$PACKAGE_HARNESS_DIR:/repo:ro" \
-v "$ROOT_DIR/scripts/docker/verify-fs-safe-native.mjs:/tmp/verify-fs-safe-native.mjs:ro" \
"$IMAGE_NAME" \
bash -lc '
set -euo pipefail
npm install -g /tmp/openclaw-current.tgz --no-fund --no-audit
node /tmp/verify-fs-safe-native.mjs --package-root /usr/local/lib/node_modules/openclaw --mode require
test "$(command -v openclaw)" = "/usr/local/bin/openclaw"
# Root installed the global package; a non-root user must still be able to
# run it. A same-user install can never catch an installed tree that ends
# up owner-only readable, which is how sudo-install breakage ships.
runuser -u appuser -- openclaw --version > /tmp/openclaw-version
runuser -u appuser -- openclaw --help > /tmp/openclaw-help
test -s /tmp/openclaw-help
chmod 644 /tmp/openclaw-version /tmp/openclaw-help
touch /tmp/openclaw-proof-ready
exec sleep infinity
' >/dev/null
echo "Installing the real OpenClaw package artifact with pnpm..."
DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d \
--name "$PNPM_PROOF_CONTAINER" \
-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
-v "$PACKAGE_HARNESS_DIR:/repo:ro" \
-v "$ROOT_DIR/scripts/docker/verify-fs-safe-native.mjs:/tmp/verify-fs-safe-native.mjs:ro" \
"$IMAGE_NAME" \
bash -lc '
set -euo pipefail
export PNPM_HOME=/tmp/pnpm-home
# pnpm stores global executables in the bin subdirectory of PNPM_HOME.
export PATH="$PNPM_HOME/bin:$PATH"
corepack prepare "$1" --activate
pnpm add --global openclaw@file:/tmp/openclaw-current.tgz
test "$(command -v openclaw)" = "$PNPM_HOME/bin/openclaw"
pnpm list --global --json > /tmp/pnpm-packages.json
package_root="$(node -p "require(\"/tmp/pnpm-packages.json\")[0].dependencies.openclaw.path")"
test -f "$package_root/package.json"
# Tarball builds require their dependency path, relative to the install group.
artifact_build="$(node -p "const path = require(\"node:path\"); \"openclaw@file:\" + path.relative(path.resolve(process.argv[1], \"../..\"), \"/tmp/openclaw-current.tgz\")" "$package_root")"
pnpm approve-builds --global "$artifact_build"
node /tmp/verify-fs-safe-native.mjs --package-root "$package_root" --mode require
printf "%s\n" "$package_root" > /tmp/openclaw-package-root
openclaw --version > /tmp/openclaw-version
openclaw --help > /tmp/openclaw-help
test -s /tmp/openclaw-help
# A source install must follow checkout rebuilds without rewriting its package files.
(
export PNPM_HOME=/tmp/pnpm-source-link-home
export PATH="$PNPM_HOME/bin:$PATH"
mkdir -p /tmp/pnpm-source-link
cd /tmp/pnpm-source-link
node - "$1" <<"SOURCE_LINK"
const fs = require("node:fs");
fs.writeFileSync("package.json", JSON.stringify({
name: "openclaw-source-link-fixture", version: "1.0.0", packageManager: process.argv[2],
bin: { "openclaw-source-link-fixture": "cli.cjs" },
}));
fs.writeFileSync("pnpm-workspace.yaml", "packages: []\n");
fs.writeFileSync("pnpm-lock.yaml", "lockfileVersion: 9.0\n");
fs.writeFileSync("cli.cjs", "#!/usr/bin/env node\nconsole.log(1);\n", { mode: 0o755 });
SOURCE_LINK
pnpm install
for file in package.json pnpm-workspace.yaml pnpm-lock.yaml; do cp "$file" "$file.before"; done
pnpm add --global "openclaw-source-link-fixture@link:$PWD"
test "$(openclaw-source-link-fixture)" = 1
node <<"SOURCE_LINK"
const fs = require("node:fs");
fs.writeFileSync("cli-next.cjs", "#!/usr/bin/env node\nconsole.log(2);\n", { mode: 0o755 });
fs.renameSync("cli-next.cjs", "cli.cjs");
SOURCE_LINK
test "$(openclaw-source-link-fixture)" = 2
for file in package.json pnpm-workspace.yaml pnpm-lock.yaml; do cmp "$file" "$file.before"; done
)
touch /tmp/openclaw-proof-ready
exec sleep infinity
' -- "$(node -p "require('$ROOT_DIR/package.json').packageManager")" >/dev/null
echo "Installing the real OpenClaw package artifact with Bun..."
DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d \
--name "$BUN_PROOF_CONTAINER" \
-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
-v "$PACKAGE_HARNESS_DIR:/repo:ro" \
"$IMAGE_NAME" \
bash -lc '
set -euo pipefail
npm install -g --prefix /tmp/bun-runtime bun@1.4.2 --no-fund --no-audit
cd /repo
BUN_BIN=/tmp/bun-runtime/bin/bun \
OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0 \
OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/tmp/openclaw-current.tgz \
OPENCLAW_BUN_GLOBAL_SMOKE_PROOF_PATH=/tmp/openclaw-bun-proof.json \
bash scripts/e2e/bun-global-install-smoke.sh
touch /tmp/openclaw-proof-ready
exec sleep infinity
' >/dev/null
echo "Installing the real OpenClaw package artifact with npm on musl..."
DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d \
--name "$MUSL_PROOF_CONTAINER" \
-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
"$MUSL_IMAGE_NAME" \
sh -lc '
set -eu
npm install -g /tmp/openclaw-current.tgz --no-fund --no-audit
node /tmp/verify-fs-safe-native.mjs --package-root /usr/local/lib/node_modules/openclaw --mode require
touch /tmp/openclaw-proof-ready
exec sleep infinity
' >/dev/null
for container_name in "$NPM_PROOF_CONTAINER" "$PNPM_PROOF_CONTAINER" "$BUN_PROOF_CONTAINER" "$MUSL_PROOF_CONTAINER"; do
docker_e2e_wait_for_proof "$container_name" 240
done
bash "$ROOT_DIR/scripts/e2e/lib/docker-package-identity.sh" \
"$PACKAGE_TGZ" \
"$IDENTITY_PATH" \
"$IMAGE_NAME" \
"$NPM_PROOF_CONTAINER" \
"$PNPM_PROOF_CONTAINER" \
"$BUN_PROOF_CONTAINER" \
"$MUSL_PROOF_CONTAINER"
echo "npm, pnpm, and Bun package artifact proofs passed."