#!/usr/bin/env bash set -euo pipefail # 一键启动本地 WebUI: # - 后端 FastAPI: 127.0.0.1:8765 # - 前端 Next.js: 127.0.0.1:3000 # - PID 和日志统一写入 .port_sessions,重复执行会清理上一次由本脚本启动的进程。 ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" RUN_DIR="${ROOT_DIR}/.port_sessions" BACKEND_HOST="${CLAW_BACKEND_HOST:-0.0.0.0}" BACKEND_PORT="${CLAW_BACKEND_PORT:-8765}" FRONTEND_HOST="${CLAW_FRONTEND_HOST:-0.0.0.0}" FRONTEND_PORT="${CLAW_FRONTEND_PORT:-3000}" BACKEND_URL="http://${BACKEND_HOST}:${BACKEND_PORT}" FRONTEND_URL="http://${FRONTEND_HOST}:${FRONTEND_PORT}" PYTHON_BIN="${CLAW_PYTHON_BIN:-${ROOT_DIR}/.venv/bin/python}" if [[ ! -x "${PYTHON_BIN}" ]]; then PYTHON_BIN="python3" fi LOCAL_IPV4_ORIGINS="$(ifconfig 2>/dev/null | awk '/inet / && $2 !~ /^127\\./ {print $2}' | awk '!seen[$0]++' | paste -sd, -)" DEFAULT_ALLOWED_DEV_ORIGINS="localhost,127.0.0.1,0.0.0.0" if [[ -n "${LOCAL_IPV4_ORIGINS}" ]]; then DEFAULT_ALLOWED_DEV_ORIGINS="${DEFAULT_ALLOWED_DEV_ORIGINS},${LOCAL_IPV4_ORIGINS}" while IFS= read -r local_ip; do [[ -z "${local_ip}" ]] && continue DEFAULT_ALLOWED_DEV_ORIGINS="${DEFAULT_ALLOWED_DEV_ORIGINS},${local_ip}:${FRONTEND_PORT}" done < <(printf "%s\n" "${LOCAL_IPV4_ORIGINS}" | tr "," "\n") fi mkdir -p "${RUN_DIR}" stop_pid_file() { local pid_file="$1" local name="$2" if [[ ! -f "${pid_file}" ]]; then return fi local pid pid="$(cat "${pid_file}" 2>/dev/null || true)" if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then echo "Stopping previous ${name} (${pid})..." kill "${pid}" 2>/dev/null || true for _ in {1..20}; do if ! kill -0 "${pid}" 2>/dev/null; then break fi sleep 0.1 done if kill -0 "${pid}" 2>/dev/null; then kill -9 "${pid}" 2>/dev/null || true fi fi rm -f "${pid_file}" } stop_port_listener() { local port="$1" local name="$2" local pids pids="$(lsof -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true)" if [[ -z "${pids}" ]]; then return fi echo "Stopping ${name} listener(s) on port ${port}: ${pids//$'\n'/ }..." while IFS= read -r pid; do [[ -z "${pid}" ]] && continue kill "${pid}" 2>/dev/null || true done <<<"${pids}" for _ in {1..20}; do if ! lsof -tiTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1; then return fi sleep 0.1 done while IFS= read -r pid; do [[ -z "${pid}" ]] && continue kill -9 "${pid}" 2>/dev/null || true done <<<"${pids}" } wait_for_url() { local url="$1" local name="$2" local log_file="$3" for _ in {1..80}; do if curl -fsS "${url}" >/dev/null 2>&1; then echo "${name} ready: ${url}" return 0 fi sleep 0.25 done echo "${name} did not become ready: ${url}" >&2 echo "---- ${name} log tail ----" >&2 tail -80 "${log_file}" >&2 || true return 1 } shell_quote() { printf "%q" "$1" } start_detached_process() { local name="$1" local pid_file="$2" local log_file="$3" local work_dir="$4" shift 4 local command="cd $(shell_quote "${work_dir}") && echo \$\$ > $(shell_quote "${pid_file}") && exec" local arg for arg in "$@"; do command+=" $(shell_quote "${arg}")" done command+=" >$(shell_quote "${log_file}") 2>&1" if command -v screen >/dev/null 2>&1; then screen -dmS "claw-${name}" bash -lc "${command}" else ( cd "${work_dir}" nohup "$@" >"${log_file}" 2>&1 & echo "$!" >"${pid_file}" ) fi } stop_pid_file "${RUN_DIR}/webui-frontend.pid" "frontend" stop_pid_file "${RUN_DIR}/webui-backend.pid" "backend" stop_port_listener "${FRONTEND_PORT}" "frontend" stop_port_listener "${BACKEND_PORT}" "backend" export OPENAI_API_KEY="${OPENAI_API_KEY:-sk-Jxoh5lf1jWg6HQZYYyfyagXudGxUXNAgkZklxMnnXHn7pFmU}" export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://model.mify.ai.srv/v1}" export OPENAI_MODEL="${OPENAI_MODEL:-xiaomi/mimo-v2-flash}" BACKEND_LOG="${RUN_DIR}/webui-backend.log" FRONTEND_LOG="${RUN_DIR}/webui-frontend.log" echo "Starting backend on ${BACKEND_URL}..." start_detached_process "backend" "${RUN_DIR}/webui-backend.pid" "${BACKEND_LOG}" "${ROOT_DIR}" \ "${PYTHON_BIN}" -m src.gui \ --host "${BACKEND_HOST}" \ --port "${BACKEND_PORT}" \ --cwd "${ROOT_DIR}" \ --session-dir "${RUN_DIR}/agent" \ --allow-write \ --allow-shell \ --no-browser wait_for_url "${BACKEND_URL}/api/state" "backend" "${BACKEND_LOG}" echo "Starting frontend on ${FRONTEND_URL}..." export CLAW_API_URL="${BACKEND_URL}" export CLAW_ALLOWED_DEV_ORIGINS="${CLAW_ALLOWED_DEV_ORIGINS:-${DEFAULT_ALLOWED_DEV_ORIGINS}}" start_detached_process "frontend" "${RUN_DIR}/webui-frontend.pid" "${FRONTEND_LOG}" "${ROOT_DIR}/frontend/app" \ npm run dev -- --hostname "${FRONTEND_HOST}" --port "${FRONTEND_PORT}" wait_for_url "${FRONTEND_URL}" "frontend" "${FRONTEND_LOG}" cat <