#!/usr/bin/env bash if [ -z "${BASH_VERSION:-}" ]; then if command -v bash >/dev/null 2>&1; then echo "[INFO] start_dev_10231_10232.sh requires bash. Re-running with bash ..." exec bash "$0" "$@" fi echo "[FATAL] bash is required but was not found on PATH." >&2 echo " Please run: bash ./start_dev_10231_10232.sh" >&2 exit 1 fi set -euo pipefail FORCE=0 SKIP_PREFLIGHT=0 SKIP_SMOKE=0 SKIP_BUILD=0 while [[ $# -gt 0 ]]; do case "$1" in --force|-f) FORCE=1 ;; --skip-preflight) SKIP_PREFLIGHT=1 ;; --skip-smoke) SKIP_SMOKE=1 ;; --skip-build) SKIP_BUILD=1 ;; --help|-h) cat <<'EOF' Usage: ./start_dev_10231_10232.sh [options] Options: --force Kill whatever holds the ports, even non-project processes (a previous run of this script is always stopped and replaced automatically) --skip-preflight Skip preflight checks --skip-smoke Skip admin account smoke login --skip-build Do not rebuild the backend, even if sources are newer --help Show this help EOF exit 0 ;; *) echo "[FATAL] Unknown option: $1" >&2 exit 1 ;; esac shift done # ================================================================ # 端口说明 # 10231 - 前端 Vite 开发服务器 (dev only) # 10232 - 后端 Go API 服务 (dev only) # 8080 - 外部 LLM 服务 (llamacpp) — AI 对话 API 入口 # 8081 - 外部 Embed 服务 (llamacpp) — 向量嵌入服务 # 11434 - 外部 Ollama 服务 (如使用 Ollama 路由) # ================================================================ BACKEND_PORT=10232 FRONTEND_PORT=10231 REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_DIR="$REPO_DIR/eai_agentplatform" BACKEND_DIR="$APP_DIR/backend-go" BACKEND_BIN="$BACKEND_DIR/bin/eai_agentplatform-server" FRONTEND_DIR="$APP_DIR/frontend" DEV_BIND_HOST="${EAI_DEV_BIND_HOST:-127.0.0.1}" DEBUG_LOG_DIR="$REPO_DIR/debuglog" mkdir -p "$DEBUG_LOG_DIR" RUN_ID="$(date +%Y%m%d_%H%M%S)" BACKEND_LOG="$DEBUG_LOG_DIR/backend_${RUN_ID}.log" FRONTEND_LOG="$DEBUG_LOG_DIR/frontend_${RUN_ID}.log" BACKEND_PID_FILE="$DEBUG_LOG_DIR/backend.pid" FRONTEND_PID_FILE="$DEBUG_LOG_DIR/frontend.pid" check() { local label="$1" local fix_hint="$2" shift 2 printf ' [.] %s ...' "$label" if "$@" >/dev/null 2>&1; then printf ' OK\n' return 0 fi printf ' FAIL\n' if [[ -n "$fix_hint" ]]; then printf ' fix: %s\n' "$fix_hint" fi return 1 } wait_health() { local url="$1" local label="$2" local timeout_sec="${3:-60}" local elapsed=0 printf ' [.] waiting on %s (%s) ...' "$label" "$url" while (( elapsed < timeout_sec )); do if curl -fsS --max-time 2 "$url" >/dev/null 2>&1; then printf ' READY\n' return 0 fi sleep 1 elapsed=$((elapsed + 1)) printf '.' done printf ' TIMEOUT\n' return 1 } detect_public_host() { local candidate for candidate in $(hostname -I 2>/dev/null || true); do case "$candidate" in 127.*|172.17.*) continue ;; *) echo "$candidate" return 0 ;; esac done echo "127.0.0.1" } port_pids() { local port="$1" if command -v lsof >/dev/null 2>&1; then lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null | sort -u return 0 fi if command -v ss >/dev/null 2>&1; then ss -ltnp "( sport = :$port )" 2>/dev/null | awk -F'pid=' 'NR>1 && NF>1 {split($2,a,","); print a[1]}' | sort -u return 0 fi return 1 } # Decide whether a process belongs to this project. # # This must look at the full command line, not `ps -o comm=`: comm is capped at # 15 characters (the kernel's TASK_COMM_LEN), so the backend reports itself as # "eai_agentplatfo" and could never match its own name. That made the launcher # treat its own backend as a foreign process and refuse to restart. process_is_ours() { local pid="$1" local port="$2" local args args="$(ps -o args= -p "$pid" 2>/dev/null || true)" [[ -z "$args" ]] && return 1 [[ "$args" == *"$BACKEND_BIN"* ]] && return 0 [[ "$args" == *vite* && "$args" == *"--port $port"* ]] && return 0 return 1 } process_label() { local pid="$1" local args args="$(ps -o args= -p "$pid" 2>/dev/null || true)" if [[ -z "$args" ]]; then printf 'unknown' return 0 fi args="${args%% *}" printf '%s' "${args##*/}" } # Stop the run recorded by a previous invocation. Uses the pid files so a # previous instance still gets cleaned up even if it is not holding its port. kill_previous_run() { local pid_file="$1" local port="$2" local label="$3" local pid [[ -f "$pid_file" ]] || return 0 pid="$(cat "$pid_file" 2>/dev/null || true)" [[ "$pid" =~ ^[0-9]+$ ]] || return 0 kill -0 "$pid" 2>/dev/null || return 0 process_is_ours "$pid" "$port" || return 0 printf ' [.] stopping previous %s (PID %s)\n' "$label" "$pid" kill "$pid" 2>/dev/null || true sleep 1 kill -9 "$pid" 2>/dev/null || true return 0 } resolve_port() { local port="$1" local label="$2" local pids mapfile -t pids < <(port_pids "$port" || true) if [[ ${#pids[@]} -eq 0 ]]; then printf ' [.] %s :%s free\n' "$label" "$port" return 0 fi printf ' [!] %s :%s in use by ' "$label" "$port" for pid in "${pids[@]}"; do printf '%s(PID %s) ' "$(process_label "$pid")" "$pid" done printf '\n' for pid in "${pids[@]}"; do if [[ "$FORCE" -eq 1 ]] || process_is_ours "$pid" "$port"; then printf ' killing previous %s(PID %s)\n' "$(process_label "$pid")" "$pid" kill "$pid" 2>/dev/null || true sleep 1 kill -9 "$pid" 2>/dev/null || true else printf ' keeping non-project process: %s(PID %s)\n' "$(process_label "$pid")" "$pid" printf ' fix: stop that process manually, or rerun with --force\n' return 1 fi done mapfile -t pids < <(port_pids "$port" || true) if [[ ${#pids[@]} -gt 0 ]]; then printf ' [FAIL] %s :%s still in use\n' "$label" "$port" return 1 fi printf ' [OK] %s :%s released\n' "$label" "$port" return 0 } # True when the backend binary is missing or older than its sources. # # Preflight only proves the binary exists, which happily launches stale code # after a source edit — the failure is silent, so it needs an explicit check. backend_needs_build() { [[ -f "$BACKEND_BIN" ]] || return 0 local newer newer="$(find "$BACKEND_DIR" -name '*.go' -newer "$BACKEND_BIN" -print -quit 2>/dev/null || true)" [[ -n "$newer" ]] && return 0 local f for f in "$BACKEND_DIR/go.mod" "$BACKEND_DIR/go.sum"; do [[ -f "$f" && "$f" -nt "$BACKEND_BIN" ]] && return 0 done return 1 } build_backend() { local build_log="$DEBUG_LOG_DIR/backend_build.log" if ! command -v go >/dev/null 2>&1; then printf ' [FAIL] backend sources changed, but go is not on PATH\n' printf ' fix: install go, or rebuild manually then rerun with --skip-build\n' return 1 fi printf ' [.] backend sources newer than binary, rebuilding ...' if ( cd "$BACKEND_DIR" && go build -o bin/eai_agentplatform-server ./cmd/server ) >"$build_log" 2>&1; then printf ' OK\n' return 0 fi printf ' FAIL\n' printf ' log : %s\n' "$build_log" tail -n 20 "$build_log" 2>/dev/null | sed 's/^/ /' || true return 1 } cleanup_started() { if [[ -f "$BACKEND_PID_FILE" ]]; then kill "$(cat "$BACKEND_PID_FILE")" 2>/dev/null || true fi if [[ -f "$FRONTEND_PID_FILE" ]]; then kill "$(cat "$FRONTEND_PID_FILE")" 2>/dev/null || true fi } echo "==> eai_agentplatform dev launcher" echo " repo : $REPO_DIR" echo " backend : $BACKEND_DIR (port $BACKEND_PORT)" echo " frontend : $FRONTEND_DIR (port $FRONTEND_PORT)" echo " bind host: $DEV_BIND_HOST" echo if [[ "${EAI_DEV_PUBLIC_HOST:-}" != "" ]]; then DEV_PUBLIC_HOST="$EAI_DEV_PUBLIC_HOST" elif [[ "$DEV_BIND_HOST" == "0.0.0.0" ]]; then DEV_PUBLIC_HOST="$(detect_public_host)" else DEV_PUBLIC_HOST="$DEV_BIND_HOST" fi if [[ "$SKIP_PREFLIGHT" -eq 0 ]]; then echo "[1/4] preflight checks" all_ok=1 check "backend dir exists" "run from repo root" test -d "$BACKEND_DIR" || all_ok=0 check "frontend dir exists" "run from repo root" test -d "$FRONTEND_DIR" || all_ok=0 check "frontend node_modules" "run npm install in frontend/" test -e "$FRONTEND_DIR/node_modules" || all_ok=0 check "node on PATH" "install nodejs" command -v node || all_ok=0 check "npm on PATH" "install npm" command -v npm || all_ok=0 check "curl on PATH" "install curl" command -v curl || all_ok=0 if [[ "$all_ok" -eq 0 ]]; then echo echo "[ABORT] preflight failed." exit 1 fi echo else echo "[1/4] preflight skipped (--skip-preflight)" echo fi # Runs whether or not preflight did: a stale binary is a silent failure, and # building before the old services are stopped means a compile error leaves the # currently running stack untouched. if [[ "$SKIP_BUILD" -eq 1 ]]; then echo "[1/4] backend build check skipped (--skip-build)" echo elif backend_needs_build; then if ! build_backend; then echo echo "[ABORT] backend build failed; services left as they were." exit 1 fi echo else echo "[1/4] backend binary up to date" echo fi echo "[2/4] port conflict check" kill_previous_run "$BACKEND_PID_FILE" "$BACKEND_PORT" "backend" kill_previous_run "$FRONTEND_PID_FILE" "$FRONTEND_PORT" "frontend" resolve_port "$BACKEND_PORT" "backend" || exit 1 resolve_port "$FRONTEND_PORT" "frontend" || exit 1 echo trap cleanup_started EXIT echo "[3/4] launch services" echo " [.] starting backend (eai_agentplatform-server :$BACKEND_PORT)" echo " [.] log : $BACKEND_LOG" nohup env PORT="$BACKEND_PORT" "$BACKEND_BIN" >>"$BACKEND_LOG" 2>&1 "$BACKEND_PID_FILE" if ! wait_health "http://127.0.0.1:$BACKEND_PORT/api/health" "backend"; then echo echo "[ABORT] backend did not become healthy, frontend not started." echo " check: $BACKEND_LOG" exit 1 fi echo " [.] starting frontend (vite :$FRONTEND_PORT)" echo " [.] log : $FRONTEND_LOG" nohup bash -lc "cd \"$FRONTEND_DIR\" && exec node ./node_modules/vite/bin/vite.js --host \"$DEV_BIND_HOST\" --port \"$FRONTEND_PORT\"" >>"$FRONTEND_LOG" 2>&1 "$FRONTEND_PID_FILE" echo echo "[4/4] health check + smoke login" f_ok=1 if ! wait_health "http://127.0.0.1:$FRONTEND_PORT/" "frontend"; then f_ok=0 fi if [[ "$SKIP_SMOKE" -eq 0 && "$f_ok" -eq 1 ]]; then echo echo " [.] smoke login admin account ..." payload='{"username":"admin","password":"admin123"}' status_code="$(curl -sS -o /dev/null -w '%{http_code}' \ -H 'Content-Type: application/json' \ -d "$payload" \ "http://127.0.0.1:$BACKEND_PORT/api/auth/login" || true)" if [[ "$status_code" == "200" ]]; then printf ' [OK] admin login success\n' else printf ' [FAIL] admin login (HTTP %s)\n' "$status_code" fi else echo " [.] smoke login skipped" fi echo echo "=================================================================" echo " Service status" echo "=================================================================" echo " backend : http://$DEV_PUBLIC_HOST:$BACKEND_PORT" echo " frontend : http://$DEV_PUBLIC_HOST:$FRONTEND_PORT" echo " backend log : $BACKEND_LOG" echo " frontend log: $FRONTEND_LOG" echo " PIDs: backend=$BACKEND_PID frontend=$FRONTEND_PID" echo "=================================================================" echo trap - EXIT echo "Usage: ./start_dev_10231_10232.sh [--force] [--skip-preflight] [--skip-build]" echo " env: EAI_DEV_BIND_HOST=0.0.0.0 (listen all)" echo " env: EAI_DEV_PUBLIC_HOST=x.x.x.x (public url)" echo