start.sh 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. #!/bin/bash
  2. # ========================================
  3. # 拾音器异响检测系统启动脚本
  4. # ========================================
  5. #
  6. # 使用方法:
  7. # ./start.sh # 前台运行
  8. # ./start.sh -d # 后台运行
  9. # ./start.sh --daemon # 后台运行
  10. # ./start.sh stop # 停止服务
  11. # ./start.sh restart # 重启服务
  12. # ./start.sh status # 查看状态
  13. #
  14. # 日志文件:
  15. # 前台运行:直接输出到控制台
  16. # 后台运行:logs/system.log(RotatingFileHandler 自动轮转)
  17. #
  18. # 切换到脚本所在目录
  19. cd "$(dirname "$0")"
  20. # PID文件路径
  21. PID_FILE="logs/pid.txt"
  22. STARTUP_TIMEOUT=5
  23. HEALTH_CHECK_INTERVAL=1
  24. # ========================================
  25. # 函数:按PID精确清理PID文件
  26. # ========================================
  27. cleanup_pid_file_if_matches() {
  28. local expected_pid="$1"
  29. if [ ! -f "$PID_FILE" ]; then
  30. return 0
  31. fi
  32. local current_pid
  33. current_pid=$(cat "$PID_FILE" 2>/dev/null)
  34. if [ -z "$expected_pid" ] || [ "$current_pid" = "$expected_pid" ]; then
  35. rm -f "$PID_FILE"
  36. fi
  37. }
  38. # ========================================
  39. # 函数:激活conda环境
  40. # ========================================
  41. activate_conda() {
  42. if command -v conda &> /dev/null; then
  43. # 激活 conda 环境
  44. source $(conda info --base)/etc/profile.d/conda.sh
  45. conda activate pump_asd
  46. echo "已激活 conda 环境: pump_asd"
  47. fi
  48. }
  49. # ========================================
  50. # 函数:检查PID是否为当前服务进程
  51. # ========================================
  52. is_expected_process() {
  53. local pid="$1"
  54. if [ -z "$pid" ]; then
  55. return 1
  56. fi
  57. if ! ps -p "$pid" > /dev/null 2>&1; then
  58. return 1
  59. fi
  60. local command
  61. command=$(ps -p "$pid" -o command= 2>/dev/null)
  62. case "$command" in
  63. *"run_with_auto_training.py"*)
  64. return 0
  65. ;;
  66. *)
  67. return 1
  68. ;;
  69. esac
  70. }
  71. # ========================================
  72. # 函数:检查进程是否运行
  73. # ========================================
  74. is_running() {
  75. if [ -f "$PID_FILE" ]; then
  76. PID=$(cat "$PID_FILE")
  77. # 不仅检查PID是否存在,还要确认是本服务进程,避免PID复用误判
  78. if is_expected_process "$PID"; then
  79. return 0 # 运行中
  80. fi
  81. cleanup_pid_file_if_matches "$PID"
  82. fi
  83. return 1 # 未运行
  84. }
  85. # ========================================
  86. # 函数:获取当前PID
  87. # ========================================
  88. get_pid() {
  89. if [ -f "$PID_FILE" ]; then
  90. cat "$PID_FILE"
  91. else
  92. echo ""
  93. fi
  94. }
  95. # ========================================
  96. # 函数:等待服务稳定启动
  97. # ========================================
  98. wait_for_service_ready() {
  99. local pid="$1"
  100. local elapsed=0
  101. while [ "$elapsed" -lt "$STARTUP_TIMEOUT" ]; do
  102. if ! is_expected_process "$pid"; then
  103. return 1
  104. fi
  105. sleep "$HEALTH_CHECK_INTERVAL"
  106. elapsed=$((elapsed + HEALTH_CHECK_INTERVAL))
  107. done
  108. return 0
  109. }
  110. # ========================================
  111. # 函数:后台监控PID,进程退出后自动清理PID文件
  112. # ========================================
  113. spawn_pid_watcher() {
  114. local watched_pid="$1"
  115. nohup bash -c '
  116. watched_pid="$1"
  117. pid_file="$2"
  118. while ps -p "$watched_pid" > /dev/null 2>&1; do
  119. sleep 2
  120. done
  121. if [ -f "$pid_file" ] && [ "$(cat "$pid_file" 2>/dev/null)" = "$watched_pid" ]; then
  122. rm -f "$pid_file"
  123. fi
  124. ' _ "$watched_pid" "$PID_FILE" > /dev/null 2>&1 &
  125. }
  126. # ========================================
  127. # 函数:启动服务
  128. # ========================================
  129. start_service() {
  130. # 检查是否已经运行
  131. if is_running; then
  132. echo "服务已在运行中, PID: $(get_pid)"
  133. echo "如需重启,请使用: ./start.sh restart"
  134. return 1
  135. fi
  136. # 激活conda环境
  137. activate_conda
  138. # 检查必要文件
  139. if [ ! -f "run_with_auto_training.py" ]; then
  140. echo "错误: run_with_auto_training.py 不存在"
  141. exit 1
  142. fi
  143. # 检查配置文件(YAML 或 DB 至少存在一个)
  144. if [ ! -f "config/pickup_config.db" ] && [ ! -f "config/rtsp_config.yaml" ]; then
  145. echo "错误: 找不到配置文件"
  146. echo "需要 config/pickup_config.db 或 config/rtsp_config.yaml 之一"
  147. exit 1
  148. fi
  149. # 创建日志目录
  150. mkdir -p logs
  151. # 启动服务
  152. echo "后台运行模式..."
  153. # stdout/stderr 丢弃,所有日志由 RotatingFileHandler 写入 logs/system.log
  154. nohup python run_with_auto_training.py > /dev/null 2>&1 &
  155. PID=$!
  156. echo $PID > "$PID_FILE"
  157. # 等待一段观察窗口,避免“刚启动1秒就退出”仍被误判为成功
  158. if wait_for_service_ready "$PID"; then
  159. spawn_pid_watcher "$PID"
  160. echo "服务启动成功, PID: $PID"
  161. echo "日志文件: logs/system.log"
  162. echo ""
  163. echo "查看日志: tail -f logs/system.log"
  164. echo "停止服务: ./start.sh stop"
  165. echo "重启服务: ./start.sh restart"
  166. else
  167. echo "服务启动失败,请检查日志: logs/system.log"
  168. cleanup_pid_file_if_matches "$PID"
  169. return 1
  170. fi
  171. }
  172. # ========================================
  173. # 函数:停止服务
  174. # ========================================
  175. stop_service() {
  176. if ! is_running; then
  177. echo "服务未运行"
  178. cleanup_pid_file_if_matches ""
  179. return 0
  180. fi
  181. PID=$(get_pid)
  182. echo "正在停止服务, PID: $PID"
  183. # 发送 SIGTERM 信号,优雅停止
  184. kill "$PID" 2>/dev/null
  185. # 等待进程结束(最多等待10秒)
  186. WAIT_COUNT=0
  187. while ps -p "$PID" > /dev/null 2>&1; do
  188. if [ $WAIT_COUNT -ge 10 ]; then
  189. echo "进程未响应,强制终止..."
  190. kill -9 "$PID" 2>/dev/null
  191. break
  192. fi
  193. sleep 1
  194. WAIT_COUNT=$((WAIT_COUNT + 1))
  195. echo "等待进程结束... ($WAIT_COUNT/10)"
  196. done
  197. cleanup_pid_file_if_matches "$PID"
  198. echo "服务已停止"
  199. }
  200. # ========================================
  201. # 函数:重启服务
  202. # ========================================
  203. restart_service() {
  204. echo "=========================================="
  205. echo "重启拾音器异响检测服务"
  206. echo "=========================================="
  207. stop_service
  208. echo ""
  209. sleep 2 # 等待2秒确保资源完全释放
  210. start_service
  211. }
  212. # ========================================
  213. # 函数:查看服务状态
  214. # ========================================
  215. show_status() {
  216. echo "=========================================="
  217. echo "拾音器异响检测服务状态"
  218. echo "=========================================="
  219. if is_running; then
  220. PID=$(get_pid)
  221. echo "状态: 运行中"
  222. echo "PID: $PID"
  223. echo ""
  224. # 显示进程信息
  225. echo "进程详情:"
  226. ps -p "$PID" -o pid,ppid,user,%cpu,%mem,etime,command | head -2
  227. echo ""
  228. # 显示最近日志
  229. echo "最近10行日志:"
  230. echo "------------------------------------------"
  231. tail -10 logs/system.log 2>/dev/null || echo "(无日志)"
  232. else
  233. echo "状态: 未运行"
  234. if [ -f "$PID_FILE" ]; then
  235. echo "注意: PID文件存在但进程已停止,可能是异常退出"
  236. cleanup_pid_file_if_matches ""
  237. fi
  238. fi
  239. }
  240. # ========================================
  241. # 函数:前台运行
  242. # ========================================
  243. run_foreground() {
  244. # 检查是否已经运行
  245. if is_running; then
  246. echo "服务已在后台运行中, PID: $(get_pid)"
  247. echo "请先停止: ./start.sh stop"
  248. return 1
  249. fi
  250. # 激活conda环境
  251. activate_conda
  252. # 检查必要文件
  253. if [ ! -f "run_with_auto_training.py" ]; then
  254. echo "错误: run_with_auto_training.py 不存在"
  255. exit 1
  256. fi
  257. # 检查配置文件(YAML 或 DB 至少存在一个)
  258. if [ ! -f "config/pickup_config.db" ] && [ ! -f "config/rtsp_config.yaml" ]; then
  259. echo "错误: 找不到配置文件"
  260. echo "需要 config/pickup_config.db 或 config/rtsp_config.yaml 之一"
  261. exit 1
  262. fi
  263. # 创建日志目录
  264. mkdir -p logs
  265. echo "前台运行模式..."
  266. python run_with_auto_training.py
  267. }
  268. # ========================================
  269. # 函数:显示帮助
  270. # ========================================
  271. show_help() {
  272. echo "拾音器异响检测系统 - 启动脚本"
  273. echo ""
  274. echo "用法: ./start.sh [命令]"
  275. echo ""
  276. echo "命令:"
  277. echo " (无参数) 前台运行"
  278. echo " -d, --daemon 后台运行"
  279. echo " start 后台启动服务"
  280. echo " stop 停止服务"
  281. echo " restart 重启服务"
  282. echo " status 查看服务状态"
  283. echo " help 显示帮助信息"
  284. echo ""
  285. echo "示例:"
  286. echo " ./start.sh -d # 后台启动"
  287. echo " ./start.sh restart # 重启服务"
  288. echo " ./start.sh status # 查看状态"
  289. }
  290. # ========================================
  291. # 主逻辑
  292. # ========================================
  293. case "$1" in
  294. stop)
  295. stop_service
  296. ;;
  297. restart)
  298. restart_service
  299. ;;
  300. status)
  301. show_status
  302. ;;
  303. start|-d|--daemon)
  304. start_service
  305. ;;
  306. help|--help|-h)
  307. show_help
  308. ;;
  309. "")
  310. run_foreground
  311. ;;
  312. *)
  313. echo "未知命令: $1"
  314. echo ""
  315. show_help
  316. exit 1
  317. ;;
  318. esac