service.sh 4.75 KB
#!/usr/bin/env bash
# =============================================================================
# ShopAgent - 服务管理脚本
# 用法:
#   ./start.sh start    # 后台启动
#   ./start.sh stop     # 停止服务
#   ./start.sh restart  # 重启服务
#   ./start.sh status   # 查看状态
#   ./start.sh run      # 前台运行(原逻辑,Ctrl+C 停止)
# =============================================================================
set -euo pipefail

CONDA_BASE="${CONDA_BASE:-$HOME/miniconda3}"
CONDA_ENV="${CONDA_ENV:-aishopping-py312}"
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PID_FILE="$PROJECT_ROOT/scripts/streamlit.pid"
LOG_DIR="$PROJECT_ROOT/logs"

# 端口: STREAMLIT_PORT 环境变量 > config.app_port > 默认 6008
read_port_from_config() {
  (cd "$PROJECT_ROOT" && python - <<'PY' 2>/dev/null
try:
    from app.config import settings
    print(settings.app_port)
except Exception:
    pass
PY
)
}
_port_from_config=$(read_port_from_config)
PORT="${STREAMLIT_PORT:-${_port_from_config:-6008}}"
HOST="${STREAMLIT_HOST:-0.0.0.0}"

activate_conda() {
  if [ "${CONDA_DEFAULT_ENV:-}" != "$CONDA_ENV" ]; then
    # shellcheck disable=SC1090
    source "$CONDA_BASE/etc/profile.d/conda.sh"
    conda activate "$CONDA_ENV"
  fi
}

pids_by_port() { lsof -ti:"$PORT" 2>/dev/null || true; }

get_master_pid() {
  if [ -f "$PID_FILE" ]; then
    local pid
    pid=$(cat "$PID_FILE" 2>/dev/null || true)
    if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
      echo "$pid"
      return 0
    fi
  fi
  pgrep -f "streamlit run app.py" | head -1 2>/dev/null || true
}

do_run() {
  activate_conda
  cd "$PROJECT_ROOT"

  if ! command -v streamlit &>/dev/null; then
    echo "❌ Streamlit 未安装,请运行: pip install streamlit"
    exit 1
  fi

  echo "=========================================="
  echo "ShopAgent 前台运行 [端口 $PORT]"
  echo "  访问: http://$HOST:$PORT"
  echo "  按 Ctrl+C 停止"
  echo "=========================================="

  exec streamlit run app.py \
    --server.port="$PORT" \
    --server.address="$HOST" \
    --server.headless=true \
    --browser.gatherUsageStats=false
}

do_start() {
  activate_conda
  cd "$PROJECT_ROOT"

  if ! command -v streamlit &>/dev/null; then
    echo "❌ Streamlit 未安装,请运行: pip install streamlit"
    exit 1
  fi

  local existing_pid
  existing_pid=$(get_master_pid)
  if [ -n "$existing_pid" ]; then
    echo "⚠️ 服务已在运行 [PID: $existing_pid],端口 $PORT"
    echo "   使用 './start.sh restart' 重启"
    return 0
  fi

  mkdir -p "$LOG_DIR"
  echo "🚀 启动 ShopAgent [后台]..."
  nohup streamlit run app.py \
    --server.port="$PORT" \
    --server.address="$HOST" \
    --server.headless=true \
    --browser.gatherUsageStats=false \
    >> "$LOG_DIR/streamlit.log" 2>> "$LOG_DIR/streamlit_error.log" &
  echo $! > "$PID_FILE"

  sleep 2
  if get_master_pid >/dev/null 2>&1; then
    echo "✅ 服务已启动 [PID: $(get_master_pid)]"
    echo "📡 访问: http://$HOST:$PORT"
    echo "📋 日志: $LOG_DIR/streamlit.log"
  else
    echo "❌ 启动失败,请查看: $LOG_DIR/streamlit_error.log"
    rm -f "$PID_FILE"
    exit 1
  fi
}

do_stop() {
  local pid pids

  pid=$(get_master_pid)
  if [ -n "$pid" ]; then
    echo "停止 Streamlit 进程 [PID: $pid]..."
    kill -TERM "$pid" 2>/dev/null || true

    for _ in {1..15}; do
      if ! kill -0 "$pid" 2>/dev/null; then
        rm -f "$PID_FILE"
        echo "✅ 服务已停止"
        return 0
      fi
      sleep 1
    done

    kill -KILL "$pid" 2>/dev/null || true
    rm -f "$PID_FILE"
    echo "✅ 服务已强制停止"
    return 0
  fi

  pids=$(pids_by_port)
  if [ -z "$pids" ]; then
    echo "ℹ️ 端口 $PORT 上无运行中的服务"
    rm -f "$PID_FILE"
    return 0
  fi

  echo "停止占用端口 $PORT 的进程..."
  for p in $pids; do kill -TERM "$p" 2>/dev/null || true; done
  sleep 3
  pids=$(pids_by_port)
  if [ -n "$pids" ]; then
    for p in $pids; do kill -KILL "$p" 2>/dev/null || true; done
  fi
  rm -f "$PID_FILE"
  echo "✅ 服务已停止"
}

do_status() {
  local pid
  pid=$(get_master_pid)

  if [ -n "$pid" ]; then
    echo "✅ 运行中"
    echo "   PID: $pid"
    echo "   端口: $PORT"
    echo "   访问: http://$HOST:$PORT"
  else
    echo "❌ 未运行"
  fi
}

case "${1:-}" in
  start)
    do_start
    ;;
  stop)
    do_stop
    ;;
  restart)
    do_stop
    do_start
    ;;
  status)
    do_status
    ;;
  run)
    do_run
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|status|run}"
    echo ""
    echo "命令说明:"
    echo "  start   - 后台启动服务"
    echo "  stop    - 停止服务"
    echo "  restart - 重启服务"
    echo "  status  - 查看服务状态"
    echo "  run     - 前台运行 [Ctrl+C 停止]"
    exit 1
    ;;
esac