Files
Cyrene_v2/scripts/ctrl.sh
T
Aska 5dfaa75a06
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(scripts): ctrl.sh restart 不再挂起超时——启动后 disown 剔除后台 job,脚本立即返回
2026-09-08 02:34:56 +08:00

140 lines
4.0 KiB
Bash

#!/usr/bin/env bash
# Cyrene v2 本地一键控制:启动 / 重启 / 终止 后台 cyrene 进程。
#
# 用法:
# bash scripts/ctrl.sh # 启动(默认)
# bash scripts/ctrl.sh start # 启动
# bash scripts/ctrl.sh restart # 重启
# bash scripts/ctrl.sh stop # 终止
# bash scripts/ctrl.sh status # 查看状态
#
# 说明:
# - 管理单个 cyrene 后端进程(单二进制)。llama-server / PG 若需一并管理,
# 可另用 docker / scripts/dev-up.sh。
# - cyrene 在仓库根下运行(读 .env 的 PORT / DATA_DIR 等),日志写 ~/cy.log。
# - 用 setsid + nohup 脱离进程组,避免终端/后台 shell 超时误杀。
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
APP_BIN="$HOME/apps/cyrene/cyrene"
PID_FILE="$HOME/.cyrene.pid"
LOG_FILE="$HOME/cy.log"
# 端口优先读 .env 的 PORT(用户可改),找不到才回退 10080
GW_PORT="$(grep -E '^PORT=' "$REPO/.env" 2>/dev/null | head -1 | cut -d= -f2)"
GW_PORT="${GW_PORT:-10080}"
HEALTH_URL="http://127.0.0.1:$GW_PORT/api/v1/health"
# 读取进程 PID:先看 PID 文件,没有则从监听端口探测(能识别外部已起的 cyrene)
read_pid() {
# 1) PID 文件优先
if [ -f "$PID_FILE" ]; then
local pid
pid="$(cat "$PID_FILE" 2>/dev/null)"
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
echo "$pid"
return 0
fi
fi
# 2) 端口探测兜底(ss 找监听 GW_PORT 的 cyrene 进程)
local p
p="$(ss -tlnp 2>/dev/null | grep ":$GW_PORT" | grep -oE 'pid=[0-9]+' | head -1 | sed -E 's/[^0-9]//g')"
if [ -n "$p" ] && kill -0 "$p" 2>/dev/null; then
echo "$p"
return 0
fi
return 1
}
is_running() {
read_pid >/dev/null 2>&1
}
# 健康检查:进程在 + HTTP 200
healthy() {
read_pid >/dev/null 2>&1 && curl -s --max-time 2 "$HEALTH_URL" >/dev/null 2>&1
}
start() {
if is_running; then
echo "cyrene 已在运行 (PID $(read_pid)),跳过。用 restart 重启。"
return 0
fi
[ -x "$APP_BIN" ] || { echo "未找到二进制 $APP_BIN,请先 go build"; return 1; }
echo "启动 cyrene → $APP_BIN (端口 $GW_PORT)"
# setsid 只脱离会话,但 cyrene 仍是本 bash 的后台 job;脚本退出时 bash 会等待
# 它结束,导致 restart 命令"挂起超时"。disown 把它从作业表剔除,脚本立即返回。
cd "$REPO" || return 1
setsid nohup "$APP_BIN" > "$LOG_FILE" 2>&1 < /dev/null &
disown
# 等待健康
local i
for i in $(seq 1 20); do
sleep 0.5
if healthy; then
echo "已启动: http://localhost:$GW_PORT (日志 $LOG_FILE)"
return 0
fi
done
echo "启动后 ${GW_PORT} 未就绪,请查看日志 $LOG_FILE"
return 1
}
stop() {
local pid
if read_pid >/dev/null 2>&1; then
pid="$(read_pid)"
kill "$pid" 2>/dev/null || true
echo "已发送终止信号 → PID $pid"
local i
for i in $(seq 1 20); do
sleep 0.4
kill -0 "$pid" 2>/dev/null || { echo "已退出"; rm -f "$PID_FILE"; return 0; }
done
echo "进程未在 8s 内退出,强制 kill -9"
kill -9 "$pid" 2>/dev/null || true
rm -f "$PID_FILE"
else
echo "cyrene 未在运行(无 PID 文件或进程已退出)"
rm -f "$PID_FILE"
fi
}
restart() {
echo "重启 cyrene…"
stop
sleep 1
start
}
status() {
if healthy; then
echo "cyrene: 运行中 (PID $(read_pid)) · 端口 $GW_PORT · health ok"
elif is_running; then
echo "cyrene: 进程在 (PID $(read_pid)) 但 health 未就绪 ($HEALTH_URL)"
else
echo "cyrene: 未运行"
fi
}
usage() {
echo "用法: bash scripts/ctrl.sh [start|stop|restart|status] (无参 = start)"
}
# start 成功后把探测到的 PID 写入文件,供后续 stop 精确管理
record_pid() {
if healthy; then
local p
p="$(ss -tlnp 2>/dev/null | grep ":$GW_PORT" | grep -oE 'pid=[0-9]+' | head -1 | sed -E 's/[^0-9]//g')"
[ -n "$p" ] && echo "$p" > "$PID_FILE"
fi
}
case "${1:-start}" in
start) start; record_pid ;;
restart) restart; record_pid ;;
stop) stop ;;
status) status ;;
*) usage; exit 1 ;;
esac