start.sh 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #!/bin/bash
  2. # KVM Manager 一键启动脚本
  3. # 用法: ./start.sh [start|stop|restart|status]
  4. set -e
  5. # ── 配置 ──────────────────────────────────────────────
  6. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  7. BACKEND_DIR="$SCRIPT_DIR/backend"
  8. FRONTEND_DIR="$SCRIPT_DIR/frontend"
  9. LOG_DIR="$SCRIPT_DIR/logs"
  10. PID_DIR="$SCRIPT_DIR/.pid"
  11. BACKEND_PORT=8004
  12. FRONTEND_PORT=8006
  13. BACKEND_CMD="$BACKEND_DIR/venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port $BACKEND_PORT"
  14. FRONTEND_CMD="$BACKEND_DIR/venv/bin/python $FRONTEND_DIR/serve.py"
  15. # ── 颜色 ──────────────────────────────────────────────
  16. RED='\033[0;31m'
  17. GREEN='\033[0;32m'
  18. YELLOW='\033[1;33m'
  19. CYAN='\033[0;36m'
  20. NC='\033[0m'
  21. # ── 工具函数 ──────────────────────────────────────────
  22. info() { echo -e "${CYAN}[INFO]${NC} $1"; }
  23. ok() { echo -e "${GREEN}[OK]${NC} $1"; }
  24. warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
  25. fail() { echo -e "${RED}[FAIL]${NC} $1"; }
  26. mkdir -p "$LOG_DIR" "$PID_DIR"
  27. # 检查端口是否被占用,返回 PID
  28. port_pid() {
  29. lsof -ti:$1 2>/dev/null | head -1
  30. }
  31. # 等待端口可用
  32. wait_port() {
  33. local port=$1 name=$2 max=10 count=0
  34. while [ $count -lt $max ]; do
  35. if port_pid $port >/dev/null 2>&1; then
  36. return 0
  37. fi
  38. sleep 1
  39. count=$((count + 1))
  40. done
  41. return 1
  42. }
  43. # ── 启动后端 ──────────────────────────────────────────
  44. start_backend() {
  45. info "启动后端服务 (端口 $BACKEND_PORT)..."
  46. # 检查是否已运行
  47. local existing_pid=$(port_pid $BACKEND_PORT)
  48. if [ -n "$existing_pid" ]; then
  49. warn "后端已在运行 (PID: $existing_pid)"
  50. return 0
  51. fi
  52. # 检查 venv
  53. if [ ! -d "$BACKEND_DIR/venv" ]; then
  54. fail "未找到 Python 虚拟环境,请先运行: cd backend && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt"
  55. return 1
  56. fi
  57. # 检查 libvirtd
  58. if ! systemctl is-active --quiet libvirtd 2>/dev/null; then
  59. warn "libvirtd 未运行,尝试启动..."
  60. systemctl start libvirtd 2>/dev/null || warn "libvirtd 启动失败,后端可能无法连接虚拟机"
  61. fi
  62. cd "$BACKEND_DIR"
  63. nohup $BACKEND_CMD > "$LOG_DIR/backend.log" 2>&1 &
  64. local pid=$!
  65. echo $pid > "$PID_DIR/backend.pid"
  66. sleep 2
  67. if port_pid $BACKEND_PORT >/dev/null 2>&1; then
  68. ok "后端服务已启动 (PID: $pid, 端口: $BACKEND_PORT)"
  69. ok "日志: $LOG_DIR/backend.log"
  70. else
  71. fail "后端服务启动失败,查看日志:"
  72. tail -20 "$LOG_DIR/backend.log"
  73. return 1
  74. fi
  75. }
  76. # ── 启动前端 ──────────────────────────────────────────
  77. start_frontend() {
  78. info "启动前端服务 (端口 $FRONTEND_PORT)..."
  79. local existing_pid=$(port_pid $FRONTEND_PORT)
  80. if [ -n "$existing_pid" ]; then
  81. warn "前端已在运行 (PID: $existing_pid)"
  82. return 0
  83. fi
  84. # 检查 dist
  85. if [ ! -d "$FRONTEND_DIR/dist" ]; then
  86. warn "未找到前端构建产物,正在构建..."
  87. cd "$FRONTEND_DIR"
  88. npm install --silent && npm run build
  89. if [ ! -d "$FRONTEND_DIR/dist" ]; then
  90. fail "前端构建失败"
  91. return 1
  92. fi
  93. ok "前端构建完成"
  94. fi
  95. cd "$FRONTEND_DIR"
  96. nohup $FRONTEND_CMD > "$LOG_DIR/frontend.log" 2>&1 &
  97. local pid=$!
  98. echo $pid > "$PID_DIR/frontend.pid"
  99. sleep 2
  100. if port_pid $FRONTEND_PORT >/dev/null 2>&1; then
  101. ok "前端服务已启动 (PID: $pid, 端口: $FRONTEND_PORT)"
  102. ok "日志: $LOG_DIR/frontend.log"
  103. else
  104. fail "前端服务启动失败,查看日志:"
  105. tail -20 "$LOG_DIR/frontend.log"
  106. return 1
  107. fi
  108. }
  109. # ── 停止服务 ──────────────────────────────────────────
  110. stop_service() {
  111. local name=$1 port=$2 pidfile=$PID_DIR/$3.pid
  112. info "停止 $name (端口 $port)..."
  113. # 先尝试 PID 文件
  114. if [ -f "$pidfile" ]; then
  115. local pid=$(cat "$pidfile")
  116. if ps -p "$pid" >/dev/null 2>&1; then
  117. kill "$pid" 2>/dev/null && ok "$name 已停止 (PID: $pid)" || warn "$name 停止失败"
  118. fi
  119. rm -f "$pidfile"
  120. fi
  121. # 再检查端口残留
  122. local residual=$(port_pid $port)
  123. if [ -n "$residual" ]; then
  124. kill "$residual" 2>/dev/null && ok "$name 残留进程已清理 (PID: $residual)" || true
  125. fi
  126. }
  127. stop_all() {
  128. info "停止所有服务..."
  129. stop_service "后端" $BACKEND_PORT "backend"
  130. stop_service "前端" $FRONTEND_PORT "frontend"
  131. ok "所有服务已停止"
  132. }
  133. # ── 状态 ──────────────────────────────────────────────
  134. show_status() {
  135. echo ""
  136. echo -e "${CYAN}========================================${NC}"
  137. echo -e "${CYAN} KVM Manager 服务状态${NC}"
  138. echo -e "${CYAN}========================================${NC}"
  139. # 后端
  140. local bpid=$(port_pid $BACKEND_PORT)
  141. if [ -n "$bpid" ]; then
  142. echo -e " 后端: ${GREEN}● 运行中${NC} PID: $bpid 端口: $BACKEND_PORT"
  143. else
  144. echo -e " 后端: ${RED}○ 未运行${NC}"
  145. fi
  146. # 前端
  147. local fpid=$(port_pid $FRONTEND_PORT)
  148. if [ -n "$fpid" ]; then
  149. echo -e " 前端: ${GREEN}● 运行中${NC} PID: $fpid 端口: $FRONTEND_PORT"
  150. else
  151. echo -e " 前端: ${RED}○ 未运行${NC}"
  152. fi
  153. echo ""
  154. echo -e " 访问地址: ${YELLOW}http://localhost:$FRONTEND_PORT${NC}"
  155. echo -e " API文档: ${YELLOW}http://localhost:$BACKEND_PORT/docs${NC}"
  156. echo -e " 日志目录: ${YELLOW}$LOG_DIR${NC}"
  157. echo ""
  158. }
  159. # ── 查看日志 ──────────────────────────────────────────
  160. show_logs() {
  161. local target=${1:-all}
  162. case $target in
  163. backend|b) tail -f "$LOG_DIR/backend.log" 2>/dev/null || fail "后端日志不存在" ;;
  164. frontend|f) tail -f "$LOG_DIR/frontend.log" 2>/dev/null || fail "前端日志不存在" ;;
  165. *) tail -f "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log" 2>/dev/null || fail "日志不存在" ;;
  166. esac
  167. }
  168. # ── 主入口 ────────────────────────────────────────────
  169. usage() {
  170. echo ""
  171. echo -e "${CYAN}KVM Manager 一键启动脚本${NC}"
  172. echo ""
  173. echo "用法: $0 <命令>"
  174. echo ""
  175. echo "命令:"
  176. echo " start 启动所有服务 (默认)"
  177. echo " stop 停止所有服务"
  178. echo " restart 重启所有服务"
  179. echo " status 查看服务状态"
  180. echo " logs 查看日志 (logs backend/frontend)"
  181. echo ""
  182. echo "示例:"
  183. echo " $0 # 启动所有"
  184. echo " $0 start # 启动所有"
  185. echo " $0 stop # 停止所有"
  186. echo " $0 restart # 重启所有"
  187. echo " $0 status # 查看状态"
  188. echo " $0 logs backend # 查看后端日志"
  189. echo ""
  190. }
  191. case "${1:-start}" in
  192. start)
  193. start_backend
  194. start_frontend
  195. show_status
  196. ;;
  197. stop)
  198. stop_all
  199. ;;
  200. restart)
  201. stop_all
  202. sleep 1
  203. start_backend
  204. start_frontend
  205. show_status
  206. ;;
  207. status)
  208. show_status
  209. ;;
  210. logs)
  211. show_logs "${2:-all}"
  212. ;;
  213. -h|--help|help)
  214. usage
  215. ;;
  216. *)
  217. usage
  218. exit 1
  219. ;;
  220. esac