#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" BACKEND_DIR="$ROOT_DIR/backend" WORKER_DIR="$BACKEND_DIR/worker" WEB_DIR="$BACKEND_DIR/web" PIDS=() cleanup() { local pid for pid in "${PIDS[@]:-}"; do if kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true fi done wait 2>/dev/null || true } trap cleanup EXIT INT TERM load_env() { if [[ -f "$BACKEND_DIR/.env" ]]; then set -a # shellcheck disable=SC1091 source "$BACKEND_DIR/.env" set +a fi export HAIXUN_MONGO_URI="${HAIXUN_MONGO_URI:-mongodb://haixun:CHANGE_ME@127.0.0.1:27017/?authSource=admin}" export HAIXUN_MONGO_DB="${HAIXUN_MONGO_DB:-haixun}" export HAIXUN_REDIS_ADDR="${HAIXUN_REDIS_ADDR:-127.0.0.1:6379}" export HAIXUN_REDIS_PASSWORD="${HAIXUN_REDIS_PASSWORD:-CHANGE_ME}" export HAIXUN_JWT_ACCESS_SECRET="${HAIXUN_JWT_ACCESS_SECRET:-dev-access-secret-change-me}" export HAIXUN_JWT_REFRESH_SECRET="${HAIXUN_JWT_REFRESH_SECRET:-dev-refresh-secret-change-me}" export HAIXUN_WORKER_SECRET="${HAIXUN_WORKER_SECRET:-dev-worker-secret-change-me}" export HAIXUN_SECRETS_KEY="${HAIXUN_SECRETS_KEY:-}" export HAIXUN_BACKEND_URL="${HAIXUN_BACKEND_URL:-http://127.0.0.1:8890}" export HAIXUN_WORKER_POLL_MS="${HAIXUN_WORKER_POLL_MS:-3000}" export HAIXUN_8D_TARGET_SAMPLES="${HAIXUN_8D_TARGET_SAMPLES:-4}" export PLAYWRIGHT_HEADLESS="${PLAYWRIGHT_HEADLESS:-true}" } start() { local name="$1" shift echo "[dev-all] starting $name" "$@" & PIDS+=("$!") } wait_for_http() { local name="$1" local url="$2" local i for i in {1..40}; do if curl -fsS "$url" >/dev/null 2>&1; then echo "[dev-all] ready $name" return 0 fi sleep 0.5 done echo "[dev-all] $name did not become ready: $url" >&2 return 1 } pick_web_port() { python3 - <<'PY' import socket for port in range(5173, 5200): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(0.1) if sock.connect_ex(('127.0.0.1', port)) != 0: print(port) raise SystemExit(0) raise SystemExit('no free Vite port found in 5173..5199') PY } load_env cd "$BACKEND_DIR" mkdir -p bin go build -o bin/gateway gateway.go go build -o bin/worker ./cmd/worker cd "$WORKER_DIR" npm install cd "$WEB_DIR" npm install cd "$BACKEND_DIR" start "gateway" ./bin/gateway -f etc/gateway.yaml wait_for_http "gateway" "http://127.0.0.1:8890/api/v1/health" GO_WORKER_TYPES=( go-demo go-expand-graph go-placement-scan go-scan-viral go-analyze-copy-mission go-expand-copy-mission-graph go-generate-copy-matrix go-generate-copy-draft go-generate-outreach-draft go-generate-topic-matrix go-generate-formula-draft go-generate-rewrite-draft go-refresh-threads-token go-publish-analytics go-refill-publish-inventory ) for worker_type in "${GO_WORKER_TYPES[@]}"; do start "worker:$worker_type" env \ HAIXUN_JOB_WORKER_TYPE="$worker_type" \ HAIXUN_JOB_WORKER_ID="local-$worker_type" \ ./bin/worker -f etc/gateway.worker.yaml done cd "$WORKER_DIR" start "node-tool:keyword-search" env PORT=9101 npm run keyword-search-server start "node-tool:resolve-media" env PORT=9102 npm run resolve-media-server start "node-worker:style-8d" env \ HAIXUN_NODE_WORKER_TYPE=node-style-8d \ HAIXUN_NODE_WORKER_ID=local-node-style-8d \ npm run style-8d wait_for_http "keyword-search tool" "http://127.0.0.1:9101/health" wait_for_http "resolve-media tool" "http://127.0.0.1:9102/health" cd "$WEB_DIR" WEB_PORT="${WEB_PORT:-$(pick_web_port)}" start "web" npm run dev -- --host 0.0.0.0 --port "$WEB_PORT" --strictPort wait_for_http "web" "http://127.0.0.1:$WEB_PORT/" echo echo "[dev-all] all services are running" echo "[dev-all] API: http://127.0.0.1:8890/api/v1/health" echo "[dev-all] Web: http://127.0.0.1:$WEB_PORT/" echo "[dev-all] Press Ctrl+C to stop" wait -n