echo"Running onboarding E2E..."
docker run --rm -t "$IMAGE_NAME" bash -lc '
set -euo pipefail
trap "" PIPE export TERM=xterm-256color
ONBOARD_FLAGS="--flow quickstart --auth-choice skip --skip-channels --skip-skills --skip-daemon --skip-ui" # tsdown may emit dist/index.js or dist/index.mjs depending on runtime/bundler. if [ -f dist/index.mjs ]; then
OPENCLAW_ENTRY="dist/index.mjs" elif [ -f dist/index.js ]; then
OPENCLAW_ENTRY="dist/index.js" else echo"Missing dist/index.(m)js (build output):"
ls -la dist || true
exit 1 fi export OPENCLAW_ENTRY
# Provide a minimal trash shim to avoid noisy "missing trash" logs in containers. export PATH="/tmp/openclaw-bin:$PATH"
mkdir -p /tmp/openclaw-bin cat > /tmp/openclaw-bin/trash <<'"'"'TRASH'"'"' #!/usr/bin/env bash
set -euo pipefail
trash_dir="$HOME/.Trash"
mkdir -p "$trash_dir" for target in "$@"; do
[ -e "$target" ] || continue
base="$(basename "$target")"
dest="$trash_dir/$base" if [ -e "$dest" ]; then
dest="$trash_dir/${base}-$(date +%s)-$$" fi
mv "$target""$dest" done
TRASH
chmod +x /tmp/openclaw-bin/trash
send() {
local payload="$1"
local delay="${2:-0.4}" # Let prompts render before sending keystrokes.
sleep "$delay"
printf "%b""$payload" >&3 2>/dev/null || true
}
wait_for_log() {
local needle="$1"
local timeout_s="${2:-45}"
local quiet_on_timeout="${3:-false}"
local needle_compact
needle_compact="$(printf "%s" "$needle" | tr -cd "[:alpha:]")"
local start_s
start_s="$(date +%s)" while true; do if [ -n "${WIZARD_LOG_PATH:-}" ] && [ -f "$WIZARD_LOG_PATH" ]; then if grep -a -F -q "$needle""$WIZARD_LOG_PATH"; then
return 0 fi if NEEDLE=\"$needle_compact\" node --input-type=module -e " import fs from \"node:fs\";
const file = process.env.WIZARD_LOG_PATH;
const needle = process.env.NEEDLE ?? \"\";
let text = \"\";
try { text = fs.readFileSync(file, \"utf8\"); } catch { process.exit(1); }
// Clack/script output can include lots of control sequences; keep a larger tail and strip ANSI more robustly. if (text.length > 120000) text = text.slice(-120000);
const normalizeScriptOutput = (value) =>
value
// util-linux script can emit each byte on its own CRLF-delimited line.
// Collapse those first so ANSI/control stripping works on real sequences.
.replace(/\\r?\\n/g, \"\")
.replace(/\\r/g, \"\");
const stripAnsi = (value) =>
normalizeScriptOutput(value)
// OSC: ESC ] ... BEL or ESC \\
.replace(/\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)/g, \"\")
// CSI: ESC [ ... cmd
.replace(/\\x1b\\[[0-?]*[ -/]*[@-~]/g, \"\");
// Letters-only: script output sometimes fragments ANSI sequences into digits/letters that
// can otherwise break substring matching.
const compact = (value) => stripAnsi(value).toLowerCase().replace(/[^a-z]+/g, \"\");
const haystack = compact(text);
const compactNeedle = compact(needle); if (!compactNeedle) process.exit(1);
process.exit(haystack.includes(compactNeedle) ? 0 : 1); "; then
return 0 fi fi if [ $(( $(date +%s) - start_s )) -ge "$timeout_s" ]; then if [ "$quiet_on_timeout" = "true" ]; then
return 1 fi echo"Timeout waiting for log: $needle" if [ -n "${WIZARD_LOG_PATH:-}" ] && [ -f "$WIZARD_LOG_PATH" ]; then
tail -n 140 "$WIZARD_LOG_PATH" || true fi
return 1 fi
sleep 0.2 done
}
wait_for_gateway() { for _ in $(seq 1 20); do if node --input-type=module -e " import net from 'node:net';
const socket = net.createConnection({ host: '127.0.0.1', port: 18789 });
const timeout = setTimeout(() => {
socket.destroy();
process.exit(1);
}, 500);
socket.on('connect', () => {
clearTimeout(timeout);
socket.end();
process.exit(0);
});
socket.on('error', () => {
clearTimeout(timeout);
process.exit(1);
}); " >/dev/null 2>&1; then
return 0 fi if [ -f /tmp/gateway-e2e.log ] && grep -E -q "listening on ws://[^ ]+:18789" /tmp/gateway-e2e.log; then if [ -n "${GATEWAY_PID:-}" ] && kill -0 "$GATEWAY_PID" 2>/dev/null; then
return 0 fi fi
sleep 1 done echo"Gateway failed to start" cat /tmp/gateway-e2e.log || true
return 1
}
stop_gateway() {
local gw_pid="$1" if [ -n "$gw_pid" ]; then
kill "$gw_pid" 2>/dev/null || true
wait "$gw_pid" || true fi
}
run_wizard_cmd() {
local case_name="$1"
local home_dir="$2"
local command="$3"
local send_fn="$4"
local with_gateway="${5:-false}"
local validate_fn="${6:-}"
input_fifo="$(mktemp -u "/tmp/openclaw-onboard-${case_name}.XXXXXX")"
mkfifo "$input_fifo"
local log_path="/tmp/openclaw-onboard-${case_name}.log"
WIZARD_LOG_PATH="$log_path" export WIZARD_LOG_PATH # Run under script to keep an interactive TTY for clack prompts.
script -q -f -c "$command""$log_path" < "$input_fifo" >/dev/null 2>&1 &
wizard_pid=$!
exec 3> "$input_fifo"
local gw_pid="" if [ "$with_gateway" = "true" ]; then
start_gateway
gw_pid="$GATEWAY_PID"
wait_for_gateway fi
"$send_fn"
if ! wait "$wizard_pid"; then
wizard_status=$?
exec 3>&- rm -f "$input_fifo"
stop_gateway "$gw_pid" echo"Wizard exited with status $wizard_status" if [ -f "$log_path" ]; then
tail -n 160 "$log_path" || true fi
exit "$wizard_status" fi
exec 3>&- rm -f "$input_fifo"
stop_gateway "$gw_pid" if [ -n "$validate_fn" ]; then "$validate_fn""$log_path" fi
}
run_wizard() {
local case_name="$1"
local home_dir="$2"
local send_fn="$3"
local validate_fn="${4:-}"
assert_file() {
local file_path="$1" if [ ! -f "$file_path" ]; then echo"Missing file: $file_path"
exit 1 fi
}
assert_dir() {
local dir_path="$1" if [ ! -d "$dir_path" ]; then echo"Missing dir: $dir_path"
exit 1 fi
}
run_case_logged() {
local label="$1"
shift
local log_path="/tmp/openclaw-onboard-${label}.log" if ! "$@" >"$log_path" 2>&1; then cat"$log_path"
exit 1 fi
}
send_channels_flow() { # Configure channels via configure wizard. Use the remove-config branch for # a stable no-op smoke path when the config starts empty.
wait_for_log "Where will the Gateway run?" 120
send $'"'"'\r'"'"' 0.6
wait_for_log "Configure/link" 120
send $'"'"'\e[B\r'"'"' 0.8 # Keep stdin open until wizard exits.
send "" 2.0
}
send_skills_flow() { # configure --section skills still runs the configure wizard.
wait_for_log "Where will the Gateway run?" 120
send $'"'"'\r'"'"' 0.6
wait_for_log "Configure skills now?" 120
send $'"'"'n\r'"'"' 0.8
send "" 2.0
}
if (errors.length > 0) {
console.error(errors.join("\n"));
process.exit(1);
}
NODE
}
assert_log_not_contains() {
local file_path="$1"
local needle="$2" if grep -q "$needle""$file_path"; then echo"Unexpected log output: $needle"
exit 1 fi
}
validate_local_basic_log() {
local log_path="$1"
assert_log_not_contains "$log_path""systemctl --user unavailable"
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.