- Add environment variable configuration for backend and frontend - Backend: DB_POOL_SIZE, JWT_EXPIRE_HOURS, timeout configs, directory paths - Frontend: VITE_API_BASE_URL, VITE_UPLOAD_TIMEOUT, Whisper configs - Create deployment script (scripts/deploy-backend.sh) - Create 1Panel deployment guide (docs/1panel-deployment.md) - Update DEPLOYMENT.md with env var documentation - Create README.md with project overview - Remove obsolete PRD.md, SDD.md, TDD.md (replaced by OpenSpec) - Keep CORS allow_origins=["*"] for Electron EXE distribution 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
418 lines
11 KiB
Bash
Executable File
418 lines
11 KiB
Bash
Executable File
#!/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 "$@"
|