thread-master/scripts/dev-all.sh

212 lines
5.8 KiB
Bash
Raw Normal View History

2026-07-03 14:42:19 +00:00
#!/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"
ENV_FILE="$ROOT_DIR/deploy/.env.dev"
RUN_DIR="$ROOT_DIR/.run"
PID_FILE="$RUN_DIR/dev-all.pids"
mkdir -p "$RUN_DIR"
: > "$PID_FILE"
PIDS=()
cleanup() {
local pid
for pid in "${PIDS[@]:-}"; do
if kill -0 "$pid" 2>/dev/null; then
# Each job was started via `setsid`, so its PID is also its process
# group id; killing the negative pid takes down the whole subtree
# (e.g. npm -> tsx -> node) instead of leaving orphaned children.
kill -TERM -- "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
fi
done
wait 2>/dev/null || true
rm -f "$PID_FILE"
}
trap cleanup EXIT INT TERM
load_env() {
if [[ ! -f "$ENV_FILE" ]]; then
echo "[dev-all] 找不到 $ENV_FILE請先cp deploy/.env.dev.example deploy/.env.dev 並填值" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
: "${HAIXUN_MONGO_URI:?請在 $ENV_FILE 設定 HAIXUN_MONGO_URI}"
: "${HAIXUN_REDIS_ADDR:?請在 $ENV_FILE 設定 HAIXUN_REDIS_ADDR}"
: "${HAIXUN_REDIS_PASSWORD:?請在 $ENV_FILE 設定 HAIXUN_REDIS_PASSWORD}"
: "${HAIXUN_JWT_ACCESS_SECRET:?請在 $ENV_FILE 設定 HAIXUN_JWT_ACCESS_SECRET}"
: "${HAIXUN_JWT_REFRESH_SECRET:?請在 $ENV_FILE 設定 HAIXUN_JWT_REFRESH_SECRET}"
: "${HAIXUN_WORKER_SECRET:?請在 $ENV_FILE 設定 HAIXUN_WORKER_SECRET}"
: "${HAIXUN_MONGO_DB:=haixun}"
: "${HAIXUN_SECRETS_KEY:=}"
: "${HAIXUN_BACKEND_URL:=http://127.0.0.1:8890}"
: "${HAIXUN_WORKER_POLL_MS:=3000}"
: "${HAIXUN_8D_TARGET_SAMPLES:=4}"
: "${PLAYWRIGHT_HEADLESS:=true}"
export HAIXUN_MONGO_URI HAIXUN_MONGO_DB HAIXUN_REDIS_ADDR HAIXUN_REDIS_PASSWORD
export HAIXUN_JWT_ACCESS_SECRET HAIXUN_JWT_REFRESH_SECRET HAIXUN_WORKER_SECRET
export HAIXUN_SECRETS_KEY HAIXUN_BACKEND_URL
export HAIXUN_WORKER_POLL_MS HAIXUN_8D_TARGET_SAMPLES
export THREADS_APP_ID THREADS_APP_SECRET THREADS_REDIRECT_URI THREADS_OAUTH_SUCCESS_REDIRECT
export PLAYWRIGHT_HEADLESS
}
start() {
local name="$1"
shift
echo "[dev-all] starting $name"
# setsid makes the launched process its own session/process-group leader,
# so dev-all-stop.sh can reliably kill it *and* every child it spawns
# (npm -> tsx -> node, etc.) with a single `kill -- -PID`, even if this
# script itself gets killed abruptly and never runs its own cleanup trap.
setsid "$@" &
local pid="$!"
PIDS+=("$pid")
echo "$pid $name" >> "$PID_FILE"
}
port_in_use() {
local port="$1"
ss -tlnp 2>/dev/null | grep -qE ":${port} "
}
free_port() {
local port="$1"
if ! port_in_use "$port"; then
return 0
fi
echo "[dev-all] freeing port :$port"
if command -v fuser >/dev/null 2>&1; then
fuser -k "${port}/tcp" 2>/dev/null || true
else
local line pid
while IFS= read -r line; do
for pid in $(grep -oE 'pid=[0-9]+' <<<"$line" | cut -d= -f2); do
kill "$pid" 2>/dev/null || true
done
done < <(ss -tlnp 2>/dev/null | grep -E ":${port} " || true)
fi
sleep 0.3
}
ensure_http_service() {
local name="$1"
local port="$2"
local health_url="$3"
shift 3
if curl -fsS "$health_url" >/dev/null 2>&1; then
echo "[dev-all] reusing $name (already healthy on :$port)"
return 0
fi
if port_in_use "$port"; then
echo "[dev-all] port :$port occupied but $name unhealthy; restarting"
free_port "$port"
fi
start "$name" "$@"
wait_for_http "$name" "$health_url"
}
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"
ensure_http_service "gateway" 8890 "http://127.0.0.1:8890/api/v1/health" \
./bin/gateway -f etc/gateway.yaml
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"
ensure_http_service "node-tool:keyword-search" 9101 "http://127.0.0.1:9101/health" \
env PORT=9101 npm run keyword-search-server
ensure_http_service "node-tool:resolve-media" 9102 "http://127.0.0.1:9102/health" \
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
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