Backend: - Add setup-backend.sh/bat for one-click backend setup - Fix test_auth.py mock settings (JWT_EXPIRE_HOURS) - Fix test_excel_export.py TEMPLATE_DIR reference Frontend: - Add config.json for runtime API URL configuration - Add init.js and settings.js for config loading - Update main.js to load config from external file - Update api.js to use dynamic API_BASE_URL - Update all pages to initialize config before API calls - Update package.json with extraResources for config Build: - Add build-client.sh/bat for packaging Electron + Sidecar - Add build-all.ps1 PowerShell script with -ApiUrl parameter - Add GitHub Actions workflow for Windows builds - Add scripts/README.md documentation This allows IT to configure backend URL without rebuilding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
394 lines
10 KiB
Bash
Executable File
394 lines
10 KiB
Bash
Executable File
#!/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 "$@"
|