#!/bin/bash # ============================================ # 山峡化工 shanxia2.0 Backend 生产环境启动脚本 # 部署路径:/iflytek/shanxia2.0/backend # 用法:./start-prod.sh [install|start|stop|restart|status|logs] # ============================================ set -euo pipefail BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$BASE_DIR" APP_MODULE="${APP_MODULE:-app.main:app}" HOST="${HOST:-127.0.0.1}" PORT="${PORT:-9111}" WORKERS="${WORKERS:-2}" LOG_LEVEL="${LOG_LEVEL:-info}" ENV_FILE="${ENV_FILE:-$BASE_DIR/.env.production}" PIP_INDEX="${PIP_INDEX:-https://pypi.tuna.tsinghua.edu.cn/simple}" VENV_DIR="${VENV_DIR:-$BASE_DIR/venv}" PYTHON_BIN="${PYTHON_BIN:-$VENV_DIR/bin/python}" PIP_BIN="${PIP_BIN:-$VENV_DIR/bin/pip}" UVICORN_BIN="${UVICORN_BIN:-$VENV_DIR/bin/uvicorn}" LOG_DIR="$BASE_DIR/logs" LOG_FILE="$LOG_DIR/backend-prod.log" PID_FILE="$BASE_DIR/backend-prod.pid" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } show_help() { cat << EOF 山峡化工 shanxia2.0 生产环境启动脚本 用法: ./start-prod.sh [install|start|stop|restart|status|logs] 命令: install 首次部署:创建/重建 venv 并安装 requirements.txt reset-venv 强制删除 venv(误上传本机 venv 时用) start 启动服务(默认) stop 停止服务 restart 重启服务 status 查看状态 logs 查看最近 100 行日志 默认配置(可通过环境变量覆盖): HOST=127.0.0.1 PORT=9111 WORKERS=2 LOG_LEVEL=info ENV_FILE=./.env.production 示例: ./start-prod.sh install # 首次上传后执行 ./start-prod.sh start # 启动 ./start-prod.sh restart # 发版后重启 curl http://127.0.0.1:9111/health EOF } ensure_dirs() { mkdir -p "$LOG_DIR" } check_python() { if ! command -v python3 >/dev/null 2>&1; then log_error "未找到 python3,请安装 Python 3.9+" exit 1 fi local py_ver major minor py_ver="$(python3 -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')" major="$(echo "$py_ver" | cut -d. -f1)" minor="$(echo "$py_ver" | cut -d. -f2)" log_info "系统 python3 版本: $py_ver" if [ "$major" -lt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -lt 9 ]; }; then log_warn "建议使用 Python 3.9+,当前版本: $py_ver" fi } venv_is_usable() { if [ ! -x "$PYTHON_BIN" ]; then return 1 fi if ! "$PYTHON_BIN" -c "import sys" >/dev/null 2>&1; then return 1 fi return 0 } reset_venv() { if [ -d "$VENV_DIR" ]; then log_warn "删除无效虚拟环境: $VENV_DIR" rm -rf "$VENV_DIR" fi } create_venv() { log_info "创建虚拟环境: $VENV_DIR" if ! python3 -m venv "$VENV_DIR"; then log_error "创建 venv 失败,请安装 python3-venv" log_warn "CentOS/RHEL: yum install -y python3-venv" exit 1 fi } ensure_valid_venv() { if [ -d "$VENV_DIR" ] && ! venv_is_usable; then log_warn "检测到无效 venv(常见于把本机 venv 一并上传到 Linux)" reset_venv fi if [ ! -d "$VENV_DIR" ]; then create_venv fi if ! venv_is_usable; then log_error "虚拟环境仍不可用,请手动执行: rm -rf $VENV_DIR && ./start-prod.sh install" exit 1 fi if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then log_warn "pip 不可用,尝试 ensurepip" "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true fi if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then log_error "pip 仍不可用,请手动初始化虚拟环境" exit 1 fi } install_dependencies() { check_python ensure_valid_venv log_info "安装/更新依赖(镜像: $PIP_INDEX)..." "$PYTHON_BIN" -m pip install --upgrade pip -i "$PIP_INDEX" "$PYTHON_BIN" -m pip install -r "$BASE_DIR/requirements.txt" -i "$PIP_INDEX" if [ ! -x "$UVICORN_BIN" ]; then log_error "依赖安装后仍未找到 uvicorn" exit 1 fi log_info "依赖安装完成" } ensure_venv_and_runtime() { if [ ! -d "$VENV_DIR" ] || ! venv_is_usable || [ ! -x "$UVICORN_BIN" ]; then install_dependencies fi } is_running() { if [ ! -f "$PID_FILE" ]; then return 1 fi local pid pid="$(cat "$PID_FILE")" if [ -z "$pid" ]; then return 1 fi if ps -p "$pid" >/dev/null 2>&1; then return 0 fi return 1 } verify_health() { local url="http://${HOST}:${PORT}/health" if command -v curl >/dev/null 2>&1; then if curl -sf "$url" >/dev/null 2>&1; then log_info "健康检查通过: $url" return 0 fi log_warn "健康检查未通过: $url(服务可能仍在启动,请稍后用 ./start-prod.sh logs 查看)" else log_info "未安装 curl,跳过健康检查" fi } start_server() { check_python ensure_venv_and_runtime ensure_dirs if [ ! -f "$ENV_FILE" ]; then log_error "环境文件不存在: $ENV_FILE" log_warn "请确认已上传 backend/.env.production" exit 1 fi if is_running; then local running_pid running_pid="$(cat "$PID_FILE")" log_warn "服务已在运行 (PID: $running_pid)" exit 0 fi if [ -f "$PID_FILE" ]; then rm -f "$PID_FILE" fi log_info "启动 shanxia2.0 生产服务..." log_info "监听地址: $HOST:$PORT" log_info "工作进程: $WORKERS" log_info "日志文件: $LOG_FILE" log_info "环境文件: $ENV_FILE" ENV_FILE="$ENV_FILE" nohup "$UVICORN_BIN" "$APP_MODULE" \ --host "$HOST" \ --port "$PORT" \ --workers "$WORKERS" \ --log-level "$LOG_LEVEL" \ >> "$LOG_FILE" 2>&1 & echo $! > "$PID_FILE" sleep 3 if is_running; then log_info "服务启动成功 (PID: $(cat "$PID_FILE"))" verify_health else log_error "服务启动失败,请检查日志: $LOG_FILE" rm -f "$PID_FILE" exit 1 fi } stop_server() { if ! is_running; then log_warn "服务未运行" [ -f "$PID_FILE" ] && rm -f "$PID_FILE" exit 0 fi local pid pid="$(cat "$PID_FILE")" log_info "正在停止服务 (PID: $pid)" kill -TERM "$pid" >/dev/null 2>&1 || true for _ in 1 2 3 4 5 6 7 8 9 10; do if ! ps -p "$pid" >/dev/null 2>&1; then break fi sleep 1 done if ps -p "$pid" >/dev/null 2>&1; then log_warn "优雅停止超时,执行强制停止" kill -9 "$pid" >/dev/null 2>&1 || true fi rm -f "$PID_FILE" log_info "服务已停止" } status_server() { if is_running; then echo -e "${GREEN}RUNNING${NC} PID=$(cat "$PID_FILE") HOST=$HOST PORT=$PORT" else echo -e "${RED}STOPPED${NC}" fi } show_logs() { ensure_dirs if [ ! -f "$LOG_FILE" ]; then log_warn "暂无日志文件: $LOG_FILE" exit 0 fi tail -n 100 "$LOG_FILE" } ACTION="${1:-start}" case "$ACTION" in install) install_dependencies ;; reset-venv) reset_venv log_info "venv 已删除,请执行: ./start-prod.sh install" ;; start) start_server ;; stop) stop_server ;; restart) stop_server || true start_server ;; status) status_server ;; logs) show_logs ;; -h|--help|help) show_help ;; *) log_error "未知命令: $ACTION" show_help exit 1 ;; esac