start-prod.sh 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. #!/bin/bash
  2. # ============================================
  3. # 山峡化工 shanxia2.0 Backend 生产环境启动脚本
  4. # 部署路径:/iflytek/shanxia2.0/backend
  5. # 用法:./start-prod.sh [install|start|stop|restart|status|logs]
  6. # ============================================
  7. set -euo pipefail
  8. BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  9. cd "$BASE_DIR"
  10. APP_MODULE="${APP_MODULE:-app.main:app}"
  11. HOST="${HOST:-127.0.0.1}"
  12. PORT="${PORT:-9111}"
  13. WORKERS="${WORKERS:-2}"
  14. LOG_LEVEL="${LOG_LEVEL:-info}"
  15. ENV_FILE="${ENV_FILE:-$BASE_DIR/.env.production}"
  16. PIP_INDEX="${PIP_INDEX:-https://pypi.tuna.tsinghua.edu.cn/simple}"
  17. VENV_DIR="${VENV_DIR:-$BASE_DIR/venv}"
  18. PYTHON_BIN="${PYTHON_BIN:-$VENV_DIR/bin/python}"
  19. PIP_BIN="${PIP_BIN:-$VENV_DIR/bin/pip}"
  20. UVICORN_BIN="${UVICORN_BIN:-$VENV_DIR/bin/uvicorn}"
  21. LOG_DIR="$BASE_DIR/logs"
  22. LOG_FILE="$LOG_DIR/backend-prod.log"
  23. PID_FILE="$BASE_DIR/backend-prod.pid"
  24. RED='\033[0;31m'
  25. GREEN='\033[0;32m'
  26. YELLOW='\033[1;33m'
  27. NC='\033[0m'
  28. log_info() {
  29. echo -e "${GREEN}[INFO]${NC} $1"
  30. }
  31. log_warn() {
  32. echo -e "${YELLOW}[WARN]${NC} $1"
  33. }
  34. log_error() {
  35. echo -e "${RED}[ERROR]${NC} $1"
  36. }
  37. show_help() {
  38. cat << EOF
  39. 山峡化工 shanxia2.0 生产环境启动脚本
  40. 用法:
  41. ./start-prod.sh [install|start|stop|restart|status|logs]
  42. 命令:
  43. install 首次部署:创建/重建 venv 并安装 requirements.txt
  44. reset-venv 强制删除 venv(误上传本机 venv 时用)
  45. start 启动服务(默认)
  46. stop 停止服务
  47. restart 重启服务
  48. status 查看状态
  49. logs 查看最近 100 行日志
  50. 默认配置(可通过环境变量覆盖):
  51. HOST=127.0.0.1
  52. PORT=9111
  53. WORKERS=2
  54. LOG_LEVEL=info
  55. ENV_FILE=./.env.production
  56. 示例:
  57. ./start-prod.sh install # 首次上传后执行
  58. ./start-prod.sh start # 启动
  59. ./start-prod.sh restart # 发版后重启
  60. curl http://127.0.0.1:9111/health
  61. EOF
  62. }
  63. ensure_dirs() {
  64. mkdir -p "$LOG_DIR"
  65. }
  66. check_python() {
  67. if ! command -v python3 >/dev/null 2>&1; then
  68. log_error "未找到 python3,请安装 Python 3.9+"
  69. exit 1
  70. fi
  71. local py_ver major minor
  72. py_ver="$(python3 -c 'import sys; print("{}.{}".format(sys.version_info[0], sys.version_info[1]))')"
  73. major="$(echo "$py_ver" | cut -d. -f1)"
  74. minor="$(echo "$py_ver" | cut -d. -f2)"
  75. log_info "系统 python3 版本: $py_ver"
  76. if [ "$major" -lt 3 ] || { [ "$major" -eq 3 ] && [ "$minor" -lt 9 ]; }; then
  77. log_warn "建议使用 Python 3.9+,当前版本: $py_ver"
  78. fi
  79. }
  80. venv_is_usable() {
  81. if [ ! -x "$PYTHON_BIN" ]; then
  82. return 1
  83. fi
  84. if ! "$PYTHON_BIN" -c "import sys" >/dev/null 2>&1; then
  85. return 1
  86. fi
  87. return 0
  88. }
  89. reset_venv() {
  90. if [ -d "$VENV_DIR" ]; then
  91. log_warn "删除无效虚拟环境: $VENV_DIR"
  92. rm -rf "$VENV_DIR"
  93. fi
  94. }
  95. create_venv() {
  96. log_info "创建虚拟环境: $VENV_DIR"
  97. if ! python3 -m venv "$VENV_DIR"; then
  98. log_error "创建 venv 失败,请安装 python3-venv"
  99. log_warn "CentOS/RHEL: yum install -y python3-venv"
  100. exit 1
  101. fi
  102. }
  103. ensure_valid_venv() {
  104. if [ -d "$VENV_DIR" ] && ! venv_is_usable; then
  105. log_warn "检测到无效 venv(常见于把本机 venv 一并上传到 Linux)"
  106. reset_venv
  107. fi
  108. if [ ! -d "$VENV_DIR" ]; then
  109. create_venv
  110. fi
  111. if ! venv_is_usable; then
  112. log_error "虚拟环境仍不可用,请手动执行: rm -rf $VENV_DIR && ./start-prod.sh install"
  113. exit 1
  114. fi
  115. if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then
  116. log_warn "pip 不可用,尝试 ensurepip"
  117. "$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true
  118. fi
  119. if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then
  120. log_error "pip 仍不可用,请手动初始化虚拟环境"
  121. exit 1
  122. fi
  123. }
  124. install_dependencies() {
  125. check_python
  126. ensure_valid_venv
  127. log_info "安装/更新依赖(镜像: $PIP_INDEX)..."
  128. "$PYTHON_BIN" -m pip install --upgrade pip -i "$PIP_INDEX"
  129. "$PYTHON_BIN" -m pip install -r "$BASE_DIR/requirements.txt" -i "$PIP_INDEX"
  130. if [ ! -x "$UVICORN_BIN" ]; then
  131. log_error "依赖安装后仍未找到 uvicorn"
  132. exit 1
  133. fi
  134. log_info "依赖安装完成"
  135. }
  136. ensure_venv_and_runtime() {
  137. if [ ! -d "$VENV_DIR" ] || ! venv_is_usable || [ ! -x "$UVICORN_BIN" ]; then
  138. install_dependencies
  139. fi
  140. }
  141. is_running() {
  142. if [ ! -f "$PID_FILE" ]; then
  143. return 1
  144. fi
  145. local pid
  146. pid="$(cat "$PID_FILE")"
  147. if [ -z "$pid" ]; then
  148. return 1
  149. fi
  150. if ps -p "$pid" >/dev/null 2>&1; then
  151. return 0
  152. fi
  153. return 1
  154. }
  155. verify_health() {
  156. local url="http://${HOST}:${PORT}/health"
  157. if command -v curl >/dev/null 2>&1; then
  158. if curl -sf "$url" >/dev/null 2>&1; then
  159. log_info "健康检查通过: $url"
  160. return 0
  161. fi
  162. log_warn "健康检查未通过: $url(服务可能仍在启动,请稍后用 ./start-prod.sh logs 查看)"
  163. else
  164. log_info "未安装 curl,跳过健康检查"
  165. fi
  166. }
  167. start_server() {
  168. check_python
  169. ensure_venv_and_runtime
  170. ensure_dirs
  171. if [ ! -f "$ENV_FILE" ]; then
  172. log_error "环境文件不存在: $ENV_FILE"
  173. log_warn "请确认已上传 backend/.env.production"
  174. exit 1
  175. fi
  176. if is_running; then
  177. local running_pid
  178. running_pid="$(cat "$PID_FILE")"
  179. log_warn "服务已在运行 (PID: $running_pid)"
  180. exit 0
  181. fi
  182. if [ -f "$PID_FILE" ]; then
  183. rm -f "$PID_FILE"
  184. fi
  185. log_info "启动 shanxia2.0 生产服务..."
  186. log_info "监听地址: $HOST:$PORT"
  187. log_info "工作进程: $WORKERS"
  188. log_info "日志文件: $LOG_FILE"
  189. log_info "环境文件: $ENV_FILE"
  190. ENV_FILE="$ENV_FILE" nohup "$UVICORN_BIN" "$APP_MODULE" \
  191. --host "$HOST" \
  192. --port "$PORT" \
  193. --workers "$WORKERS" \
  194. --log-level "$LOG_LEVEL" \
  195. >> "$LOG_FILE" 2>&1 &
  196. echo $! > "$PID_FILE"
  197. sleep 3
  198. if is_running; then
  199. log_info "服务启动成功 (PID: $(cat "$PID_FILE"))"
  200. verify_health
  201. else
  202. log_error "服务启动失败,请检查日志: $LOG_FILE"
  203. rm -f "$PID_FILE"
  204. exit 1
  205. fi
  206. }
  207. stop_server() {
  208. if ! is_running; then
  209. log_warn "服务未运行"
  210. [ -f "$PID_FILE" ] && rm -f "$PID_FILE"
  211. exit 0
  212. fi
  213. local pid
  214. pid="$(cat "$PID_FILE")"
  215. log_info "正在停止服务 (PID: $pid)"
  216. kill -TERM "$pid" >/dev/null 2>&1 || true
  217. for _ in 1 2 3 4 5 6 7 8 9 10; do
  218. if ! ps -p "$pid" >/dev/null 2>&1; then
  219. break
  220. fi
  221. sleep 1
  222. done
  223. if ps -p "$pid" >/dev/null 2>&1; then
  224. log_warn "优雅停止超时,执行强制停止"
  225. kill -9 "$pid" >/dev/null 2>&1 || true
  226. fi
  227. rm -f "$PID_FILE"
  228. log_info "服务已停止"
  229. }
  230. status_server() {
  231. if is_running; then
  232. echo -e "${GREEN}RUNNING${NC} PID=$(cat "$PID_FILE") HOST=$HOST PORT=$PORT"
  233. else
  234. echo -e "${RED}STOPPED${NC}"
  235. fi
  236. }
  237. show_logs() {
  238. ensure_dirs
  239. if [ ! -f "$LOG_FILE" ]; then
  240. log_warn "暂无日志文件: $LOG_FILE"
  241. exit 0
  242. fi
  243. tail -n 100 "$LOG_FILE"
  244. }
  245. ACTION="${1:-start}"
  246. case "$ACTION" in
  247. install)
  248. install_dependencies
  249. ;;
  250. reset-venv)
  251. reset_venv
  252. log_info "venv 已删除,请执行: ./start-prod.sh install"
  253. ;;
  254. start)
  255. start_server
  256. ;;
  257. stop)
  258. stop_server
  259. ;;
  260. restart)
  261. stop_server || true
  262. start_server
  263. ;;
  264. status)
  265. status_server
  266. ;;
  267. logs)
  268. show_logs
  269. ;;
  270. -h|--help|help)
  271. show_help
  272. ;;
  273. *)
  274. log_error "未知命令: $ACTION"
  275. show_help
  276. exit 1
  277. ;;
  278. esac