# syntax=docker/dockerfile:1.7 # enable BuildKit cache mounts # use with: docker buildx build --load . FROM ubuntu:24.04 ARG DEBIAN_FRONTEND=noninteractive ARG OPENCLAW_VERSION ENV OPENCLAW_VERSION=$OPENCLAW_VERSION # ============================================ # Runtime Override Variables # docker run -e VAR=value or docker build --build-arg VAR=value # ============================================ # ----- HuggingFace Core ----- ARG HF_TOKEN ARG OPENCLAW_HF_SPACE_ID ARG OPENCLAW_BACKUP_DATASET_REPO ARG OPENCLAW_RESTORE_DATASET_REPO ARG OPENCLAW_BACKUP_REPO_TYPE ARG OPENCLAW_BACKUP_PATH_PREFIX # ----- Backup Enable/Disable ----- ARG OPENCLAW_BACKUP_ENABLED ARG OPENCLAW_BACKUP_NPM_ENABLED ARG OPENCLAW_RESTORE_NPM_ENABLED # ----- Backup Schedule ----- ARG OPENCLAW_BACKUP_CRON ARG OPENCLAW_INCREMENTAL_BACKUP ARG OPENCLAW_INCREMENTAL_INTERVAL_MINUTES ARG OPENCLAW_FULL_BACKUP_INTERVAL_HOURS ARG OPENCLAW_MAX_INCREMENTAL_BACKUPS # ----- Backup Encryption ----- ARG OPENCLAW_BACKUP_ENCRYPTION_ENABLED ARG OPENCLAW_BACKUP_ENCRYPTION_PASSWORD # ----- Backup Retention & Compression ----- ARG OPENCLAW_BACKUP_KEEP_COUNT ARG OPENCLAW_BACKUP_COMPRESSION_LEVEL ARG OPENCLAW_BACKUP_SPLIT_SIZE ARG OPENCLAW_BACKUP_SIZE_WARNING_MB # ----- Dynamic Backup Strategy ----- ARG OPENCLAW_DYNAMIC_BACKUP ARG OPENCLAW_DYNAMIC_SMALL_THRESHOLD_MB ARG OPENCLAW_DYNAMIC_MEDIUM_THRESHOLD_MB ARG OPENCLAW_DYNAMIC_HIGH_CHANGE_RATE ARG OPENCLAW_DYNAMIC_LOW_CHANGE_RATE ARG OPENCLAW_DYNAMIC_MIN_CHANGED_FILES ARG OPENCLAW_DYNAMIC_MIN_CHANGED_SIZE_KB # ----- Backup Extra Paths ----- ARG OPENCLAW_BACKUP_EXTRA_DIRS ARG OPENCLAW_BACKUP_EXTRA_FILES # ----- Restore & Watchdog ----- ARG OPENCLAW_RESTORE_TIMEOUT ARG WATCHDOG_INTERVAL ARG MAX_BACKUP_AGE_MINUTES ARG FORCE_BACKUP_INTERVAL # ----- SSH & SSHX ----- ARG OPENCLAW_SSH_AGENT_AUTOSTART ARG OPENCLAW_SSHX_AUTO_START # ----- LLM Configuration ----- ARG OPENCLAW_LLM_BASE_URL ARG OPENCLAW_LLM_MODEL # ----- BT Panel ----- ARG BT_PANEL_PORT ARG BT_PANEL_USERNAME ARG BT_PANEL_PASSWORD ARG BT_PANEL_SAFE_PATH ARG BT_PANEL_TIMEZONE # ============================================ # Environment Variables (with defaults) # ============================================ # ----- HuggingFace Core ----- ENV HF_TOKEN=${HF_TOKEN:-} ENV OPENCLAW_HF_SPACE_ID=${OPENCLAW_HF_SPACE_ID:-} ENV OPENCLAW_BACKUP_DATASET_REPO=${OPENCLAW_BACKUP_DATASET_REPO:-} ENV OPENCLAW_RESTORE_DATASET_REPO=${OPENCLAW_RESTORE_DATASET_REPO:-} ENV OPENCLAW_BACKUP_REPO_TYPE=${OPENCLAW_BACKUP_REPO_TYPE:-dataset} ENV OPENCLAW_BACKUP_PATH_PREFIX=${OPENCLAW_BACKUP_PATH_PREFIX:-backups} # ----- Backup Enable/Disable ----- ENV OPENCLAW_BACKUP_ENABLED=${OPENCLAW_BACKUP_ENABLED:-false} ENV OPENCLAW_BACKUP_NPM_ENABLED=${OPENCLAW_BACKUP_NPM_ENABLED:-true} ENV OPENCLAW_RESTORE_NPM_ENABLED=${OPENCLAW_RESTORE_NPM_ENABLED:-true} # ----- Backup Schedule ----- ENV OPENCLAW_BACKUP_CRON=${OPENCLAW_BACKUP_CRON:-"*/5 * * * *"} ENV OPENCLAW_INCREMENTAL_BACKUP=${OPENCLAW_INCREMENTAL_BACKUP:-true} ENV OPENCLAW_INCREMENTAL_INTERVAL_MINUTES=${OPENCLAW_INCREMENTAL_INTERVAL_MINUTES:-5} ENV OPENCLAW_FULL_BACKUP_INTERVAL_HOURS=${OPENCLAW_FULL_BACKUP_INTERVAL_HOURS:-1} ENV OPENCLAW_MAX_INCREMENTAL_BACKUPS=${OPENCLAW_MAX_INCREMENTAL_BACKUPS:-10} # ----- Backup Encryption ----- ENV OPENCLAW_BACKUP_ENCRYPTION_ENABLED=${OPENCLAW_BACKUP_ENCRYPTION_ENABLED:-false} ENV OPENCLAW_BACKUP_ENCRYPTION_PASSWORD=${OPENCLAW_BACKUP_ENCRYPTION_PASSWORD:-} # ----- Backup Retention & Compression ----- ENV OPENCLAW_BACKUP_KEEP_COUNT=${OPENCLAW_BACKUP_KEEP_COUNT:-48} ENV OPENCLAW_BACKUP_COMPRESSION_LEVEL=${OPENCLAW_BACKUP_COMPRESSION_LEVEL:-6} ENV OPENCLAW_BACKUP_SPLIT_SIZE=${OPENCLAW_BACKUP_SPLIT_SIZE:-500M} ENV OPENCLAW_BACKUP_SIZE_WARNING_MB=${OPENCLAW_BACKUP_SIZE_WARNING_MB:-1500} # ----- Dynamic Backup Strategy ----- ENV OPENCLAW_DYNAMIC_BACKUP=${OPENCLAW_DYNAMIC_BACKUP:-true} ENV OPENCLAW_DYNAMIC_SMALL_THRESHOLD_MB=${OPENCLAW_DYNAMIC_SMALL_THRESHOLD_MB:-500} ENV OPENCLAW_DYNAMIC_MEDIUM_THRESHOLD_MB=${OPENCLAW_DYNAMIC_MEDIUM_THRESHOLD_MB:-2000} ENV OPENCLAW_DYNAMIC_HIGH_CHANGE_RATE=${OPENCLAW_DYNAMIC_HIGH_CHANGE_RATE:-10} ENV OPENCLAW_DYNAMIC_LOW_CHANGE_RATE=${OPENCLAW_DYNAMIC_LOW_CHANGE_RATE:-2} ENV OPENCLAW_DYNAMIC_MIN_CHANGED_FILES=${OPENCLAW_DYNAMIC_MIN_CHANGED_FILES:-5} ENV OPENCLAW_DYNAMIC_MIN_CHANGED_SIZE_KB=${OPENCLAW_DYNAMIC_MIN_CHANGED_SIZE_KB:-100} # ----- Backup Extra Paths ----- ENV OPENCLAW_BACKUP_EXTRA_DIRS=${OPENCLAW_BACKUP_EXTRA_DIRS:-} ENV OPENCLAW_BACKUP_EXTRA_FILES=${OPENCLAW_BACKUP_EXTRA_FILES:-} # ----- Restore & Watchdog ----- ENV OPENCLAW_RESTORE_TIMEOUT=${OPENCLAW_RESTORE_TIMEOUT:-5400} ENV WATCHDOG_INTERVAL=${WATCHDOG_INTERVAL:-300} ENV MAX_BACKUP_AGE_MINUTES=${MAX_BACKUP_AGE_MINUTES:-20} ENV FORCE_BACKUP_INTERVAL=${FORCE_BACKUP_INTERVAL:-3600} # ----- SSH & SSHX ----- ENV OPENCLAW_SSH_AGENT_AUTOSTART=${OPENCLAW_SSH_AGENT_AUTOSTART:-true} ENV OPENCLAW_SSHX_AUTO_START=${OPENCLAW_SSHX_AUTO_START:-false} # ----- LLM Configuration ----- ENV OPENCLAW_LLM_BASE_URL=${OPENCLAW_LLM_BASE_URL:-} ENV OPENCLAW_LLM_MODEL=${OPENCLAW_LLM_MODEL:-} # ----- BT Panel ----- ENV BT_PANEL_PORT=${BT_PANEL_PORT:-7860} ENV BT_PANEL_USERNAME=${BT_PANEL_USERNAME:-btadmin} ENV BT_PANEL_PASSWORD=${BT_PANEL_PASSWORD:-} ENV BT_PANEL_SAFE_PATH=${BT_PANEL_SAFE_PATH:-} ENV BT_PANEL_TIMEZONE=${BT_PANEL_TIMEZONE:-Asia/Shanghai} # ============================================ # BuildKit cache mounts for faster builds # ============================================ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/root/.cache/pip,sharing=locked \ --mount=type=cache,target=/root/.go/cache,sharing=locked \ set -eux; \ echo "BuildKit cache mounts enabled" RUN printf 'Acquire::Retries "5";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' \ > /etc/apt/apt.conf.d/80-openclaw-retries && \ set -eux; \ for attempt in 1 2 3 4 5; do \ if apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ cmake \ cron \ curl \ sudo \ supervisor \ g++ \ gh \ git \ lsof \ gnupg \ gosu \ hostname \ jq \ libasound2t64 \ libatk-bridge2.0-0 \ libatk1.0-0 \ libatspi2.0-0 \ libcairo2 \ libcups2t64 \ libdbus-1-3 \ libgbm1 \ libglib2.0-0 \ libnspr4 \ libnss3 \ libpango-1.0-0 \ libx11-6 \ libxcb1 \ libxcomposite1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxkbcommon0 \ libxrandr2 \ make \ neovim \ openssl \ procps \ python3 \ python3-pip \ tar \ unzip \ vim \ wget; then \ break; \ fi; \ if [ "$attempt" -eq 5 ]; then \ exit 1; \ fi; \ echo "apt bootstrap failed on attempt $attempt, retrying..." >&2; \ rm -rf /var/lib/apt/lists/*; \ sleep $((attempt * 5)); \ done; \ rm -rf /var/lib/apt/lists/* # Install Node.js 24 LTS RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \ apt-get install -y --no-install-recommends nodejs && \ node --version && \ npm --version && \ rm -rf /var/lib/apt/lists/* RUN /bin/bash -lc 'set -euo pipefail; \ CFT_VERSION="$(curl -fsSL https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_STABLE)"; \ DEB_ARCH="$(dpkg --print-architecture)"; \ case "$DEB_ARCH" in \ amd64) CFT_ARCH=linux64 ;; \ arm64) CFT_ARCH=linux-arm64 ;; \ *) echo "unsupported architecture for chromium: $DEB_ARCH" >&2; exit 1 ;; \ esac; \ CFT_ZIP="chrome-${CFT_ARCH}.zip"; \ CFT_DIR="chrome-${CFT_ARCH}"; \ CFT_URL="https://storage.googleapis.com/chrome-for-testing-public/${CFT_VERSION}/${CFT_ARCH}/${CFT_ZIP}"; \ curl -fsSL "$CFT_URL" -o "/tmp/${CFT_ZIP}"; \ rm -rf "/opt/${CFT_DIR}"; \ unzip -q "/tmp/${CFT_ZIP}" -d /opt; \ rm -f "/tmp/${CFT_ZIP}"; \ ln -sf "/opt/${CFT_DIR}/chrome" /usr/local/bin/chromium; \ chromium --version >/dev/null' RUN /bin/bash -lc 'set -euo pipefail; \ OPENCLAW_VERSION_VALUE="${OPENCLAW_VERSION:-latest}"; \ HOME=/root curl -fsSL https://openclaw.ai/install.sh | bash -s -- --no-onboard --version "$OPENCLAW_VERSION_VALUE"' RUN /bin/bash -lc 'set -euo pipefail; \ OPENCLAW_BIN="$(command -v openclaw || true)"; \ if [[ -z "$OPENCLAW_BIN" ]] && [[ -x /root/.npm-global/bin/openclaw ]]; then \ OPENCLAW_BIN=/root/.npm-global/bin/openclaw; \ elif [[ -z "$OPENCLAW_BIN" ]] && [[ -x /root/.local/bin/openclaw ]]; then \ OPENCLAW_BIN=/root/.local/bin/openclaw; \ fi; \ if [[ -z "$OPENCLAW_BIN" ]]; then \ echo "openclaw command not found after installer" >&2; \ exit 1; \ fi; \ ln -sf "$OPENCLAW_BIN" /usr/local/bin/openclaw; \ /usr/local/bin/openclaw --help >/dev/null' RUN /bin/bash -lc 'set -euo pipefail; \ if ! command -v npm >/dev/null; then \ echo "npm command not found; cannot install developer CLIs" >&2; \ exit 1; \ fi; \ npm install -g --no-audit --no-fund opencode-ai @openai/codex @anthropic-ai/claude-code @larksuite/cli; \ npx skills add larksuite/cli -y -g; \ NPM_PREFIX="$(npm config get prefix)"; \ NPM_BIN="${NPM_PREFIX%/}/bin"; \ for cmd in opencode codex claude; do \ CLI_BIN="$(command -v "$cmd" || true)"; \ if [[ -z "$CLI_BIN" ]] && [[ -x "$NPM_BIN/$cmd" ]]; then \ CLI_BIN="$NPM_BIN/$cmd"; \ elif [[ -z "$CLI_BIN" ]] && [[ -x /root/.npm-global/bin/"$cmd" ]]; then \ CLI_BIN=/root/.npm-global/bin/"$cmd"; \ elif [[ -z "$CLI_BIN" ]] && [[ -x /root/.local/bin/"$cmd" ]]; then \ CLI_BIN=/root/.local/bin/"$cmd"; \ fi; \ if [[ -z "$CLI_BIN" ]]; then \ echo "$cmd command not found after npm install" >&2; \ exit 1; \ fi; \ ln -sf "$CLI_BIN" "/usr/local/bin/$cmd"; \ "/usr/local/bin/$cmd" --help >/dev/null; \ done' # Install PM2 for process management RUN npm install -g --no-audit --no-fund pm2 && pm2 --version RUN python3 -m pip install --no-cache-dir --break-system-packages "huggingface_hub[cli]>=0.31.1" "uv>=0.6.0" && \ hf --help >/dev/null && \ uv --version >/dev/null RUN python3 --version >/dev/null && \ gh --version >/dev/null && \ nvim --version >/dev/null && \ command -v chromium >/dev/null RUN HOME=/root /bin/bash -lc "curl -sSf https://sshx.io/get | sh" && \ if [ -x /root/.local/bin/sshx ]; then ln -sf /root/.local/bin/sshx /usr/local/bin/sshx; fi && \ sshx --help >/dev/null # Install DDNS-GO for dynamic DNS updates (non-critical, don't fail build) RUN /bin/bash -lc ' \ DDNS_VERSION="$(curl -fsSL https://api.github.com/repos/jeessy2/ddns-go/releases/latest 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)[\"tag_name\"])" 2>/dev/null || echo "v6.1.0")"; \ DEB_ARCH="$(dpkg --print-architecture)"; \ case "$DEB_ARCH" in \ amd64) DDNS_ARCH=amd64 ;; \ arm64) DDNS_ARCH=arm64 ;; \ *) echo "unsupported architecture for ddns-go: $DEB_ARCH" >&2; exit 0 ;; \ esac; \ curl -fsSL "https://github.com/jeessy2/ddns-go/releases/download/${DDNS_VERSION}/ddns-go_${DDNS_VERSION#v}_linux_${DDNS_ARCH}.tar.gz" -o /tmp/ddns-go.tar.gz && \ tar -xzf /tmp/ddns-go.tar.gz -C /tmp && \ mv /tmp/ddns-go /usr/local/bin/ddns-go && \ chmod +x /usr/local/bin/ddns-go && \ rm -f /tmp/ddns-go.tar.gz && \ ddns-go --version || true' WORKDIR /root # Copy all scripts and configs first COPY scripts/openclaw-entrypoint.sh /usr/local/bin/openclaw-entrypoint.sh COPY scripts/openclaw-backup-cron.sh /usr/local/bin/openclaw-backup-cron.sh COPY scripts/openclaw-backup-health.sh /usr/local/bin/openclaw-backup-health.sh COPY scripts/openclaw-backup-watchdog.sh /usr/local/bin/openclaw-backup-watchdog.sh COPY scripts/openclaw-restore.sh /usr/local/bin/openclaw-restore.sh COPY scripts/openclaw-gateway-ctl /usr/local/bin/openclaw-gateway-ctl COPY scripts/hf-entrypoint.sh /usr/local/bin/hf-entrypoint.sh COPY scripts/hf-storage.sh /usr/local/bin/hf-storage.sh COPY scripts/hf-storage.py /usr/local/bin/hf-storage.py COPY scripts/ssh-agent-autostart.sh /usr/local/bin/ssh-agent-autostart.sh COPY scripts/save-env.sh /usr/local/bin/save-env.sh COPY scripts/update-env-from-secrets.sh /usr/local/bin/update-env-from-secrets.sh COPY scripts/openclaw-env-sync.sh /usr/local/bin/openclaw-env-sync.sh COPY scripts/supervisord.conf /etc/supervisor/supervisord.conf COPY pm2/ecosystem.config.js /app/pm2/ecosystem.config.js COPY scripts/server.js /app/hf-server.js COPY openclaw_hf /opt/openclaw-hf/openclaw_hf # Set execute permissions on all scripts RUN chmod 755 /usr/local/bin/openclaw-entrypoint.sh \ /usr/local/bin/openclaw-backup-cron.sh \ /usr/local/bin/openclaw-backup-health.sh \ /usr/local/bin/openclaw-backup-watchdog.sh \ /usr/local/bin/openclaw-restore.sh \ /usr/local/bin/openclaw-gateway-ctl \ /usr/local/bin/hf-entrypoint.sh \ /usr/local/bin/hf-storage.sh \ /usr/local/bin/hf-storage.py \ /usr/local/bin/ssh-agent-autostart.sh \ /usr/local/bin/save-env.sh \ /usr/local/bin/update-env-from-secrets.sh \ /usr/local/bin/openclaw-env-sync.sh && \ mkdir -p /root/.openclaw/workspace /var/log/openclaw /root/.pm2 /var/log/supervisor /var/run /root/.ssh && \ chmod 700 /root/.ssh ENV NODE_ENV=production ENV PM2_HOME=/root/.pm2 ENV PYTHONUNBUFFERED=1 ENV HOME=/root ENV PATH="/usr/local/go/bin:/root/.cargo/bin:$PATH" ENV GOPROXY=https://goproxy.cn,direct ENV OPENCLAW_HOME=/root ENV OPENCLAW_USER=root ENV OPENCLAW_GROUP=root ENV OPENCLAW_STATE_DIR=/root/.openclaw ENV OPENCLAW_WORKSPACE_DIR=/root/.openclaw/workspace ENV OPENCLAW_CONFIG_PATH=/root/.openclaw/openclaw.json ENV OPENCLAW_GATEWAY_BIND=lan ENV OPENCLAW_GATEWAY_PORT=18789 ENV OPENCLAW_INIT_GATEWAY_MODE=local ENV OPENCLAW_GATEWAY_AUTH_MODE=token ENV OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_ALLOW_HOST_HEADER_ORIGIN_FALLBACK=true ENV OPENCLAW_GATEWAY_CONTROLUI_ALLOW_INSECURE_AUTH=false ENV OPENCLAW_GATEWAY_CONTROLUI_DANGEROUSLY_DISABLE_DEVICE_AUTH=false ENV OPENCLAW_LLM_PROVIDER=thirdparty ENV OPENCLAW_LLM_API=openai-completions ENV OPENCLAW_BACKUP_SOURCE_DIR=/root/.openclaw ENV OPENCLAW_BACKUP_ROOT_CONFIG_DIR=/root/.config ENV OPENCLAW_BACKUP_ROOT_CODEX_DIR=/root/.codex ENV OPENCLAW_BACKUP_ROOT_CLAUDE_DIR=/root/.claude ENV OPENCLAW_BACKUP_ROOT_AGENTS_DIR=/root/.agents ENV OPENCLAW_BACKUP_ROOT_SSH_DIR=/root/.ssh ENV OPENCLAW_BACKUP_ROOT_ENV_DIR=/root/.env.d ENV OPENCLAW_BACKUP_ROOT_NPM_DIR=/root/.npm ENV OPENCLAW_BACKUP_ROOT_LARK_CLI_DIR=/root/.lark-cli ENV OPENCLAW_BACKUP_PRIVATE=true # Backup health check configuration ENV OPENCLAW_BACKUP_HEALTH_CHECK_ENABLED=false ENV OPENCLAW_BACKUP_HEALTH_CHECK_BEFORE=false ENV OPENCLAW_BACKUP_HEALTH_CHECK_AFTER=false ENV OPENCLAW_BACKUP_MAX_RETRIES=3 ENV OPENCLAW_MAX_BACKUP_AGE_MINUTES=90 ENV OPENCLAW_MAX_FAILED_ATTEMPTS=3 # ============================================ # Labels # ============================================ ARG BUILD_VERSION ENV BUILD_VERSION=${BUILD_VERSION:-latest} LABEL org.opencontainers.image.title="GGSheng ClawCopilot HF Space" LABEL org.opencontainers.image.description="GGSheng ClawCopilot with BT Panel on HuggingFace Spaces" LABEL org.opencontainers.image.version="${BUILD_VERSION:-latest}" LABEL org.opencontainers.image.authors="GGSheng ClawCopilot Team" LABEL org.opencontainers.image.source="https://github.com/ClawCopilot/Openclaw-Hf-Docker" # ============================================ # Healthcheck # ============================================ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD curl -f http://localhost:7860 >/dev/null 2>&1 || exit 1 # BT Panel default port (使用变量支持构建时自定义) EXPOSE ${BT_PANEL_PORT:-7860} # Openclaw default gateway port (使用变量支持构建时自定义) EXPOSE ${OPENCLAW_GATEWAY_PORT:-18789} # Install BT Panel (宝塔面板) with custom root route # 使用本地修改后的宝塔源码和依赖 COPY scripts/bt_install_panel_custom.sh /tmp/bt-install-panel.sh COPY bt-source/panel /www/server/panel COPY bt-source/init/bt6.init /www/server/panel/install/src/bt6.init COPY bt-source/init/bt7.init /www/server/panel/install/src/bt7.init COPY bt-source/install/public.sh /www/server/panel/install/public.sh COPY bt-source/conf/softList.conf /www/server/panel/install/conf/softList.conf COPY index.html /www/server/panel/index.html RUN chmod 755 /tmp/bt-install-panel.sh && \ echo "[BT-PANEL] 开始自动化安装..." && \ echo "[BT-PANEL] 使用本地修改后的源码和依赖" && \ echo " - PANEL_PORT: ${BT_PANEL_PORT:-7860}" && \ export PANEL_PORT="${BT_PANEL_PORT:-7860}" && \ export PANEL_USER="${BT_PANEL_USERNAME:-bt}" && \ export PANEL_PASSWORD="${BT_PANEL_PASSWORD:-}" && \ export SAFE_PATH="${BT_PANEL_SAFE_PATH:-}" && \ export BT_TIMEZONE="${BT_PANEL_TIMEZONE:-Asia/Shanghai}" && \ export go=y && \ export yes=yes && \ bash /tmp/bt-install-panel.sh; \ rm -f /tmp/bt-install-panel.sh # HF Spaces entrypoint: bt-panel as PID 1, PM2 manages openclaw ENTRYPOINT ["/usr/local/bin/hf-entrypoint.sh"] CMD []