| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- #!/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
|