start-prod.sh 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #!/bin/bash
  2. # ============================================
  3. # 陇耘鲜达 Backend 生产环境启动脚本
  4. # 适配: python3.9+(推荐 venv;显式不使用 python2)
  5. #
  6. # 生产服务器目录示例: /root/zkxw-backend/backend
  7. # 默认监听: 0.0.0.0:8111(可通过环境变量 PORT 覆盖)
  8. #
  9. # 首次部署:
  10. # cd /root/zkxw-backend/backend
  11. # cp .env.example .env.production # 填写数据库、Redis、MinIO、SECRET_KEY 等
  12. # python3 -m venv venv && ./venv/bin/pip install -r requirements.txt
  13. # ./start-prod.sh start
  14. # ============================================
  15. set -euo pipefail
  16. BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  17. cd "$BASE_DIR"
  18. APP_MODULE="${APP_MODULE:-app.main:app}"
  19. HOST="${HOST:-0.0.0.0}"
  20. PORT="${PORT:-8111}"
  21. WORKERS="${WORKERS:-2}"
  22. LOG_LEVEL="${LOG_LEVEL:-info}"
  23. ENV_FILE="${ENV_FILE:-$BASE_DIR/.env.production}"
  24. VENV_DIR="${VENV_DIR:-$BASE_DIR/venv}"
  25. PYTHON_BIN="${PYTHON_BIN:-$VENV_DIR/bin/python}"
  26. UVICORN_BIN="${UVICORN_BIN:-$VENV_DIR/bin/uvicorn}"
  27. LOG_DIR="$BASE_DIR/logs"
  28. LOG_FILE="$LOG_DIR/backend-prod.log"
  29. PID_FILE="$BASE_DIR/backend-prod.pid"
  30. RED='\033[0;31m'
  31. GREEN='\033[0;32m'
  32. YELLOW='\033[1;33m'
  33. NC='\033[0m'
  34. log_info() {
  35. echo -e "${GREEN}[INFO]${NC} $1"
  36. }
  37. log_warn() {
  38. echo -e "${YELLOW}[WARN]${NC} $1"
  39. }
  40. log_error() {
  41. echo -e "${RED}[ERROR]${NC} $1"
  42. }
  43. show_help() {
  44. cat << 'EOF'
  45. 用法:
  46. ./start-prod.sh [start|stop|restart|status|logs]
  47. 命令:
  48. start 启动服务(默认)
  49. stop 停止服务
  50. restart 重启服务
  51. status 查看状态
  52. logs 查看日志(最近100行)
  53. 可选环境变量:
  54. HOST=0.0.0.0
  55. PORT=8111
  56. WORKERS=2
  57. LOG_LEVEL=info
  58. APP_MODULE=app.main:app
  59. VENV_DIR=./venv
  60. ENV_FILE=./.env.production
  61. 生产部署目录(示例):
  62. /root/zkxw-backend/backend
  63. cd /root/zkxw-backend/backend && ./start-prod.sh start
  64. EOF
  65. }
  66. ensure_dirs() {
  67. mkdir -p "$LOG_DIR"
  68. }
  69. check_python() {
  70. if ! command -v python3 >/dev/null 2>&1; then
  71. log_error "未找到 python3,请安装 python3.9+"
  72. exit 1
  73. fi
  74. local py_ver
  75. py_ver="$(python3 -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')"
  76. log_info "系统 python3 版本: $py_ver"
  77. }
  78. ensure_venv_and_runtime() {
  79. if [ ! -d "$VENV_DIR" ]; then
  80. log_warn "未找到虚拟环境,开始自动创建: $VENV_DIR"
  81. if ! python3 -m venv "$VENV_DIR" >/dev/null 2>&1; then
  82. log_error "自动创建 venv 失败,请先安装 python3-venv 或手动创建虚拟环境"
  83. log_warn "可尝试: yum install -y python3-venv 或 python3 -m ensurepip --upgrade"
  84. exit 1
  85. fi
  86. fi
  87. if [ ! -x "$PYTHON_BIN" ]; then
  88. log_error "虚拟环境 python 不存在: $PYTHON_BIN"
  89. exit 1
  90. fi
  91. if [ ! -x "$VENV_DIR/bin/pip" ]; then
  92. log_warn "虚拟环境 pip 不存在,尝试引导安装"
  93. "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true
  94. fi
  95. if [ ! -x "$UVICORN_BIN" ]; then
  96. log_warn "未检测到 uvicorn,开始安装依赖 requirements.txt"
  97. if [ ! -x "$VENV_DIR/bin/pip" ]; then
  98. log_error "pip 仍不可用,请手动执行依赖初始化"
  99. exit 1
  100. fi
  101. "$VENV_DIR/bin/pip" install -r "$BASE_DIR/requirements.txt"
  102. if [ ! -x "$UVICORN_BIN" ]; then
  103. log_error "依赖安装后仍未找到 uvicorn: $UVICORN_BIN"
  104. exit 1
  105. fi
  106. fi
  107. }
  108. is_running() {
  109. if [ ! -f "$PID_FILE" ]; then
  110. return 1
  111. fi
  112. local pid
  113. pid="$(cat "$PID_FILE")"
  114. if [ -z "$pid" ]; then
  115. return 1
  116. fi
  117. if ps -p "$pid" >/dev/null 2>&1; then
  118. return 0
  119. fi
  120. return 1
  121. }
  122. start_server() {
  123. check_python
  124. ensure_venv_and_runtime
  125. ensure_dirs
  126. if is_running; then
  127. local running_pid
  128. running_pid="$(cat "$PID_FILE")"
  129. log_warn "服务已在运行 (PID: $running_pid)"
  130. exit 0
  131. fi
  132. if [ -f "$PID_FILE" ]; then
  133. rm -f "$PID_FILE"
  134. fi
  135. log_info "启动生产服务..."
  136. log_info "监听地址: $HOST:$PORT"
  137. log_info "工作进程: $WORKERS"
  138. log_info "日志文件: $LOG_FILE"
  139. log_info "环境文件: $ENV_FILE"
  140. if [ ! -f "$ENV_FILE" ]; then
  141. log_error "环境文件不存在: $ENV_FILE"
  142. exit 1
  143. fi
  144. export ENV_FILE
  145. nohup "$UVICORN_BIN" "$APP_MODULE" \
  146. --host "$HOST" \
  147. --port "$PORT" \
  148. --workers "$WORKERS" \
  149. --log-level "$LOG_LEVEL" \
  150. >> "$LOG_FILE" 2>&1 &
  151. echo $! > "$PID_FILE"
  152. sleep 2
  153. if is_running; then
  154. log_info "服务启动成功 (PID: $(cat "$PID_FILE"))"
  155. log_info "健康检查: http://127.0.0.1:$PORT/health"
  156. else
  157. log_error "服务启动失败,请检查日志: $LOG_FILE"
  158. rm -f "$PID_FILE"
  159. exit 1
  160. fi
  161. }
  162. stop_server() {
  163. if ! is_running; then
  164. log_warn "服务未运行"
  165. [ -f "$PID_FILE" ] && rm -f "$PID_FILE"
  166. exit 0
  167. fi
  168. local pid
  169. pid="$(cat "$PID_FILE")"
  170. log_info "正在停止服务 (PID: $pid)"
  171. kill -TERM "$pid" >/dev/null 2>&1 || true
  172. for _ in 1 2 3 4 5 6 7 8 9 10; do
  173. if ! ps -p "$pid" >/dev/null 2>&1; then
  174. break
  175. fi
  176. sleep 1
  177. done
  178. if ps -p "$pid" >/dev/null 2>&1; then
  179. log_warn "优雅停止超时,执行强制停止"
  180. kill -9 "$pid" >/dev/null 2>&1 || true
  181. fi
  182. rm -f "$PID_FILE"
  183. log_info "服务已停止"
  184. }
  185. status_server() {
  186. if is_running; then
  187. echo -e "${GREEN}RUNNING${NC} PID=$(cat "$PID_FILE") HOST=$HOST PORT=$PORT"
  188. else
  189. echo -e "${RED}STOPPED${NC}"
  190. fi
  191. }
  192. show_logs() {
  193. ensure_dirs
  194. if [ ! -f "$LOG_FILE" ]; then
  195. log_warn "暂无日志文件: $LOG_FILE"
  196. exit 0
  197. fi
  198. # 生产排障时常用最近日志
  199. tail -n 100 "$LOG_FILE"
  200. }
  201. ACTION="${1:-start}"
  202. case "$ACTION" in
  203. start)
  204. start_server
  205. ;;
  206. stop)
  207. stop_server
  208. ;;
  209. restart)
  210. stop_server || true
  211. start_server
  212. ;;
  213. status)
  214. status_server
  215. ;;
  216. logs)
  217. show_logs
  218. ;;
  219. -h|--help|help)
  220. show_help
  221. ;;
  222. *)
  223. log_error "未知命令: $ACTION"
  224. show_help
  225. exit 1
  226. ;;
  227. esac