#!/bin/bash # KVM Manager 一键启动脚本 # 用法: ./start.sh [start|stop|restart|status] set -e # ── 配置 ────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" BACKEND_DIR="$SCRIPT_DIR/backend" FRONTEND_DIR="$SCRIPT_DIR/frontend" LOG_DIR="$SCRIPT_DIR/logs" PID_DIR="$SCRIPT_DIR/.pid" BACKEND_PORT=8004 FRONTEND_PORT=8006 BACKEND_CMD="$BACKEND_DIR/venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port $BACKEND_PORT" FRONTEND_CMD="$BACKEND_DIR/venv/bin/python $FRONTEND_DIR/serve.py" # ── 颜色 ────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # ── 工具函数 ────────────────────────────────────────── info() { echo -e "${CYAN}[INFO]${NC} $1"; } ok() { echo -e "${GREEN}[OK]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } fail() { echo -e "${RED}[FAIL]${NC} $1"; } mkdir -p "$LOG_DIR" "$PID_DIR" # 检查端口是否被占用,返回 PID port_pid() { lsof -ti:$1 2>/dev/null | head -1 } # 等待端口可用 wait_port() { local port=$1 name=$2 max=10 count=0 while [ $count -lt $max ]; do if port_pid $port >/dev/null 2>&1; then return 0 fi sleep 1 count=$((count + 1)) done return 1 } # ── 启动后端 ────────────────────────────────────────── start_backend() { info "启动后端服务 (端口 $BACKEND_PORT)..." # 检查是否已运行 local existing_pid=$(port_pid $BACKEND_PORT) if [ -n "$existing_pid" ]; then warn "后端已在运行 (PID: $existing_pid)" return 0 fi # 检查 venv if [ ! -d "$BACKEND_DIR/venv" ]; then fail "未找到 Python 虚拟环境,请先运行: cd backend && python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt" return 1 fi # 检查 libvirtd if ! systemctl is-active --quiet libvirtd 2>/dev/null; then warn "libvirtd 未运行,尝试启动..." systemctl start libvirtd 2>/dev/null || warn "libvirtd 启动失败,后端可能无法连接虚拟机" fi cd "$BACKEND_DIR" nohup $BACKEND_CMD > "$LOG_DIR/backend.log" 2>&1 & local pid=$! echo $pid > "$PID_DIR/backend.pid" sleep 2 if port_pid $BACKEND_PORT >/dev/null 2>&1; then ok "后端服务已启动 (PID: $pid, 端口: $BACKEND_PORT)" ok "日志: $LOG_DIR/backend.log" else fail "后端服务启动失败,查看日志:" tail -20 "$LOG_DIR/backend.log" return 1 fi } # ── 启动前端 ────────────────────────────────────────── start_frontend() { info "启动前端服务 (端口 $FRONTEND_PORT)..." local existing_pid=$(port_pid $FRONTEND_PORT) if [ -n "$existing_pid" ]; then warn "前端已在运行 (PID: $existing_pid)" return 0 fi # 检查 dist if [ ! -d "$FRONTEND_DIR/dist" ]; then warn "未找到前端构建产物,正在构建..." cd "$FRONTEND_DIR" npm install --silent && npm run build if [ ! -d "$FRONTEND_DIR/dist" ]; then fail "前端构建失败" return 1 fi ok "前端构建完成" fi cd "$FRONTEND_DIR" nohup $FRONTEND_CMD > "$LOG_DIR/frontend.log" 2>&1 & local pid=$! echo $pid > "$PID_DIR/frontend.pid" sleep 2 if port_pid $FRONTEND_PORT >/dev/null 2>&1; then ok "前端服务已启动 (PID: $pid, 端口: $FRONTEND_PORT)" ok "日志: $LOG_DIR/frontend.log" else fail "前端服务启动失败,查看日志:" tail -20 "$LOG_DIR/frontend.log" return 1 fi } # ── 停止服务 ────────────────────────────────────────── stop_service() { local name=$1 port=$2 pidfile=$PID_DIR/$3.pid info "停止 $name (端口 $port)..." # 先尝试 PID 文件 if [ -f "$pidfile" ]; then local pid=$(cat "$pidfile") if ps -p "$pid" >/dev/null 2>&1; then kill "$pid" 2>/dev/null && ok "$name 已停止 (PID: $pid)" || warn "$name 停止失败" fi rm -f "$pidfile" fi # 再检查端口残留 local residual=$(port_pid $port) if [ -n "$residual" ]; then kill "$residual" 2>/dev/null && ok "$name 残留进程已清理 (PID: $residual)" || true fi } stop_all() { info "停止所有服务..." stop_service "后端" $BACKEND_PORT "backend" stop_service "前端" $FRONTEND_PORT "frontend" ok "所有服务已停止" } # ── 状态 ────────────────────────────────────────────── show_status() { echo "" echo -e "${CYAN}========================================${NC}" echo -e "${CYAN} KVM Manager 服务状态${NC}" echo -e "${CYAN}========================================${NC}" # 后端 local bpid=$(port_pid $BACKEND_PORT) if [ -n "$bpid" ]; then echo -e " 后端: ${GREEN}● 运行中${NC} PID: $bpid 端口: $BACKEND_PORT" else echo -e " 后端: ${RED}○ 未运行${NC}" fi # 前端 local fpid=$(port_pid $FRONTEND_PORT) if [ -n "$fpid" ]; then echo -e " 前端: ${GREEN}● 运行中${NC} PID: $fpid 端口: $FRONTEND_PORT" else echo -e " 前端: ${RED}○ 未运行${NC}" fi echo "" echo -e " 访问地址: ${YELLOW}http://localhost:$FRONTEND_PORT${NC}" echo -e " API文档: ${YELLOW}http://localhost:$BACKEND_PORT/docs${NC}" echo -e " 日志目录: ${YELLOW}$LOG_DIR${NC}" echo "" } # ── 查看日志 ────────────────────────────────────────── show_logs() { local target=${1:-all} case $target in backend|b) tail -f "$LOG_DIR/backend.log" 2>/dev/null || fail "后端日志不存在" ;; frontend|f) tail -f "$LOG_DIR/frontend.log" 2>/dev/null || fail "前端日志不存在" ;; *) tail -f "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log" 2>/dev/null || fail "日志不存在" ;; esac } # ── 主入口 ──────────────────────────────────────────── usage() { echo "" echo -e "${CYAN}KVM Manager 一键启动脚本${NC}" echo "" echo "用法: $0 <命令>" echo "" echo "命令:" echo " start 启动所有服务 (默认)" echo " stop 停止所有服务" echo " restart 重启所有服务" echo " status 查看服务状态" echo " logs 查看日志 (logs backend/frontend)" echo "" echo "示例:" echo " $0 # 启动所有" echo " $0 start # 启动所有" echo " $0 stop # 停止所有" echo " $0 restart # 重启所有" echo " $0 status # 查看状态" echo " $0 logs backend # 查看后端日志" echo "" } case "${1:-start}" in start) start_backend start_frontend show_status ;; stop) stop_all ;; restart) stop_all sleep 1 start_backend start_frontend show_status ;; status) show_status ;; logs) show_logs "${2:-all}" ;; -h|--help|help) usage ;; *) usage exit 1 ;; esac