#!/bin/bash # # Meeting Assistant - Startup Script # 啟動前端、後端及 Sidecar 服務 # set -e # 顏色定義 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 專案路徑 PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BACKEND_DIR="$PROJECT_DIR/backend" CLIENT_DIR="$PROJECT_DIR/client" SIDECAR_DIR="$PROJECT_DIR/sidecar" # Load environment variables from .env files if they exist if [ -f "$BACKEND_DIR/.env" ]; then log_info "Loading backend environment from $BACKEND_DIR/.env" export $(grep -v '^#' "$BACKEND_DIR/.env" | grep -v '^$' | xargs) fi if [ -f "$CLIENT_DIR/.env" ]; then log_info "Loading client environment from $CLIENT_DIR/.env" export $(grep -v '^#' "$CLIENT_DIR/.env" | grep -v '^$' | xargs) fi # Server Configuration (can be overridden by .env) BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}" BACKEND_PORT="${BACKEND_PORT:-8000}" # Whisper Configuration (can be overridden by .env) export WHISPER_MODEL="${WHISPER_MODEL:-medium}" # 模型大小: tiny, base, small, medium, large export WHISPER_DEVICE="${WHISPER_DEVICE:-cpu}" # 執行裝置: cpu, cuda export WHISPER_COMPUTE="${WHISPER_COMPUTE:-int8}" # 運算精度: int8, float16, float32 # PID 檔案 PID_FILE="$PROJECT_DIR/.running_pids" # 函數:印出訊息 log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[OK]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # 函數:檢查 port 是否被佔用 check_port() { local port=$1 if lsof -i :$port > /dev/null 2>&1; then return 0 # port 被佔用 else return 1 # port 可用 fi } # 函數:釋放 port release_port() { local port=$1 if check_port $port; then log_warn "Port $port 被佔用,嘗試釋放..." local pid=$(lsof -t -i :$port 2>/dev/null) if [ -n "$pid" ]; then kill -9 $pid 2>/dev/null || true sleep 1 log_success "Port $port 已釋放" fi fi } # 函數:檢查環境 check_environment() { echo "" echo "==========================================" echo " Meeting Assistant 環境檢查" echo "==========================================" echo "" local all_ok=true # 檢查 Python log_info "檢查 Python..." if command -v python3 &> /dev/null; then local py_version=$(python3 --version 2>&1) log_success "Python: $py_version" else log_error "Python3 未安裝" all_ok=false fi # 檢查 Node.js log_info "檢查 Node.js..." if command -v node &> /dev/null; then local node_version=$(node --version) log_success "Node.js: $node_version" else log_error "Node.js 未安裝" all_ok=false fi # 檢查 npm log_info "檢查 npm..." if command -v npm &> /dev/null; then local npm_version=$(npm --version) log_success "npm: $npm_version" else log_error "npm 未安裝" all_ok=false fi # 檢查後端虛擬環境 log_info "檢查後端虛擬環境..." if [ -d "$BACKEND_DIR/venv" ]; then log_success "後端虛擬環境: 存在" else log_error "後端虛擬環境不存在,請執行: cd backend && python3 -m venv venv" all_ok=false fi # 檢查 Sidecar 虛擬環境 log_info "檢查 Sidecar 虛擬環境..." if [ -d "$SIDECAR_DIR/venv" ]; then log_success "Sidecar 虛擬環境: 存在" else log_warn "Sidecar 虛擬環境不存在(語音轉寫功能將無法使用)" fi # 檢查前端依賴 log_info "檢查前端依賴..." if [ -d "$CLIENT_DIR/node_modules" ]; then log_success "前端依賴: 已安裝" else log_error "前端依賴未安裝,請執行: cd client && npm install" all_ok=false fi # 檢查後端 .env log_info "檢查後端環境變數..." if [ -f "$BACKEND_DIR/.env" ]; then log_success "後端 .env: 存在" else log_warn "後端 .env 不存在,請複製 .env.example 並設定" fi # 檢查 Port 狀態 log_info "檢查 Port $BACKEND_PORT..." if check_port $BACKEND_PORT; then log_warn "Port $BACKEND_PORT 已被佔用" else log_success "Port $BACKEND_PORT: 可用" fi echo "" if [ "$all_ok" = true ]; then log_success "環境檢查通過!" return 0 else log_error "環境檢查失敗,請修正上述問題" return 1 fi } # 函數:啟動後端 start_backend() { log_info "啟動後端服務..." # 釋放 port release_port $BACKEND_PORT cd "$BACKEND_DIR" source venv/bin/activate # 背景啟動 uvicorn nohup uvicorn app.main:app --host 0.0.0.0 --port $BACKEND_PORT --reload > "$PROJECT_DIR/backend.log" 2>&1 & local backend_pid=$! echo "BACKEND_PID=$backend_pid" >> "$PID_FILE" # 等待啟動(最多等待 15 秒) local max_wait=15 local waited=0 log_info "等待後端服務啟動..." while [ $waited -lt $max_wait ]; do sleep 1 waited=$((waited + 1)) # 檢查健康狀態 if curl -s http://localhost:$BACKEND_PORT/api/health > /dev/null 2>&1; then log_success "後端服務已啟動 (PID: $backend_pid, Port: $BACKEND_PORT)" return 0 fi done # 最後再檢查一次 port 狀態 if check_port $BACKEND_PORT; then log_success "後端服務已啟動 (PID: $backend_pid, Port: $BACKEND_PORT)" else log_error "後端服務啟動失敗,請檢查 backend.log" return 1 fi } # 函數:啟動前端 start_frontend() { log_info "啟動前端應用..." cd "$CLIENT_DIR" # 背景啟動 Electron(它會自動管理 Sidecar) nohup npm start > "$PROJECT_DIR/frontend.log" 2>&1 & local frontend_pid=$! echo "FRONTEND_PID=$frontend_pid" >> "$PID_FILE" sleep 2 log_success "前端應用已啟動 (PID: $frontend_pid)" log_info "Sidecar 語音識別引擎將由 Electron 自動管理" } # 函數:停止所有服務 stop_all() { echo "" log_info "停止所有服務..." # 讀取 PID 檔案並終止程序 if [ -f "$PID_FILE" ]; then while IFS='=' read -r key pid; do if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then log_info "停止 $key (PID: $pid)..." kill -TERM "$pid" 2>/dev/null || true fi done < "$PID_FILE" rm -f "$PID_FILE" fi # 釋放 port release_port $BACKEND_PORT # 清理可能殘留的程序 pkill -f "uvicorn app.main:app" 2>/dev/null || true pkill -f "electron ." 2>/dev/null || true pkill -f "transcriber.py" 2>/dev/null || true sleep 1 log_success "所有服務已停止" } # 函數:顯示狀態 show_status() { echo "" echo "==========================================" echo " Meeting Assistant 服務狀態" echo "==========================================" echo "" # 檢查後端 if check_port $BACKEND_PORT; then local backend_pid=$(lsof -t -i :$BACKEND_PORT 2>/dev/null | head -1) log_success "後端服務: 運行中 (PID: $backend_pid, Port: $BACKEND_PORT)" else log_warn "後端服務: 未運行" fi # 檢查 Electron local electron_pid=$(pgrep -f "electron ." 2>/dev/null | head -1) if [ -n "$electron_pid" ]; then log_success "前端應用: 運行中 (PID: $electron_pid)" else log_warn "前端應用: 未運行" fi # 檢查 Sidecar local sidecar_pid=$(pgrep -f "transcriber.py" 2>/dev/null | head -1) if [ -n "$sidecar_pid" ]; then log_success "Sidecar: 運行中 (PID: $sidecar_pid)" else log_warn "Sidecar: 未運行(將在前端需要時自動啟動)" fi echo "" } # 函數:顯示幫助 show_help() { echo "" echo "Meeting Assistant 啟動腳本" echo "" echo "用法: $0 [命令]" echo "" echo "命令:" echo " start 啟動所有服務(後端 + 前端)" echo " stop 停止所有服務並釋放 port" echo " restart 重啟所有服務" echo " status 顯示服務狀態" echo " check 檢查環境設定" echo " backend 僅啟動後端服務" echo " frontend 僅啟動前端應用" echo " logs 顯示日誌" echo " help 顯示此幫助訊息" echo "" echo "範例:" echo " $0 start # 啟動所有服務" echo " $0 stop # 停止所有服務" echo " $0 status # 查看狀態" echo "" } # 函數:顯示日誌 show_logs() { echo "" echo "==========================================" echo " Meeting Assistant 日誌" echo "==========================================" if [ -f "$PROJECT_DIR/backend.log" ]; then echo "" echo "--- 後端日誌 (最後 20 行) ---" tail -20 "$PROJECT_DIR/backend.log" fi if [ -f "$PROJECT_DIR/frontend.log" ]; then echo "" echo "--- 前端日誌 (最後 20 行) ---" tail -20 "$PROJECT_DIR/frontend.log" fi } # 主程式 main() { local command=${1:-help} case $command in start) echo "" echo "==========================================" echo " Meeting Assistant 啟動中..." echo "==========================================" # 清理舊的 PID 檔案 rm -f "$PID_FILE" if ! check_environment; then exit 1 fi echo "" start_backend start_frontend echo "" echo "==========================================" log_success "Meeting Assistant 已啟動!" echo "==========================================" echo "" echo " 後端 API: http://localhost:$BACKEND_PORT" echo " API 文件: http://localhost:$BACKEND_PORT/docs" echo "" echo " 停止服務: $0 stop" echo " 查看狀態: $0 status" echo " 查看日誌: $0 logs" echo "" ;; stop) stop_all ;; restart) stop_all sleep 2 $0 start ;; status) show_status ;; check) check_environment ;; backend) release_port $BACKEND_PORT rm -f "$PID_FILE" start_backend ;; frontend) start_frontend ;; logs) show_logs ;; help|--help|-h) show_help ;; *) log_error "未知命令: $command" show_help exit 1 ;; esac } # 捕捉中斷信號 trap 'echo ""; log_info "收到中斷信號,停止服務..."; stop_all; exit 0' INT TERM # 執行主程式 main "$@"