first commit
This commit is contained in:
398
dev_services.sh
Normal file
398
dev_services.sh
Normal file
@@ -0,0 +1,398 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user