#!/usr/bin/env bash set -euo pipefail log() { echo "[openclaw-docker-ui] $*" } fail() { echo "[openclaw-docker-ui] ERROR: $*" >&2 exit 1 } require_cmd() { local cmd="$1" command -v "$cmd" >/dev/null 2>&1 || fail "missing required command: $cmd" } reset_openclaw_state_dir() { local state_dir="$1" local resolved_state_dir resolved_home [[ -n "$state_dir" ]] || fail "OPENCLAW_CONFIG_DIR must not be empty when resetting state" mkdir -p "$state_dir" resolved_state_dir="$(cd "$state_dir" && pwd -P)" resolved_home="$(cd "$HOME" && pwd -P)" case "$resolved_state_dir" in "/"|"$resolved_home") fail "refusing to reset unsafe OPENCLAW_CONFIG_DIR: $resolved_state_dir" ;; esac log "resetting OpenClaw state under $resolved_state_dir" rm -rf \ "$resolved_state_dir/agents" \ "$resolved_state_dir/canvas" \ "$resolved_state_dir/cron" \ "$resolved_state_dir/credentials" \ "$resolved_state_dir/devices" \ "$resolved_state_dir/identity" \ "$resolved_state_dir/logs" \ "$resolved_state_dir/memory" \ "$resolved_state_dir/skills" \ "$resolved_state_dir/workspace" rm -f \ "$resolved_state_dir/openclaw.json" \ "$resolved_state_dir/openclaw.json.bak" \ "$resolved_state_dir/update-check.json" } require_cmd docker require_cmd git require_cmd curl require_cmd openssl require_cmd grep OPENCLAW_REPO_URL="${OPENCLAW_REPO_URL:-https://github.com/openclaw/openclaw.git}" OPENCLAW_DOCKER_DIR="${OPENCLAW_DOCKER_DIR:-/tmp/openclaw-docker}" OPENCLAW_REPO_REF="${OPENCLAW_REPO_REF:-v2026.3.2}" OPENCLAW_IMAGE="${OPENCLAW_IMAGE:-openclaw:local}" OPENCLAW_TMP_DIR="${OPENCLAW_TMP_DIR:-${TMPDIR:-/tmp}}" OPENCLAW_TMP_DIR="${OPENCLAW_TMP_DIR%/}" OPENCLAW_TMP_DIR="${OPENCLAW_TMP_DIR:-/tmp}" OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$OPENCLAW_TMP_DIR/openclaw-paperclip-smoke}" OPENCLAW_WORKSPACE_DIR="${OPENCLAW_WORKSPACE_DIR:-$OPENCLAW_CONFIG_DIR/workspace}" OPENCLAW_GATEWAY_PORT="${OPENCLAW_GATEWAY_PORT:-18789}" OPENCLAW_BRIDGE_PORT="${OPENCLAW_BRIDGE_PORT:-18790}" OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}" OPENCLAW_GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-$(openssl rand -hex 32)}" OPENCLAW_BUILD="${OPENCLAW_BUILD:-1}" OPENCLAW_WAIT_SECONDS="${OPENCLAW_WAIT_SECONDS:-45}" OPENCLAW_OPEN_BROWSER="${OPENCLAW_OPEN_BROWSER:-0}" OPENCLAW_SECRETS_FILE="${OPENCLAW_SECRETS_FILE:-$HOME/.secrets}" # Keep default one-command UX: local smoke run should not require manual pairing. OPENCLAW_DISABLE_DEVICE_AUTH="${OPENCLAW_DISABLE_DEVICE_AUTH:-1}" OPENCLAW_MODEL_PRIMARY="${OPENCLAW_MODEL_PRIMARY:-openai/gpt-5.2}" OPENCLAW_MODEL_FALLBACK="${OPENCLAW_MODEL_FALLBACK:-openai/gpt-5.2-chat-latest}" OPENCLAW_RESET_STATE="${OPENCLAW_RESET_STATE:-1}" PAPERCLIP_HOST_PORT="${PAPERCLIP_HOST_PORT:-3100}" PAPERCLIP_HOST_FROM_CONTAINER="${PAPERCLIP_HOST_FROM_CONTAINER:-host.docker.internal}" case "$OPENCLAW_DISABLE_DEVICE_AUTH" in 1|true|TRUE|True|yes|YES|Yes) OPENCLAW_DISABLE_DEVICE_AUTH_JSON="true" ;; 0|false|FALSE|False|no|NO|No) OPENCLAW_DISABLE_DEVICE_AUTH_JSON="false" ;; *) fail "OPENCLAW_DISABLE_DEVICE_AUTH must be one of: 1,0,true,false,yes,no" ;; esac if [[ -z "${OPENAI_API_KEY:-}" && -f "$OPENCLAW_SECRETS_FILE" ]]; then set +u # shellcheck source=/dev/null source "$OPENCLAW_SECRETS_FILE" set -u fi [[ -n "${OPENAI_API_KEY:-}" ]] || fail "OPENAI_API_KEY is required (set env var or include it in $OPENCLAW_SECRETS_FILE)" log "preparing OpenClaw repo at $OPENCLAW_DOCKER_DIR" if [[ -d "$OPENCLAW_DOCKER_DIR/.git" ]]; then git -C "$OPENCLAW_DOCKER_DIR" fetch --quiet --tags origin || true else rm -rf "$OPENCLAW_DOCKER_DIR" git clone "$OPENCLAW_REPO_URL" "$OPENCLAW_DOCKER_DIR" git -C "$OPENCLAW_DOCKER_DIR" fetch --quiet --tags origin || true fi resolved_openclaw_ref="" if git -C "$OPENCLAW_DOCKER_DIR" rev-parse --verify --quiet "origin/$OPENCLAW_REPO_REF" >/dev/null; then resolved_openclaw_ref="origin/$OPENCLAW_REPO_REF" elif git -C "$OPENCLAW_DOCKER_DIR" rev-parse --verify --quiet "$OPENCLAW_REPO_REF" >/dev/null; then resolved_openclaw_ref="$OPENCLAW_REPO_REF" fi [[ -n "$resolved_openclaw_ref" ]] || fail "unable to resolve OPENCLAW_REPO_REF=$OPENCLAW_REPO_REF in $OPENCLAW_DOCKER_DIR" git -C "$OPENCLAW_DOCKER_DIR" checkout --quiet "$resolved_openclaw_ref" log "using OpenClaw ref $resolved_openclaw_ref ($(git -C "$OPENCLAW_DOCKER_DIR" rev-parse --short HEAD))" if [[ "$OPENCLAW_BUILD" == "1" ]]; then log "building Docker image $OPENCLAW_IMAGE" docker build -t "$OPENCLAW_IMAGE" -f "$OPENCLAW_DOCKER_DIR/Dockerfile" "$OPENCLAW_DOCKER_DIR" fi log "writing OpenClaw config under $OPENCLAW_CONFIG_DIR" if [[ "$OPENCLAW_RESET_STATE" == "1" ]]; then # Ensure deterministic smoke behavior across reruns by restarting with a clean state dir. OPENCLAW_CONFIG_DIR="$OPENCLAW_CONFIG_DIR" \ OPENCLAW_WORKSPACE_DIR="$OPENCLAW_WORKSPACE_DIR" \ OPENCLAW_GATEWAY_PORT="$OPENCLAW_GATEWAY_PORT" \ OPENCLAW_BRIDGE_PORT="$OPENCLAW_BRIDGE_PORT" \ OPENCLAW_GATEWAY_BIND="$OPENCLAW_GATEWAY_BIND" \ OPENCLAW_GATEWAY_TOKEN="$OPENCLAW_GATEWAY_TOKEN" \ OPENCLAW_IMAGE="$OPENCLAW_IMAGE" \ OPENAI_API_KEY="$OPENAI_API_KEY" \ docker compose -f "$OPENCLAW_DOCKER_DIR/docker-compose.yml" down --remove-orphans >/dev/null 2>&1 || true reset_openclaw_state_dir "$OPENCLAW_CONFIG_DIR" fi mkdir -p "$OPENCLAW_WORKSPACE_DIR" "$OPENCLAW_CONFIG_DIR/identity" "$OPENCLAW_CONFIG_DIR/credentials" chmod 700 "$OPENCLAW_CONFIG_DIR" "$OPENCLAW_CONFIG_DIR/credentials" cat > "$OPENCLAW_CONFIG_DIR/openclaw.json" < "$OPENCLAW_DOCKER_DIR/.env" < "$COMPOSE_OVERRIDE" <process.exit(r.ok?0:1)).catch(()=>process.exit(1))" >/dev/null 2>&1; then echo "http://${candidate}:${PAPERCLIP_HOST_PORT}" return 0 fi done return 1 } log "starting OpenClaw gateway container" compose up -d openclaw-gateway log "waiting for gateway health on http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/" READY="0" for _ in $(seq 1 "$OPENCLAW_WAIT_SECONDS"); do code="$(curl -sS -o /dev/null -w "%{http_code}" "http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/" || true)" if [[ "$code" == "200" ]]; then READY="1" break fi sleep 1 done if [[ "$READY" != "1" ]]; then compose logs --tail=100 openclaw-gateway || true fail "gateway did not become healthy in ${OPENCLAW_WAIT_SECONDS}s" fi paperclip_base_url="$(detect_paperclip_base_url || true)" dashboard_output="$(compose run --rm openclaw-cli dashboard --no-open)" dashboard_url="$(grep -Eo 'https?://[^[:space:]]+#token=[^[:space:]]+' <<<"$dashboard_output" | head -n1 || true)" if [[ -z "$dashboard_url" ]]; then dashboard_url="http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/#token=${OPENCLAW_GATEWAY_TOKEN}" fi cat </dev/null 2>&1; then log "opening dashboard in browser" open "$dashboard_url" fi