#!/bin/bash # ======================================== # 拾音器异响检测系统启动脚本 # ======================================== # # 使用方法: # ./start.sh # 前台运行 # ./start.sh -d # 后台运行 # ./start.sh --daemon # 后台运行 # ./start.sh stop # 停止服务 # ./start.sh restart # 重启服务 # ./start.sh status # 查看状态 # # 日志文件: # 前台运行:直接输出到控制台 # 后台运行:logs/system.log(RotatingFileHandler 自动轮转) # # 切换到脚本所在目录 cd "$(dirname "$0")" # PID文件路径 PID_FILE="logs/pid.txt" STARTUP_TIMEOUT=5 HEALTH_CHECK_INTERVAL=1 # ======================================== # 函数:按PID精确清理PID文件 # ======================================== cleanup_pid_file_if_matches() { local expected_pid="$1" if [ ! -f "$PID_FILE" ]; then return 0 fi local current_pid current_pid=$(cat "$PID_FILE" 2>/dev/null) if [ -z "$expected_pid" ] || [ "$current_pid" = "$expected_pid" ]; then rm -f "$PID_FILE" fi } # ======================================== # 函数:激活conda环境 # ======================================== activate_conda() { if command -v conda &> /dev/null; then # 激活 conda 环境 source $(conda info --base)/etc/profile.d/conda.sh conda activate pump_asd echo "已激活 conda 环境: pump_asd" fi } # ======================================== # 函数:检查PID是否为当前服务进程 # ======================================== is_expected_process() { local pid="$1" if [ -z "$pid" ]; then return 1 fi if ! ps -p "$pid" > /dev/null 2>&1; then return 1 fi local command command=$(ps -p "$pid" -o command= 2>/dev/null) case "$command" in *"run_with_auto_training.py"*) return 0 ;; *) return 1 ;; esac } # ======================================== # 函数:检查进程是否运行 # ======================================== is_running() { if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE") # 不仅检查PID是否存在,还要确认是本服务进程,避免PID复用误判 if is_expected_process "$PID"; then return 0 # 运行中 fi cleanup_pid_file_if_matches "$PID" fi return 1 # 未运行 } # ======================================== # 函数:获取当前PID # ======================================== get_pid() { if [ -f "$PID_FILE" ]; then cat "$PID_FILE" else echo "" fi } # ======================================== # 函数:等待服务稳定启动 # ======================================== wait_for_service_ready() { local pid="$1" local elapsed=0 while [ "$elapsed" -lt "$STARTUP_TIMEOUT" ]; do if ! is_expected_process "$pid"; then return 1 fi sleep "$HEALTH_CHECK_INTERVAL" elapsed=$((elapsed + HEALTH_CHECK_INTERVAL)) done return 0 } # ======================================== # 函数:后台监控PID,进程退出后自动清理PID文件 # ======================================== spawn_pid_watcher() { local watched_pid="$1" nohup bash -c ' watched_pid="$1" pid_file="$2" while ps -p "$watched_pid" > /dev/null 2>&1; do sleep 2 done if [ -f "$pid_file" ] && [ "$(cat "$pid_file" 2>/dev/null)" = "$watched_pid" ]; then rm -f "$pid_file" fi ' _ "$watched_pid" "$PID_FILE" > /dev/null 2>&1 & } # ======================================== # 函数:启动服务 # ======================================== start_service() { # 检查是否已经运行 if is_running; then echo "服务已在运行中, PID: $(get_pid)" echo "如需重启,请使用: ./start.sh restart" return 1 fi # 激活conda环境 activate_conda # 检查必要文件 if [ ! -f "run_with_auto_training.py" ]; then echo "错误: run_with_auto_training.py 不存在" exit 1 fi # 检查配置文件(YAML 或 DB 至少存在一个) if [ ! -f "config/pickup_config.db" ] && [ ! -f "config/rtsp_config.yaml" ]; then echo "错误: 找不到配置文件" echo "需要 config/pickup_config.db 或 config/rtsp_config.yaml 之一" exit 1 fi # 创建日志目录 mkdir -p logs # 启动服务 echo "后台运行模式..." # stdout/stderr 丢弃,所有日志由 RotatingFileHandler 写入 logs/system.log nohup python run_with_auto_training.py > /dev/null 2>&1 & PID=$! echo $PID > "$PID_FILE" # 等待一段观察窗口,避免“刚启动1秒就退出”仍被误判为成功 if wait_for_service_ready "$PID"; then spawn_pid_watcher "$PID" echo "服务启动成功, PID: $PID" echo "日志文件: logs/system.log" echo "" echo "查看日志: tail -f logs/system.log" echo "停止服务: ./start.sh stop" echo "重启服务: ./start.sh restart" else echo "服务启动失败,请检查日志: logs/system.log" cleanup_pid_file_if_matches "$PID" return 1 fi } # ======================================== # 函数:停止服务 # ======================================== stop_service() { if ! is_running; then echo "服务未运行" cleanup_pid_file_if_matches "" return 0 fi PID=$(get_pid) echo "正在停止服务, PID: $PID" # 发送 SIGTERM 信号,优雅停止 kill "$PID" 2>/dev/null # 等待进程结束(最多等待10秒) WAIT_COUNT=0 while ps -p "$PID" > /dev/null 2>&1; do if [ $WAIT_COUNT -ge 10 ]; then echo "进程未响应,强制终止..." kill -9 "$PID" 2>/dev/null break fi sleep 1 WAIT_COUNT=$((WAIT_COUNT + 1)) echo "等待进程结束... ($WAIT_COUNT/10)" done cleanup_pid_file_if_matches "$PID" echo "服务已停止" } # ======================================== # 函数:重启服务 # ======================================== restart_service() { echo "==========================================" echo "重启拾音器异响检测服务" echo "==========================================" stop_service echo "" sleep 2 # 等待2秒确保资源完全释放 start_service } # ======================================== # 函数:查看服务状态 # ======================================== show_status() { echo "==========================================" echo "拾音器异响检测服务状态" echo "==========================================" if is_running; then PID=$(get_pid) echo "状态: 运行中" echo "PID: $PID" echo "" # 显示进程信息 echo "进程详情:" ps -p "$PID" -o pid,ppid,user,%cpu,%mem,etime,command | head -2 echo "" # 显示最近日志 echo "最近10行日志:" echo "------------------------------------------" tail -10 logs/system.log 2>/dev/null || echo "(无日志)" else echo "状态: 未运行" if [ -f "$PID_FILE" ]; then echo "注意: PID文件存在但进程已停止,可能是异常退出" cleanup_pid_file_if_matches "" fi fi } # ======================================== # 函数:前台运行 # ======================================== run_foreground() { # 检查是否已经运行 if is_running; then echo "服务已在后台运行中, PID: $(get_pid)" echo "请先停止: ./start.sh stop" return 1 fi # 激活conda环境 activate_conda # 检查必要文件 if [ ! -f "run_with_auto_training.py" ]; then echo "错误: run_with_auto_training.py 不存在" exit 1 fi # 检查配置文件(YAML 或 DB 至少存在一个) if [ ! -f "config/pickup_config.db" ] && [ ! -f "config/rtsp_config.yaml" ]; then echo "错误: 找不到配置文件" echo "需要 config/pickup_config.db 或 config/rtsp_config.yaml 之一" exit 1 fi # 创建日志目录 mkdir -p logs echo "前台运行模式..." python run_with_auto_training.py } # ======================================== # 函数:显示帮助 # ======================================== show_help() { echo "拾音器异响检测系统 - 启动脚本" echo "" echo "用法: ./start.sh [命令]" echo "" echo "命令:" echo " (无参数) 前台运行" echo " -d, --daemon 后台运行" echo " start 后台启动服务" echo " stop 停止服务" echo " restart 重启服务" echo " status 查看服务状态" echo " help 显示帮助信息" echo "" echo "示例:" echo " ./start.sh -d # 后台启动" echo " ./start.sh restart # 重启服务" echo " ./start.sh status # 查看状态" } # ======================================== # 主逻辑 # ======================================== case "$1" in stop) stop_service ;; restart) restart_service ;; status) show_status ;; start|-d|--daemon) start_service ;; help|--help|-h) show_help ;; "") run_foreground ;; *) echo "未知命令: $1" echo "" show_help exit 1 ;; esac