refactor: simplify deployment - unified env and startup script

- Remove Docker deployment files (1panel doesn't use Docker)
- Unify .env files: .env.example -> .env (single config file)
- Merge start.sh and start-prod.sh into unified start.sh with --prod flag
- Update setup_dev_env.sh to use .env instead of .env.local
- Add DEPLOY.md with 1panel deployment guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-14 15:16:26 +08:00
parent e255039419
commit f46402f6c9
13 changed files with 375 additions and 841 deletions

111
.env
View File

@@ -1,49 +1,116 @@
# Tool_OCR - Production/Docker Environment Configuration
# For local development, copy .env.example to .env.local and configure there
#
# This file is for Docker deployment or production use.
# Sensitive values should be set via environment variables or secrets management.
# Tool_OCR - Environment Configuration Template
# Copy this file to .env and fill in your actual values
# ===== Database Configuration =====
# Set these via Docker secrets or environment variables in production
MYSQL_HOST=${MYSQL_HOST:-localhost}
MYSQL_PORT=${MYSQL_PORT:-3306}
MYSQL_USER=${MYSQL_USER:-}
MYSQL_PASSWORD=${MYSQL_PASSWORD:-}
MYSQL_DATABASE=${MYSQL_DATABASE:-}
MYSQL_HOST=mysql.theaken.com
MYSQL_PORT=33306
MYSQL_USER=A060
MYSQL_PASSWORD=WLeSCi0yhtc7
MYSQL_DATABASE=db_A060
# ===== Application Configuration =====
# Production port (different from development)
FRONTEND_PORT=12010
# Server ports
BACKEND_PORT=8000
FRONTEND_PORT=5173
# Security - MUST be set via environment variable in production
SECRET_KEY=${SECRET_KEY:-change-this-in-production}
# Security
SECRET_KEY=your-secret-key-here-please-change-this-to-random-string
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440
# ===== External Authentication Configuration =====
EXTERNAL_AUTH_API_URL=${EXTERNAL_AUTH_API_URL:-https://your-auth-api.example.com}
# External authentication API URL
EXTERNAL_AUTH_API_URL=https://pj-auth-api.vercel.app
# Authentication endpoint path
EXTERNAL_AUTH_ENDPOINT=/api/auth/login
# API request timeout in seconds
EXTERNAL_AUTH_TIMEOUT=30
# Token refresh buffer in seconds (refresh tokens X seconds before expiry)
TOKEN_REFRESH_BUFFER=300
# ===== Task Management Configuration =====
# Database table prefix for clear namespace separation
DATABASE_TABLE_PREFIX=tool_ocr_
# Enable task history feature
ENABLE_TASK_HISTORY=true
# Auto-delete old tasks after X days (0 to disable)
TASK_RETENTION_DAYS=30
# Maximum tasks per user (0 for unlimited)
MAX_TASKS_PER_USER=1000
# ===== OCR Configuration =====
# Note: PaddleOCR/PaddleX models are stored in ~/.paddleocr/ and ~/.paddlex/ by default
# Supported languages (comma-separated)
OCR_LANGUAGES=ch,en,japan,korean
# Default confidence threshold
OCR_CONFIDENCE_THRESHOLD=0.5
# Maximum concurrent OCR workers
MAX_OCR_WORKERS=4
# ===== File Configuration =====
# GPU Acceleration Configuration
# Force CPU mode even if GPU is available (default: false)
FORCE_CPU_MODE=false
# GPU memory fraction (0.0-1.0, default: 0.8)
GPU_MEMORY_FRACTION=0.8
# GPU device ID to use (default: 0, -1 for CPU)
GPU_DEVICE_ID=0
# ===== File Upload Configuration =====
# Maximum file size in bytes (50MB default)
MAX_UPLOAD_SIZE=52428800
# Allowed file extensions (comma-separated)
ALLOWED_EXTENSIONS=png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx
# Upload directories
UPLOAD_DIR=./uploads
TEMP_DIR=./uploads/temp
PROCESSED_DIR=./uploads/processed
IMAGES_DIR=./uploads/images
# ===== Export Configuration =====
# Storage directories
STORAGE_DIR=./storage
MARKDOWN_DIR=./storage/markdown
JSON_DIR=./storage/json
EXPORTS_DIR=./storage/exports
# ===== PDF Generation Configuration =====
# Pandoc path (Linux)
PANDOC_PATH=/usr/bin/pandoc
# WeasyPrint font directory
FONT_DIR=/usr/share/fonts
# Default PDF page size
PDF_PAGE_SIZE=A4
# Default PDF margins (mm)
PDF_MARGIN_TOP=20
PDF_MARGIN_BOTTOM=20
PDF_MARGIN_LEFT=20
PDF_MARGIN_RIGHT=20
# ===== Translation Configuration (DIFY API) =====
ENABLE_TRANSLATION=${ENABLE_TRANSLATION:-false}
DIFY_BASE_URL=${DIFY_BASE_URL:-}
DIFY_API_KEY=${DIFY_API_KEY:-}
# Enable translation feature
ENABLE_TRANSLATION=true
# DIFY API base URL
DIFY_BASE_URL=https://dify.theaken.com/v1
# DIFY API key (required for translation)
DIFY_API_KEY=app-YOPrF2ro5fshzMkCZviIuUJd
# API request timeout in seconds
DIFY_TIMEOUT=120.0
# Maximum retry attempts
DIFY_MAX_RETRIES=3
# Batch translation limits
DIFY_MAX_BATCH_CHARS=5000
DIFY_MAX_BATCH_ITEMS=20
# ===== Background Tasks Configuration =====
# Task queue type: memory (default) or redis (future)
TASK_QUEUE_TYPE=memory
# Redis URL (if using redis)
# REDIS_URL=redis://localhost:6379/0
# ===== CORS Configuration =====
CORS_ORIGINS=http://localhost:12010,http://127.0.0.1:12010
# Allowed origins (comma-separated, * for all)
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
# ===== Logging Configuration =====
LOG_LEVEL=INFO
LOG_LEVEL=DEBUG
LOG_FILE=./logs/app.log

View File

@@ -1,69 +1,35 @@
# Tool_OCR - Environment Configuration Template
# Copy this file to .env.local and fill in your actual values
#
# Note: Most path configurations have sensible defaults in config.py
# Only override if you need custom paths
# Tool_OCR - 環境變數配置
# 複製此檔案為 .env 並填入實際值
# cp .env.example .env
# ===== Database Configuration (Required) =====
# ===== 資料庫 =====
MYSQL_HOST=your-mysql-host
MYSQL_PORT=3306
MYSQL_USER=your-username
MYSQL_PASSWORD=your-password
MYSQL_DATABASE=your-database
# ===== Application Configuration =====
# Server ports
BACKEND_HOST=0.0.0.0
# ===== 服務端口 =====
# IT 會指派實際端口
BACKEND_PORT=8000
FRONTEND_HOST=0.0.0.0
FRONTEND_PORT=5173
# Security (generate a random string for production: openssl rand -hex 32)
SECRET_KEY=your-secret-key-here-please-change-this-to-random-string
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440
# ===== 安全性 =====
# 生產環境請使用: openssl rand -hex 32
SECRET_KEY=your-secret-key-change-this
# ===== External Authentication Configuration (Required) =====
# ===== 外部認證 =====
EXTERNAL_AUTH_API_URL=https://your-auth-api.example.com
EXTERNAL_AUTH_ENDPOINT=/api/auth/login
EXTERNAL_AUTH_TIMEOUT=30
TOKEN_REFRESH_BUFFER=300
# ===== Task Management Configuration =====
DATABASE_TABLE_PREFIX=tool_ocr_
ENABLE_TASK_HISTORY=true
TASK_RETENTION_DAYS=30
MAX_TASKS_PER_USER=1000
# ===== OCR Configuration =====
# Note: PaddleOCR/PaddleX models are stored in ~/.paddleocr/ and ~/.paddlex/ by default
OCR_LANGUAGES=ch,en,japan,korean
OCR_CONFIDENCE_THRESHOLD=0.5
MAX_OCR_WORKERS=4
# GPU Acceleration Configuration
FORCE_CPU_MODE=false
GPU_MEMORY_FRACTION=0.8
GPU_DEVICE_ID=0
# ===== File Upload Configuration =====
MAX_UPLOAD_SIZE=52428800
ALLOWED_EXTENSIONS=png,jpg,jpeg,pdf,bmp,tiff,doc,docx,ppt,pptx
# Path defaults to backend/uploads - only override if needed
# UPLOAD_DIR=./uploads
# ===== Translation Configuration (DIFY API) =====
# ===== 翻譯 API (DIFY) =====
ENABLE_TRANSLATION=true
DIFY_BASE_URL=https://your-dify-instance.example.com/v1
DIFY_API_KEY=your-dify-api-key
DIFY_TIMEOUT=120.0
DIFY_MAX_RETRIES=3
DIFY_MAX_BATCH_CHARS=5000
DIFY_MAX_BATCH_ITEMS=20
# ===== CORS Configuration =====
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
# ===== CORS =====
# 根據實際部署的前端 URL 設定
CORS_ORIGINS=http://localhost:5173
# ===== Logging Configuration =====
# ===== 日誌 =====
LOG_LEVEL=INFO
# LOG_FILE defaults to backend/logs/app.log

8
.gitignore vendored
View File

@@ -49,11 +49,9 @@ AGENTS.md
CLAUDE.md
# ===== Environment Variables =====
# Local environment files (contain secrets, never commit)
.env.local
.env.*.local
frontend/.env.local
frontend/.env.*.local
# 實際配置檔案包含敏感資訊,不追蹤
.env
frontend/.env
# ===== Process ID Files =====
.pid/

163
DEPLOY.md Normal file
View File

@@ -0,0 +1,163 @@
# Tool_OCR 佈署指南
本指南說明如何在 1panel 或其他 Linux 環境中佈署 Tool_OCR。
## 快速佈署
```bash
# 1. 複製並配置環境變數
cp .env.example .env
# 編輯 .env 填入實際的資料庫、API 金鑰等配置
# 2. 安裝依賴並初始化
./setup_dev_env.sh
# 3. 啟動服務
./start.sh --prod
```
## 詳細步驟
### 1. 系統需求
- Ubuntu 20.04+ 或相容的 Linux 發行版
- Python 3.10+
- Node.js 18+ (透過 nvm 自動安裝)
- 至少 4GB RAM
- 至少 10GB 磁碟空間
### 2. 環境配置
複製範例配置檔:
```bash
cp .env.example .env
```
編輯 `.env` 填入實際值:
```bash
# 資料庫 (必填)
MYSQL_HOST=your-mysql-host
MYSQL_PORT=3306
MYSQL_USER=your-username
MYSQL_PASSWORD=your-password
MYSQL_DATABASE=your-database
# 服務端口 (由 IT 指派)
BACKEND_PORT=8000
FRONTEND_PORT=5173
# 安全性 (必填,請使用隨機字串)
SECRET_KEY=your-random-secret-key
# 外部認證 (必填)
EXTERNAL_AUTH_API_URL=https://your-auth-api.example.com
# 翻譯 API (選填)
ENABLE_TRANSLATION=true
DIFY_BASE_URL=https://your-dify-instance.example.com/v1
DIFY_API_KEY=your-dify-api-key
# CORS (根據前端 URL 設定)
CORS_ORIGINS=http://localhost:5173
```
### 3. 安裝依賴
執行設置腳本:
```bash
./setup_dev_env.sh
```
此腳本會:
- 安裝系統依賴 (pandoc, fonts, opencv 等)
- 安裝 Node.js (透過 nvm)
- 建立 Python 虛擬環境
- 自動偵測 GPU 並安裝對應的 PaddlePaddle 版本
- 安裝前端依賴
- 初始化資料庫
### 4. 啟動服務
**開發模式** (hot-reload):
```bash
./start.sh
```
**正式模式** (無 hot-reload多 worker):
```bash
./start.sh --prod
```
**自訂端口**:
```bash
BACKEND_PORT=8088 FRONTEND_PORT=3000 ./start.sh --prod
```
### 5. 服務管理
```bash
# 查看狀態
./start.sh --status
# 停止服務
./start.sh --stop
# 僅啟動後端
./start.sh backend
# 僅啟動前端
./start.sh frontend
```
### 6. 日誌
```bash
# 後端日誌
tail -f .pid/backend.log
# 前端日誌
tail -f .pid/frontend.log
```
## 1panel 設定
在 1panel 中設定為「應用」或「腳本任務」:
1. 工作目錄設為專案根目錄
2. 啟動指令:`./start.sh --prod`
3. 停止指令:`./start.sh --stop`
4. 環境變數:可在 `.env` 中配置,或透過 1panel 的環境變數功能設定端口
## 常見問題
### GPU 支援
如果有 NVIDIA GPU`setup_dev_env.sh` 會自動偵測並安裝 GPU 版本的 PaddlePaddle。
強制使用 CPU 版本:
```bash
./setup_dev_env.sh --cpu-only
```
### 資料庫連線失敗
1. 確認 `.env` 中的資料庫配置正確
2. 手動執行資料庫遷移:
```bash
source venv/bin/activate
cd backend
alembic upgrade head
```
### 端口被佔用
檢查並終止佔用端口的程序:
```bash
lsof -i :8000 # 檢查後端端口
lsof -i :5173 # 檢查前端端口
```
或使用不同端口:
```bash
BACKEND_PORT=8088 FRONTEND_PORT=3000 ./start.sh --prod
```

View File

@@ -1,24 +0,0 @@
# Tool_OCR - 1Panel Docker Deployment Configuration
# Copy this file to .env and fill in your values
# ===== Service Ports =====
BACKEND_PORT=8000
FRONTEND_PORT=12010
# ===== Database Configuration =====
MYSQL_HOST=your-mysql-host
MYSQL_PORT=3306
MYSQL_USER=your-username
MYSQL_PASSWORD=your-password
MYSQL_DATABASE=your-database
# ===== Security =====
# Generate a random string for production: openssl rand -hex 32
SECRET_KEY=your-secret-key-change-this
# ===== Translation API (DIFY) =====
DIFY_BASE_URL=https://your-dify-instance.example.com/v1
DIFY_API_KEY=your-dify-api-key
# ===== Logging =====
LOG_LEVEL=INFO

View File

@@ -1,67 +0,0 @@
# Tool_OCR Backend Dockerfile
# Multi-stage build for optimized image size
# Stage 1: Build environment
FROM python:3.12-slim as builder
WORKDIR /build
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
git \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install dependencies
COPY backend/requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Stage 2: Runtime environment
FROM python:3.12-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1-mesa-glx \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender1 \
libgomp1 \
pandoc \
poppler-utils \
fonts-dejavu-core \
fonts-noto-cjk \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy Python packages from builder
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# Copy application code
COPY backend/ /app/backend/
# Create necessary directories
RUN mkdir -p /app/backend/uploads/{temp,processed,images} \
&& mkdir -p /app/backend/storage/{markdown,json,exports,results} \
&& mkdir -p /app/backend/models/paddleocr \
&& mkdir -p /app/backend/logs
# Set working directory
WORKDIR /app/backend
# Environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Run application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

View File

@@ -1,38 +0,0 @@
# Tool_OCR Frontend Dockerfile
# Multi-stage build for optimized image size
# Stage 1: Build
FROM node:20-alpine as builder
WORKDIR /app
# Copy package files
COPY frontend/package*.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY frontend/ ./
# Build application
RUN npm run build
# Stage 2: Production
FROM nginx:alpine
# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY deploy/1panel/nginx.conf /etc/nginx/conf.d/default.conf
# Expose port
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:80 || exit 1
# Run nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,125 +0,0 @@
# Tool_OCR - 1Panel Docker 佈署指南
本目錄包含 1Panel 平台的 Docker 佈署配置。
## 前置需求
- Docker 20.10+
- Docker Compose 2.0+
- NVIDIA Container ToolkitGPU 加速)
- MySQL 8.0+(外部資料庫)
## 佈署步驟
### 1. 配置環境變數
```bash
# 複製環境變數範本
cp .env.example .env
# 編輯配置
nano .env
```
必要配置項:
- `MYSQL_*` - 資料庫連線設定
- `SECRET_KEY` - JWT 簽名金鑰(使用 `openssl rand -hex 32` 生成)
- `DIFY_*` - 翻譯 API 設定
### 2. 建置映像
```bash
# 建置所有服務
docker-compose build
# 或分別建置
docker-compose build backend
docker-compose build frontend
```
### 3. 啟動服務
```bash
# 啟動所有服務
docker-compose up -d
# 查看日誌
docker-compose logs -f
# 查看狀態
docker-compose ps
```
### 4. 存取服務
- 前端介面http://your-server:12010
- API 文件http://your-server:8000/docs
- 健康檢查http://your-server:8000/health
## 服務管理
```bash
# 停止服務
docker-compose down
# 重啟服務
docker-compose restart
# 更新服務
docker-compose pull
docker-compose up -d --build
# 查看資源使用
docker stats
```
## 資料持久化
以下 Docker volumes 會自動建立:
| Volume | 用途 |
|--------|------|
| `tool_ocr_uploads` | 上傳檔案 |
| `tool_ocr_storage` | 處理結果 |
| `tool_ocr_logs` | 應用日誌 |
| `tool_ocr_models` | OCR 模型快取 |
## GPU 加速
預設配置會使用 NVIDIA GPU。如果不需要 GPU 加速,請修改 `docker-compose.yml`,移除 `deploy.resources.reservations` 區塊。
## 常見問題
### Q: 服務啟動失敗?
檢查日誌:
```bash
docker-compose logs backend
docker-compose logs frontend
```
### Q: 資料庫連線失敗?
確認:
1. MySQL 服務正在運行
2. 防火牆允許連接
3. 使用者權限正確
### Q: GPU 不可用?
確認 NVIDIA Container Toolkit 已安裝:
```bash
nvidia-smi
docker run --rm --gpus all nvidia/cuda:11.8.0-base-ubuntu22.04 nvidia-smi
```
## 1Panel 整合
在 1Panel 中:
1. 進入「應用商店」→「自訂應用」
2. 上傳此目錄的所有檔案
3. 配置環境變數
4. 點擊「安裝」
或使用 1Panel 的「容器」功能直接導入 `docker-compose.yml`

View File

@@ -1,68 +0,0 @@
version: '3.8'
services:
backend:
build:
context: ../..
dockerfile: deploy/1panel/Dockerfile.backend
container_name: tool_ocr_backend
restart: unless-stopped
ports:
- "${BACKEND_PORT:-8000}:8000"
environment:
- MYSQL_HOST=${MYSQL_HOST}
- MYSQL_PORT=${MYSQL_PORT}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- SECRET_KEY=${SECRET_KEY}
- DIFY_BASE_URL=${DIFY_BASE_URL}
- DIFY_API_KEY=${DIFY_API_KEY}
- CORS_ORIGINS=http://localhost:${FRONTEND_PORT:-12010}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
volumes:
- tool_ocr_uploads:/app/backend/uploads
- tool_ocr_storage:/app/backend/storage
- tool_ocr_logs:/app/backend/logs
- tool_ocr_models:/app/backend/models
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
frontend:
build:
context: ../..
dockerfile: deploy/1panel/Dockerfile.frontend
container_name: tool_ocr_frontend
restart: unless-stopped
ports:
- "${FRONTEND_PORT:-12010}:80"
environment:
- VITE_API_BASE_URL=http://localhost:${BACKEND_PORT:-8000}
depends_on:
- backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 30s
timeout: 10s
retries: 3
volumes:
tool_ocr_uploads:
tool_ocr_storage:
tool_ocr_logs:
tool_ocr_models:
networks:
default:
name: tool_ocr_network

View File

@@ -1,37 +0,0 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# SPA routing - all routes go to index.html
location / {
try_files $uri $uri/ /index.html;
}
# Static assets caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}

View File

@@ -227,17 +227,17 @@ init_database() {
echo "初始化數據庫..."
cd "$SCRIPT_DIR/backend"
# 檢查 .env.local 是否存在
if [ ! -f "$SCRIPT_DIR/.env.local" ]; then
print_warning "未找到 .env.local,跳過數據庫初始化"
echo " 請複製 .env.example 到 .env.local 並配置後,手動執行:"
# 檢查 .env 是否存在
if [ ! -f "$SCRIPT_DIR/.env" ]; then
print_warning "未找到 .env跳過數據庫初始化"
echo " 請複製 .env.example 到 .env 並配置後,手動執行:"
echo " cd backend && alembic upgrade head"
cd "$SCRIPT_DIR"
return
fi
# 載入環境變量
export $(grep -v '^#' "$SCRIPT_DIR/.env.local" | xargs)
export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
# 執行遷移
if alembic upgrade head 2>/dev/null; then
@@ -414,8 +414,8 @@ echo ""
echo "下一步操作:"
echo ""
echo "1. 配置環境變量 (如尚未配置):"
echo " cp .env.example .env.local"
echo " # 編輯 .env.local 填入實際配置"
echo " cp .env.example .env"
echo " # 編輯 .env 填入實際配置"
echo ""
echo "2. 啟動應用:"
echo " ./start.sh # 同時啟動前後端"

View File

@@ -1,375 +0,0 @@
#!/bin/bash
# Tool_OCR - Production Server Startup Script
# Usage:
# ./start-prod.sh Start all services (backend + frontend)
# ./start-prod.sh backend Start only backend
# ./start-prod.sh frontend Start only frontend
# ./start-prod.sh --stop Stop all services
# ./start-prod.sh --status Show service status
# ./start-prod.sh --help Show help
# Note: We don't use 'set -e' here because stop commands may fail gracefully
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PID_DIR="$SCRIPT_DIR/.pid-prod"
BACKEND_PID_FILE="$PID_DIR/backend.pid"
FRONTEND_PID_FILE="$PID_DIR/frontend.pid"
# Production defaults (different from development)
BACKEND_HOST=${BACKEND_HOST:-0.0.0.0}
BACKEND_PORT=${BACKEND_PORT:-8000}
FRONTEND_HOST=${FRONTEND_HOST:-0.0.0.0}
FRONTEND_PORT=${FRONTEND_PORT:-12010}
# Production-specific settings
UVICORN_WORKERS=${UVICORN_WORKERS:-4}
# Create PID directory
mkdir -p "$PID_DIR"
# Functions
print_header() {
echo ""
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE} Tool_OCR Production Server${NC}"
echo -e "${BLUE}================================${NC}"
echo ""
}
show_help() {
echo "Tool_OCR - Production Server Manager"
echo ""
echo "Usage: ./start-prod.sh [command]"
echo ""
echo "Commands:"
echo " (none) Start all services (backend + frontend)"
echo " backend Start only backend service"
echo " frontend Start only frontend service"
echo " --stop Stop all running services"
echo " --status Show status of services"
echo " --help Show this help message"
echo ""
echo "Environment Variables:"
echo " BACKEND_PORT Backend port (default: 8000)"
echo " FRONTEND_PORT Frontend port (default: 12010)"
echo " UVICORN_WORKERS Number of uvicorn workers (default: 4)"
echo ""
echo "Examples:"
echo " ./start-prod.sh # Start everything"
echo " ./start-prod.sh backend # Start only backend"
echo " FRONTEND_PORT=3000 ./start-prod.sh # Custom port"
echo " ./start-prod.sh --stop # Stop all services"
echo ""
}
check_requirements() {
local missing=0
# Check virtual environment
if [ ! -d "$SCRIPT_DIR/venv" ]; then
echo -e "${RED}Error: Python virtual environment not found${NC}"
echo "Please run: ./setup_dev_env.sh"
missing=1
fi
# Check node_modules
if [ ! -d "$SCRIPT_DIR/frontend/node_modules" ]; then
echo -e "${RED}Error: Frontend dependencies not found${NC}"
echo "Please run: ./setup_dev_env.sh"
missing=1
fi
# Check .env (production uses .env, not .env.local)
if [ ! -f "$SCRIPT_DIR/.env" ]; then
echo -e "${YELLOW}Warning: .env not found, using defaults${NC}"
fi
return $missing
}
load_env() {
# Production loads .env only (not .env.local)
# For production, you should use .env with production settings
if [ -f "$SCRIPT_DIR/.env" ]; then
set -a
# shellcheck disable=SC1090
source "$SCRIPT_DIR/.env"
set +a
fi
}
is_running() {
local pid_file=$1
if [ -f "$pid_file" ]; then
local pid=$(cat "$pid_file")
if ps -p "$pid" > /dev/null 2>&1; then
return 0
else
# PID file exists but process is not running, clean up
rm -f "$pid_file"
fi
fi
return 1
}
get_pid() {
local pid_file=$1
if [ -f "$pid_file" ]; then
cat "$pid_file"
fi
}
start_backend() {
if is_running "$BACKEND_PID_FILE"; then
echo -e "${YELLOW}Backend already running (PID: $(get_pid $BACKEND_PID_FILE))${NC}"
return 0
fi
echo -e "${GREEN}Starting backend server (production mode)...${NC}"
# Activate virtual environment
source "$SCRIPT_DIR/venv/bin/activate"
# Load environment variables
load_env
# Start backend in background
cd "$SCRIPT_DIR/backend"
# Create necessary directories
mkdir -p uploads/{temp,processed,images}
mkdir -p storage/{markdown,json,exports,results}
mkdir -p models/paddleocr
mkdir -p logs
# Start uvicorn in production mode (no --reload, with workers)
nohup uvicorn app.main:app \
--host "$BACKEND_HOST" \
--port "$BACKEND_PORT" \
--workers "$UVICORN_WORKERS" \
--access-log \
--log-level info \
> "$PID_DIR/backend.log" 2>&1 &
local pid=$!
echo $pid > "$BACKEND_PID_FILE"
cd "$SCRIPT_DIR"
# Wait a moment and verify
sleep 3
if is_running "$BACKEND_PID_FILE"; then
echo -e "${GREEN}Backend started (PID: $pid, Workers: $UVICORN_WORKERS)${NC}"
echo -e " API Docs: http://localhost:$BACKEND_PORT/docs"
echo -e " Health: http://localhost:$BACKEND_PORT/health"
else
echo -e "${RED}Backend failed to start. Check $PID_DIR/backend.log${NC}"
return 1
fi
}
start_frontend() {
if is_running "$FRONTEND_PID_FILE"; then
echo -e "${YELLOW}Frontend already running (PID: $(get_pid $FRONTEND_PID_FILE))${NC}"
return 0
fi
echo -e "${GREEN}Starting frontend server (production mode)...${NC}"
# Load environment variables
load_env
# Load nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
cd "$SCRIPT_DIR/frontend"
# Build frontend if dist doesn't exist or is older than src
if [ ! -d "dist" ] || [ "$(find src -newer dist -type f 2>/dev/null | head -1)" ]; then
echo -e "${YELLOW}Building frontend...${NC}"
npm run build
fi
# Start production preview server
# Note: For real production, use nginx to serve dist/ folder
nohup npm run preview -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT" > "$PID_DIR/frontend.log" 2>&1 &
local pid=$!
echo $pid > "$FRONTEND_PID_FILE"
cd "$SCRIPT_DIR"
# Wait a moment and verify
sleep 3
if is_running "$FRONTEND_PID_FILE"; then
echo -e "${GREEN}Frontend started (PID: $pid)${NC}"
echo -e " URL: http://localhost:$FRONTEND_PORT"
else
echo -e "${RED}Frontend failed to start. Check $PID_DIR/frontend.log${NC}"
return 1
fi
}
kill_process_tree() {
local pid=$1
# Kill all child processes first
pkill -TERM -P "$pid" 2>/dev/null || true
# Then kill the parent
kill -TERM "$pid" 2>/dev/null || true
}
force_kill_process_tree() {
local pid=$1
# Force kill all child processes
pkill -9 -P "$pid" 2>/dev/null || true
# Force kill the parent
kill -9 "$pid" 2>/dev/null || true
}
kill_by_port() {
local port=$1
local pids=$(lsof -ti :$port 2>/dev/null)
if [ -n "$pids" ]; then
echo "$pids" | xargs kill -TERM 2>/dev/null || true
sleep 1
# Force kill if still running
pids=$(lsof -ti :$port 2>/dev/null)
if [ -n "$pids" ]; then
echo "$pids" | xargs kill -9 2>/dev/null || true
fi
fi
}
stop_service() {
local name=$1
local pid_file=$2
local port=$3
if is_running "$pid_file"; then
local pid=$(get_pid "$pid_file")
echo -e "${YELLOW}Stopping $name (PID: $pid)...${NC}"
# Kill the entire process tree
kill_process_tree "$pid"
# Wait up to 5 seconds
local count=0
while [ $count -lt 5 ] && is_running "$pid_file"; do
sleep 1
count=$((count + 1))
done
# Force kill if still running
if is_running "$pid_file"; then
force_kill_process_tree "$pid"
fi
rm -f "$pid_file"
fi
# Also kill any orphaned processes by port (fallback)
if [ -n "$port" ]; then
local port_pids=$(lsof -ti :$port 2>/dev/null)
if [ -n "$port_pids" ]; then
echo -e "${YELLOW}Cleaning up orphaned processes on port $port...${NC}"
kill_by_port "$port"
fi
fi
echo -e "${GREEN}$name stopped${NC}"
}
stop_all() {
echo -e "${YELLOW}Stopping all production services...${NC}"
stop_service "Backend" "$BACKEND_PID_FILE" "$BACKEND_PORT"
stop_service "Frontend" "$FRONTEND_PID_FILE" "$FRONTEND_PORT"
echo -e "${GREEN}All services stopped${NC}"
}
show_status() {
echo -e "${BLUE}Production Service Status:${NC}"
echo ""
if is_running "$BACKEND_PID_FILE"; then
local pid=$(get_pid "$BACKEND_PID_FILE")
echo -e " Backend: ${GREEN}Running${NC} (PID: $pid, Workers: $UVICORN_WORKERS)"
echo -e " http://localhost:$BACKEND_PORT"
else
echo -e " Backend: ${RED}Stopped${NC}"
fi
if is_running "$FRONTEND_PID_FILE"; then
local pid=$(get_pid "$FRONTEND_PID_FILE")
echo -e " Frontend: ${GREEN}Running${NC} (PID: $pid)"
echo -e " http://localhost:$FRONTEND_PORT"
else
echo -e " Frontend: ${RED}Stopped${NC}"
fi
echo ""
}
# Main
case "${1:-all}" in
--help|-h)
show_help
;;
--stop)
stop_all
;;
--status)
show_status
;;
backend)
print_header
check_requirements || exit 1
start_backend
echo ""
echo -e "${YELLOW}Logs: tail -f $PID_DIR/backend.log${NC}"
;;
frontend)
print_header
check_requirements || exit 1
start_frontend
echo ""
echo -e "${YELLOW}Logs: tail -f $PID_DIR/frontend.log${NC}"
;;
all|"")
print_header
check_requirements || exit 1
start_backend
start_frontend
echo ""
echo -e "${GREEN}================================${NC}"
echo -e "${GREEN}All production services started!${NC}"
echo -e "${GREEN}================================${NC}"
echo ""
echo "Access the application:"
echo -e " Frontend: ${BLUE}http://localhost:$FRONTEND_PORT${NC}"
echo -e " API Docs: ${BLUE}http://localhost:$BACKEND_PORT/docs${NC}"
echo ""
echo -e "${YELLOW}Use ./start-prod.sh --stop to stop all services${NC}"
echo -e "${YELLOW}Use ./start-prod.sh --status to check status${NC}"
echo ""
echo "Logs:"
echo " Backend: tail -f $PID_DIR/backend.log"
echo " Frontend: tail -f $PID_DIR/frontend.log"
echo ""
echo -e "${YELLOW}Note: For real production deployment, consider using:${NC}"
echo " - nginx as reverse proxy"
echo " - systemd for service management"
echo " - Docker for containerization"
;;
*)
echo -e "${RED}Unknown command: $1${NC}"
show_help
exit 1
;;
esac

114
start.sh
View File

@@ -1,9 +1,10 @@
#!/bin/bash
# Tool_OCR - Unified Development Server Startup Script
# Tool_OCR - Unified Server Startup Script
# Usage:
# ./start.sh Start all services (backend + frontend)
# ./start.sh backend Start only backend
# ./start.sh frontend Start only frontend
# ./start.sh --prod Start in production mode (no hot-reload)
# ./start.sh --stop Stop all services
# ./start.sh --status Show service status
# ./start.sh --help Show help
@@ -27,6 +28,10 @@ BACKEND_PORT=${BACKEND_PORT:-8000}
FRONTEND_HOST=${FRONTEND_HOST:-0.0.0.0}
FRONTEND_PORT=${FRONTEND_PORT:-5173}
# Production settings
PROD_MODE=false
UVICORN_WORKERS=${UVICORN_WORKERS:-4}
# Create PID directory
mkdir -p "$PID_DIR"
@@ -34,15 +39,19 @@ mkdir -p "$PID_DIR"
print_header() {
echo ""
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE} Tool_OCR Development Server${NC}"
if [ "$PROD_MODE" = true ]; then
echo -e "${BLUE} Tool_OCR Server (Production)${NC}"
else
echo -e "${BLUE} Tool_OCR Server (Development)${NC}"
fi
echo -e "${BLUE}================================${NC}"
echo ""
}
show_help() {
echo "Tool_OCR - Development Server Manager"
echo "Tool_OCR - Server Manager"
echo ""
echo "Usage: ./start.sh [command]"
echo "Usage: ./start.sh [options] [command]"
echo ""
echo "Commands:"
echo " (none) Start all services (backend + frontend)"
@@ -52,9 +61,18 @@ show_help() {
echo " --status Show status of services"
echo " --help Show this help message"
echo ""
echo "Options:"
echo " --prod Run in production mode (no hot-reload, multiple workers)"
echo ""
echo "Environment Variables:"
echo " BACKEND_PORT Backend port (default: 8000)"
echo " FRONTEND_PORT Frontend port (default: 5173)"
echo " UVICORN_WORKERS Number of workers in prod mode (default: 4)"
echo ""
echo "Examples:"
echo " ./start.sh # Start everything"
echo " ./start.sh backend # Start only backend"
echo " ./start.sh # Start in dev mode"
echo " ./start.sh --prod # Start in production mode"
echo " BACKEND_PORT=8088 ./start.sh # Use custom port"
echo " ./start.sh --stop # Stop all services"
echo ""
}
@@ -76,10 +94,10 @@ check_requirements() {
missing=1
fi
# Check .env.local
if [ ! -f "$SCRIPT_DIR/.env.local" ]; then
echo -e "${RED}Error: .env.local not found${NC}"
echo "Please copy .env.example to .env.local and configure"
# Check .env
if [ ! -f "$SCRIPT_DIR/.env" ]; then
echo -e "${RED}Error: .env not found${NC}"
echo "Please copy .env.example to .env and configure"
missing=1
fi
@@ -87,12 +105,12 @@ check_requirements() {
}
load_env() {
# Load environment variables from root .env.local (if present).
# Load environment variables from root .env (if present).
# This keeps backend/frontend config in sync without hardcoding ports/URLs in scripts.
if [ -f "$SCRIPT_DIR/.env.local" ]; then
if [ -f "$SCRIPT_DIR/.env" ]; then
set -a
# shellcheck disable=SC1090
source "$SCRIPT_DIR/.env.local"
source "$SCRIPT_DIR/.env"
set +a
fi
}
@@ -124,7 +142,11 @@ start_backend() {
return 0
fi
echo -e "${GREEN}Starting backend server...${NC}"
if [ "$PROD_MODE" = true ]; then
echo -e "${GREEN}Starting backend server (production mode)...${NC}"
else
echo -e "${GREEN}Starting backend server (development mode)...${NC}"
fi
# Activate virtual environment
source "$SCRIPT_DIR/venv/bin/activate"
@@ -141,8 +163,18 @@ start_backend() {
mkdir -p models/paddleocr
mkdir -p logs
# Start uvicorn
# Start uvicorn (different modes)
if [ "$PROD_MODE" = true ]; then
nohup uvicorn app.main:app \
--host "$BACKEND_HOST" \
--port "$BACKEND_PORT" \
--workers "$UVICORN_WORKERS" \
--access-log \
--log-level info \
> "$SCRIPT_DIR/.pid/backend.log" 2>&1 &
else
nohup uvicorn app.main:app --reload --host "$BACKEND_HOST" --port "$BACKEND_PORT" > "$SCRIPT_DIR/.pid/backend.log" 2>&1 &
fi
local pid=$!
echo $pid > "$BACKEND_PID_FILE"
@@ -151,7 +183,11 @@ start_backend() {
# Wait a moment and verify
sleep 2
if is_running "$BACKEND_PID_FILE"; then
if [ "$PROD_MODE" = true ]; then
echo -e "${GREEN}Backend started (PID: $pid, Workers: $UVICORN_WORKERS)${NC}"
else
echo -e "${GREEN}Backend started (PID: $pid)${NC}"
fi
echo -e " API Docs: http://localhost:$BACKEND_PORT/docs"
echo -e " Health: http://localhost:$BACKEND_PORT/health"
else
@@ -166,7 +202,11 @@ start_frontend() {
return 0
fi
echo -e "${GREEN}Starting frontend server...${NC}"
if [ "$PROD_MODE" = true ]; then
echo -e "${GREEN}Starting frontend server (production mode)...${NC}"
else
echo -e "${GREEN}Starting frontend server (development mode)...${NC}"
fi
# Load environment variables so Vite config can use FRONTEND_PORT/FRONTEND_HOST/etc.
load_env
@@ -177,8 +217,19 @@ start_frontend() {
cd "$SCRIPT_DIR/frontend"
# Start vite in background
# Start frontend (different modes)
if [ "$PROD_MODE" = true ]; then
# Build if needed
if [ ! -d "dist" ] || [ "$(find src -newer dist -type f 2>/dev/null | head -1)" ]; then
echo -e "${YELLOW}Building frontend...${NC}"
npm run build
fi
# Start production preview server
nohup npm run preview -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT" > "$SCRIPT_DIR/.pid/frontend.log" 2>&1 &
else
# Start vite dev server
nohup npm run dev > "$SCRIPT_DIR/.pid/frontend.log" 2>&1 &
fi
local pid=$!
echo $pid > "$FRONTEND_PID_FILE"
@@ -294,8 +345,31 @@ show_status() {
echo ""
}
# Parse arguments
COMMAND=""
while [[ $# -gt 0 ]]; do
case $1 in
--prod)
PROD_MODE=true
shift
;;
--help|-h|--stop|--status|backend|frontend|all)
COMMAND=$1
shift
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
show_help
exit 1
;;
esac
done
# Default command
COMMAND=${COMMAND:-all}
# Main
case "${1:-all}" in
case "$COMMAND" in
--help|-h)
show_help
;;
@@ -321,7 +395,7 @@ case "${1:-all}" in
echo -e "${YELLOW}Press Ctrl+C to stop (or use ./start.sh --stop)${NC}"
echo -e "${YELLOW}Logs: tail -f .pid/frontend.log${NC}"
;;
all|"")
all)
print_header
check_requirements || exit 1
start_backend
@@ -343,7 +417,7 @@ case "${1:-all}" in
echo " Frontend: tail -f .pid/frontend.log"
;;
*)
echo -e "${RED}Unknown command: $1${NC}"
echo -e "${RED}Unknown command: $COMMAND${NC}"
show_help
exit 1
;;