#!/bin/bash # # Meeting Assistant Backend - 一鍵設置與啟動腳本 # 自動安裝依賴、設置環境並啟動後端服務 # set -e # 顏色定義 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # 專案路徑 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" BACKEND_DIR="$PROJECT_DIR/backend" SIDECAR_DIR="$PROJECT_DIR/sidecar" # 預設配置 DEFAULT_PORT=8000 DEFAULT_HOST="0.0.0.0" # 函數:印出訊息 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" } log_step() { echo -e "${CYAN}[STEP]${NC} $1" } # 函數:顯示標題 show_banner() { echo "" echo -e "${CYAN}==========================================" echo " Meeting Assistant Backend Setup" echo " 一鍵設置與啟動腳本" echo -e "==========================================${NC}" echo "" } # 函數:檢查 Python 版本 check_python() { log_step "檢查 Python 環境..." # 嘗試不同的 Python 命令 local python_cmd="" for cmd in python3 python; do if command -v $cmd &> /dev/null; then local version=$($cmd -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') local major=$($cmd -c 'import sys; print(sys.version_info.major)') local minor=$($cmd -c 'import sys; print(sys.version_info.minor)') if [ "$major" -ge 3 ] && [ "$minor" -ge 10 ]; then python_cmd=$cmd log_success "Python $version ($cmd)" break fi fi done if [ -z "$python_cmd" ]; then log_error "需要 Python 3.10 或更高版本" log_info "請安裝 Python: https://www.python.org/downloads/" exit 1 fi echo "$python_cmd" } # 函數:設置後端虛擬環境 setup_backend_venv() { local python_cmd=$1 log_step "設置後端虛擬環境..." cd "$BACKEND_DIR" if [ ! -d "venv" ]; then log_info "創建虛擬環境..." $python_cmd -m venv venv log_success "虛擬環境已創建" else log_info "虛擬環境已存在" fi # 啟動虛擬環境並安裝依賴 log_info "安裝後端依賴..." if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin" ]]; then # Windows source venv/Scripts/activate else # Linux/Mac source venv/bin/activate fi pip install --upgrade pip -q pip install -r requirements.txt -q log_success "後端依賴安裝完成" } # 函數:設置 Sidecar 虛擬環境 setup_sidecar_venv() { local python_cmd=$1 log_step "設置 Sidecar 虛擬環境..." cd "$SIDECAR_DIR" if [ ! -d "venv" ]; then log_info "創建 Sidecar 虛擬環境..." $python_cmd -m venv venv log_success "Sidecar 虛擬環境已創建" else log_info "Sidecar 虛擬環境已存在" fi # 啟動虛擬環境並安裝依賴 log_info "安裝 Sidecar 依賴 (這可能需要幾分鐘)..." if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin" ]]; then source venv/Scripts/activate else source venv/bin/activate fi pip install --upgrade pip -q pip install -r requirements.txt -q log_success "Sidecar 依賴安裝完成" } # 函數:設置環境變數檔案 setup_env_file() { log_step "檢查環境變數配置..." cd "$BACKEND_DIR" if [ ! -f ".env" ]; then if [ -f ".env.example" ]; then cp .env.example .env log_warn "已從 .env.example 創建 .env 檔案" log_warn "請編輯 $BACKEND_DIR/.env 設置資料庫和 API 密鑰" else log_error "找不到 .env.example 檔案" exit 1 fi else log_success "環境變數檔案已存在" fi } # 函數:檢查 port 是否被佔用 check_port() { local port=$1 if command -v lsof &> /dev/null; then if lsof -i :$port > /dev/null 2>&1; then return 0 # port 被佔用 fi elif command -v netstat &> /dev/null; then if netstat -tuln | grep -q ":$port "; then return 0 fi fi return 1 # port 可用 } # 函數:釋放 port release_port() { local port=$1 if check_port $port; then log_warn "Port $port 被佔用,嘗試釋放..." if command -v lsof &> /dev/null; then 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 else log_error "無法釋放 port,請手動關閉佔用 $port 的程序" exit 1 fi fi } # 函數:啟動後端服務 start_backend() { local host=${1:-$DEFAULT_HOST} local port=${2:-$DEFAULT_PORT} local mode=${3:-"foreground"} # foreground 或 background log_step "啟動後端服務..." cd "$BACKEND_DIR" # 載入環境變數 if [ -f ".env" ]; then export $(grep -v '^#' .env | grep -v '^$' | xargs) fi # 使用 .env 中的配置或預設值 host=${BACKEND_HOST:-$host} port=${BACKEND_PORT:-$port} # 釋放 port release_port $port # 啟動虛擬環境 if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin" ]]; then source venv/Scripts/activate else source venv/bin/activate fi echo "" log_success "後端服務準備就緒!" echo "" echo -e "${CYAN}==========================================" echo " 服務資訊" echo -e "==========================================${NC}" echo "" echo " API 地址: http://localhost:$port" echo " API 文件: http://localhost:$port/docs" echo " 健康檢查: http://localhost:$port/api/health" echo "" echo " 按 Ctrl+C 停止服務" echo "" if [ "$mode" = "background" ]; then nohup uvicorn app.main:app --host $host --port $port --reload > "$PROJECT_DIR/backend.log" 2>&1 & local pid=$! echo $pid > "$PROJECT_DIR/.backend_pid" log_success "後端服務已在背景啟動 (PID: $pid)" log_info "查看日誌: tail -f $PROJECT_DIR/backend.log" else # 前景執行 uvicorn app.main:app --host $host --port $port --reload fi } # 函數:顯示幫助 show_help() { echo "" echo "Meeting Assistant Backend - 一鍵設置與啟動腳本" echo "" echo "用法: $0 [命令] [選項]" echo "" echo "命令:" echo " setup 僅設置環境 (安裝依賴)" echo " start 設置並啟動後端服務 (前景執行)" echo " start-bg 設置並啟動後端服務 (背景執行)" echo " stop 停止背景執行的後端服務" echo " help 顯示此幫助訊息" echo "" echo "選項:" echo " --port PORT 服務端口 (預設: $DEFAULT_PORT)" echo " --host HOST 綁定地址 (預設: $DEFAULT_HOST)" echo " --no-sidecar 不安裝 Sidecar 依賴" echo "" echo "範例:" echo " $0 start # 設置並啟動服務" echo " $0 start --port 8080 # 使用自訂端口" echo " $0 setup # 僅安裝依賴" echo " $0 start-bg # 背景執行" echo "" } # 函數:停止背景服務 stop_backend() { log_step "停止後端服務..." if [ -f "$PROJECT_DIR/.backend_pid" ]; then local pid=$(cat "$PROJECT_DIR/.backend_pid") if kill -0 $pid 2>/dev/null; then kill $pid rm -f "$PROJECT_DIR/.backend_pid" log_success "後端服務已停止 (PID: $pid)" else rm -f "$PROJECT_DIR/.backend_pid" log_warn "服務已經停止" fi else # 嘗試通過 port 查找 if command -v lsof &> /dev/null; then local port=${BACKEND_PORT:-$DEFAULT_PORT} local pid=$(lsof -t -i :$port 2>/dev/null) if [ -n "$pid" ]; then kill $pid 2>/dev/null || true log_success "已停止 port $port 上的服務" else log_warn "沒有找到運行中的後端服務" fi fi fi } # 主程式 main() { local command=${1:-"start"} local port=$DEFAULT_PORT local host=$DEFAULT_HOST local setup_sidecar=true # 解析參數 shift || true while [[ $# -gt 0 ]]; do case $1 in --port) port="$2" shift 2 ;; --host) host="$2" shift 2 ;; --no-sidecar) setup_sidecar=false shift ;; *) log_error "未知參數: $1" show_help exit 1 ;; esac done case $command in setup) show_banner local python_cmd=$(check_python) setup_backend_venv "$python_cmd" if [ "$setup_sidecar" = true ]; then setup_sidecar_venv "$python_cmd" fi setup_env_file echo "" log_success "環境設置完成!" echo "" log_info "啟動服務: $0 start" ;; start) show_banner local python_cmd=$(check_python) setup_backend_venv "$python_cmd" setup_env_file start_backend "$host" "$port" "foreground" ;; start-bg) show_banner local python_cmd=$(check_python) setup_backend_venv "$python_cmd" setup_env_file start_backend "$host" "$port" "background" ;; stop) stop_backend ;; help|--help|-h) show_help ;; *) log_error "未知命令: $command" show_help exit 1 ;; esac } # 捕捉中斷信號 trap 'echo ""; log_info "收到中斷信號"; exit 0' INT TERM # 執行主程式 main "$@"