Files
gpt-register-oss/dev_services.sh
2026-04-05 10:23:02 +08:00

399 lines
8.2 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -u
set -o pipefail
PROJECT_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
RUNTIME_DIR="$PROJECT_ROOT/logs/dev-services"
PID_DIR="$RUNTIME_DIR/pids"
SERVICES=(backend frontend)
FG_PIDS=()
FG_NAMES=()
CLEANED_UP=0
mkdir -p "$PID_DIR"
usage() {
cat <<'EOF'
用法:
./dev_services.sh fg 前台启动两个服务,按 Ctrl+C 一键关闭
./dev_services.sh bg 后台启动两个服务
./dev_services.sh stop 停止由本脚本后台启动的两个服务
./dev_services.sh restart 重启后台服务
./dev_services.sh status 查看后台服务状态
说明:
- 后台模式日志目录: logs/dev-services/
- 后台模式 PID 目录: logs/dev-services/pids/
EOF
}
service_title() {
case "$1" in
backend) printf '%s' "backend" ;;
frontend) printf '%s' "frontend" ;;
*) printf '%s' "$1" ;;
esac
}
service_log_file() {
printf '%s/%s.log' "$RUNTIME_DIR" "$1"
}
service_pid_file() {
printf '%s/%s.pid' "$PID_DIR" "$1"
}
service_command() {
local service="$1"
local cmd=""
case "$service" in
backend)
printf -v cmd 'cd %q && exec %q api_server.py' "$PROJECT_ROOT" "$PROJECT_ROOT/.venv/bin/python"
;;
frontend)
if [[ -x "$PROJECT_ROOT/frontend/node_modules/.bin/vite" ]]; then
printf -v cmd 'cd %q && exec %q' "$PROJECT_ROOT/frontend" "$PROJECT_ROOT/frontend/node_modules/.bin/vite"
else
printf -v cmd 'cd %q && exec pnpm run dev' "$PROJECT_ROOT/frontend"
fi
;;
*)
echo "未知服务: $service" >&2
return 1
;;
esac
printf '%s' "$cmd"
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "缺少命令: $1" >&2
exit 1
fi
}
check_dependencies() {
require_command bash
if [[ ! -x "$PROJECT_ROOT/.venv/bin/python" ]]; then
echo "缺少 Python 解释器: $PROJECT_ROOT/.venv/bin/python" >&2
exit 1
fi
if [[ ! -d "$PROJECT_ROOT/frontend" ]]; then
echo "缺少前端目录: $PROJECT_ROOT/frontend" >&2
exit 1
fi
if [[ ! -x "$PROJECT_ROOT/frontend/node_modules/.bin/vite" ]]; then
require_command pnpm
fi
}
is_pid_running() {
local pid="$1"
[[ "$pid" =~ ^[0-9]+$ ]] || return 1
kill -0 "$pid" 2>/dev/null
}
service_pid() {
local pid_file
pid_file="$(service_pid_file "$1")"
[[ -f "$pid_file" ]] || return 1
local pid
pid="$(tr -d '[:space:]' <"$pid_file")"
[[ -n "$pid" ]] || return 1
printf '%s' "$pid"
}
service_running() {
local pid
pid="$(service_pid "$1")" || return 1
is_pid_running "$pid"
}
clear_stale_pid() {
local service="$1"
local pid_file
pid_file="$(service_pid_file "$service")"
if [[ ! -f "$pid_file" ]]; then
return 0
fi
local pid
pid="$(tr -d '[:space:]' <"$pid_file")"
if ! is_pid_running "$pid"; then
rm -f "$pid_file"
fi
}
kill_pid_group() {
local pid="$1"
kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
}
force_kill_pid_group() {
local pid="$1"
kill -KILL -- "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
}
stop_service() {
local service="$1"
clear_stale_pid "$service"
local pid
pid="$(service_pid "$service")" || return 0
if ! is_pid_running "$pid"; then
rm -f "$(service_pid_file "$service")"
return 0
fi
printf '停止 %-12s pid=%s\n' "$(service_title "$service")" "$pid"
kill_pid_group "$pid"
local i
for i in $(seq 1 20); do
if ! is_pid_running "$pid"; then
rm -f "$(service_pid_file "$service")"
return 0
fi
sleep 0.5
done
force_kill_pid_group "$pid"
rm -f "$(service_pid_file "$service")"
}
ensure_no_managed_services_running() {
local busy=0
local service
for service in "${SERVICES[@]}"; do
clear_stale_pid "$service"
if service_running "$service"; then
local pid
pid="$(service_pid "$service")"
printf '%-12s 已在运行 pid=%s请先执行 ./dev_services.sh stop\n' "$(service_title "$service")" "$pid" >&2
busy=1
fi
done
if (( busy != 0 )); then
exit 1
fi
}
start_service_background() {
local service="$1"
local cmd
cmd="$(service_command "$service")"
local log_file pid_file
log_file="$(service_log_file "$service")"
pid_file="$(service_pid_file "$service")"
{
printf '\n[%s] starting %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$(service_title "$service")"
printf '[%s] command: %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$cmd"
} >>"$log_file"
if command -v setsid >/dev/null 2>&1; then
setsid bash -lc "$cmd" >>"$log_file" 2>&1 < /dev/null &
else
bash -lc "$cmd" >>"$log_file" 2>&1 < /dev/null &
fi
local pid=$!
printf '%s\n' "$pid" >"$pid_file"
sleep 1
if is_pid_running "$pid"; then
printf '启动 %-12s 成功 pid=%s log=%s\n' "$(service_title "$service")" "$pid" "$log_file"
return 0
fi
echo "启动 $(service_title "$service") 失败,最近日志:" >&2
tail -n 20 "$log_file" >&2 || true
rm -f "$pid_file"
return 1
}
start_background() {
check_dependencies
ensure_no_managed_services_running
local started=()
local service
for service in "${SERVICES[@]}"; do
if start_service_background "$service"; then
started+=("$service")
else
local started_service
for started_service in "${started[@]}"; do
stop_service "$started_service"
done
exit 1
fi
done
echo
echo "后台服务已启动。"
echo "停止命令: ./dev_services.sh stop"
echo "状态命令: ./dev_services.sh status"
}
show_status() {
local service
for service in "${SERVICES[@]}"; do
clear_stale_pid "$service"
local title pid_file log_file
title="$(service_title "$service")"
pid_file="$(service_pid_file "$service")"
log_file="$(service_log_file "$service")"
if service_running "$service"; then
local pid
pid="$(service_pid "$service")"
printf '%-12s running pid=%-8s log=%s\n' "$title" "$pid" "$log_file"
else
printf '%-12s stopped pid=%-8s log=%s\n' "$title" "-" "$log_file"
fi
done
}
stop_background() {
local service
for service in "${SERVICES[@]}"; do
stop_service "$service"
done
}
cleanup_foreground() {
if (( CLEANED_UP != 0 )); then
return 0
fi
CLEANED_UP=1
if (( ${#FG_PIDS[@]} == 0 )); then
return 0
fi
echo
echo "正在关闭前台服务..."
local pid
for pid in "${FG_PIDS[@]}"; do
kill -TERM "$pid" 2>/dev/null || true
done
sleep 1
for pid in "${FG_PIDS[@]}"; do
if is_pid_running "$pid"; then
kill -KILL "$pid" 2>/dev/null || true
fi
done
wait "${FG_PIDS[@]}" 2>/dev/null || true
}
on_foreground_interrupt() {
echo
echo "收到中断信号,准备关闭两个服务..."
cleanup_foreground
exit 130
}
start_service_foreground() {
local service="$1"
local cmd
cmd="$(service_command "$service")"
local log_file
log_file="$(service_log_file "$service")"
: >"$log_file"
printf '启动 %-12s 前台模式\n' "$(service_title "$service")"
bash -lc "$cmd" > >(tee -a "$log_file" | sed -u "s/^/[$(service_title "$service")] /") 2>&1 &
FG_PIDS+=("$!")
FG_NAMES+=("$service")
}
monitor_foreground() {
while true; do
local idx
for idx in "${!FG_PIDS[@]}"; do
local pid service
pid="${FG_PIDS[$idx]}"
service="${FG_NAMES[$idx]}"
if ! is_pid_running "$pid"; then
wait "$pid"
local status=$?
echo
echo "$(service_title "$service") 已退出,退出码=$status,其余服务也会一并关闭。"
return "$status"
fi
done
sleep 1
done
}
start_foreground() {
check_dependencies
ensure_no_managed_services_running
trap on_foreground_interrupt INT TERM
trap cleanup_foreground EXIT
local service
for service in "${SERVICES[@]}"; do
start_service_foreground "$service"
done
echo
echo "两个服务已进入前台托管模式。按 Ctrl+C 可一键关闭。"
monitor_foreground
}
main() {
local action="${1:-}"
case "$action" in
fg)
start_foreground
;;
bg)
start_background
;;
stop)
stop_background
;;
restart)
stop_background
start_background
;;
status)
show_status
;;
-h|--help|help|"")
usage
;;
*)
echo "未知命令: $action" >&2
echo >&2
usage >&2
exit 1
;;
esac
}
main "$@"