-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun.sh
More file actions
executable file
·183 lines (171 loc) · 6.04 KB
/
Copy pathrun.sh
File metadata and controls
executable file
·183 lines (171 loc) · 6.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/usr/bin/env bash
# 未来专业人格卡 H5 —— 开发服务器管理脚本
# 用法: ./run.sh {start|stop|restart|status|prod-deploy|prod-start|prod-stop|prod-restart|prod-status|prod-render-nginx|prod-bench-log|prod-bench-static|prod-bench-app|prod-start-app|prod-stop-app|prod-restart-app|prod-start-api|prod-stop-api|prod-restart-api}
# start 启动开发服务器(已在运行则不重复启动)
# stop 停止所有本项目的服务进程(含历史遗留的多个实例)
# restart 先 stop 再 start
# status 查看运行状态
set -u
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RUN_DIR="$PROJECT_DIR/.run"
PID_FILE="$RUN_DIR/server.pids"
LOG_FILE="$RUN_DIR/server.log"
PORT="${PORT:-5173}"
PROD_SCRIPT="$PROJECT_DIR/scripts/prod.sh"
if [[ ! "$PORT" =~ ^[0-9]+$ ]]; then
echo "PORT 必须是数字,当前值:$PORT" >&2
exit 1
fi
# 找出所有属于本项目的 vite 进程 pid
# 三重判定:① 可执行文件必须是 node(排除恰好命令行含 "vite" 的 shell)
# ② 工作目录在本项目内 ③ 命令行确为 vite 服务器
find_pids() {
local pid cwd cmd comm
for pid in $(pgrep -f 'node_modules' 2>/dev/null); do
comm="$(cat "/proc/$pid/comm" 2>/dev/null)" || continue
[[ "$comm" == node ]] || continue
cwd="$(readlink "/proc/$pid/cwd" 2>/dev/null)" || continue
case "$cwd" in
"$PROJECT_DIR"|"$PROJECT_DIR"/*) ;;
*) continue ;;
esac
cmd="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null)" || continue
case "$cmd" in
*vitest*) continue ;; # 排除单元测试进程
*node_modules*vite*) echo "$pid" ;; # vite 开发/预览服务器
esac
done
}
current_url() {
awk '/Local:/ { print $3 }' "$LOG_FILE" 2>/dev/null | tail -n 1
}
port_in_use() {
ss -ltnH "sport = :$1" 2>/dev/null | grep -q .
}
find_free_port() {
local candidate="$PORT"
while [[ "$candidate" -le 65535 ]]; do
if ! port_in_use "$candidate"; then
echo "$candidate"
return 0
fi
candidate=$((candidate + 1))
done
return 1
}
# 收集需要清理的进程组 ID(来源:pid 文件 + 实时扫描)
collect_pgids() {
local p pid pgid
if [[ -f "$PID_FILE" ]]; then
while read -r p; do [[ -n "$p" ]] && echo "$p"; done < "$PID_FILE"
fi
for pid in $(find_pids); do
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
[[ -n "$pgid" ]] && echo "$pgid"
done
}
start() {
local chosen_port url
mkdir -p "$RUN_DIR"
if [[ -n "$(find_pids)" ]]; then
url="$(current_url)"
[[ -n "$url" ]] || url="http://localhost:$PORT"
echo "✓ 服务已在运行: $url"
echo " 如需重启: $0 restart"
return 0
fi
chosen_port="$(find_free_port)" || {
echo "✗ 从 $PORT 到 65535 没有可用端口" >&2
return 1
}
: > "$LOG_FILE"
: > "$PID_FILE"
# setsid 让服务进程自成会话/进程组,便于 stop 时整组清理
setsid bash -c 'cd "$1" && exec npm run dev -- --host 0.0.0.0 --port "$2" --strictPort' _ "$PROJECT_DIR" "$chosen_port" >> "$LOG_FILE" 2>&1 &
local pid=$!
echo "$pid" >> "$PID_FILE"
# 等待就绪(最多 ~20s)
local i
for i in $(seq 1 40); do
if grep -Eq '^[[:space:]]*VITE v[0-9][^[:space:]]*[[:space:]]+ready in ' "$LOG_FILE" 2>/dev/null; then
url="$(current_url)"
[[ -n "$url" ]] || url="http://localhost:$chosen_port"
echo "✓ 启动成功: $url"
echo " 日志: $LOG_FILE"
return 0
fi
if ! kill -0 "$pid" 2>/dev/null && [[ -z "$(find_pids)" ]]; then
echo "✗ 启动失败,日志末尾:"
tail -n 15 "$LOG_FILE"
rm -f "$PID_FILE"
return 1
fi
sleep 0.5
done
echo "⚠ 启动超时,请查看日志: $LOG_FILE"
return 1
}
stop() {
local pgids targets=() count=0 g self_pgid
self_pgid="$(ps -o pgid= -p $$ 2>/dev/null | tr -d ' ')"
pgids="$(collect_pgids | sort -u)"
# 过滤非法 / 危险的进程组(空、≤1、本脚本自身所在组)
for g in $pgids; do
[[ "$g" =~ ^[0-9]+$ ]] || continue
[[ "$g" -le 1 ]] && continue
[[ -n "$self_pgid" && "$g" == "$self_pgid" ]] && continue
targets+=("$g")
done
if [[ ${#targets[@]} -eq 0 ]]; then
echo "没有正在运行的服务"
rm -f "$PID_FILE"
return 0
fi
# 先 TERM 温和退出
for g in "${targets[@]}"; do
kill -TERM -"$g" 2>/dev/null && count=$((count + 1))
done
sleep 1
# 再 KILL 兜底,确保全部停掉
for g in "${targets[@]}"; do
kill -KILL -"$g" 2>/dev/null
done
rm -f "$PID_FILE"
echo "✓ 已停止 $count 个服务进程组"
}
status() {
local pids url
pids="$(find_pids)"
if [[ -n "$pids" ]]; then
url="$(current_url)"
[[ -n "$url" ]] || url="http://localhost:$PORT"
echo "● 运行中 (pid: $(echo "$pids" | tr '\n' ' ')) $url"
else
echo "○ 未运行"
fi
}
case "${1:-}" in
start) start ;;
stop) stop ;;
restart) stop; sleep 1; start ;;
status) status ;;
prod-deploy) "$PROD_SCRIPT" deploy "${@:2}" ;;
prod-start) "$PROD_SCRIPT" start "${@:2}" ;;
prod-stop) "$PROD_SCRIPT" stop "${@:2}" ;;
prod-restart) "$PROD_SCRIPT" restart "${@:2}" ;;
prod-status) "$PROD_SCRIPT" status "${@:2}" ;;
prod-render-nginx) "$PROD_SCRIPT" render-nginx "${@:2}" ;;
prod-bench-log) "$PROD_SCRIPT" bench-log "${@:2}" ;;
prod-bench-static) "$PROD_SCRIPT" bench-static "${@:2}" ;;
prod-bench-app) "$PROD_SCRIPT" bench-app "${@:2}" ;;
prod-start-app) "$PROD_SCRIPT" start-app "${@:2}" ;;
prod-stop-app) "$PROD_SCRIPT" stop-app "${@:2}" ;;
prod-restart-app) "$PROD_SCRIPT" restart-app "${@:2}" ;;
prod-start-api) "$PROD_SCRIPT" start-api "${@:2}" ;;
prod-stop-api) "$PROD_SCRIPT" stop-api "${@:2}" ;;
prod-restart-api) "$PROD_SCRIPT" restart-api "${@:2}" ;;
*)
echo "用法: $0 {start|stop|restart|status|prod-deploy|prod-start|prod-stop|prod-restart|prod-status|prod-render-nginx|prod-bench-log|prod-bench-static|prod-bench-app|prod-start-app|prod-stop-app|prod-restart-app|prod-start-api|prod-stop-api|prod-restart-api}"
exit 1
;;
esac