#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ PANJIT Document Translator - 統一啟動入口 適用於 1Panel 環境部署 此腳本會: 1. 啟動 Flask Web 服務 2. 啟動 Celery Worker(翻譯任務處理) 3. 啟動 Celery Beat(定時任務) Author: PANJIT IT Team Created: 2025-10-03 """ import os import sys import signal import subprocess import time from pathlib import Path from multiprocessing import Process # 添加專案根目錄到 Python 路徑 project_root = Path(__file__).parent sys.path.insert(0, str(project_root)) # ANSI 顏色碼 class Colors: BLUE = '\033[94m' GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' # 全域進程列表 processes = [] def log_info(message): """顯示資訊日誌""" print(f"{Colors.BLUE}[INFO]{Colors.ENDC} {message}") def log_success(message): """顯示成功日誌""" print(f"{Colors.GREEN}[SUCCESS]{Colors.ENDC} {message}") def log_warning(message): """顯示警告日誌""" print(f"{Colors.YELLOW}[WARNING]{Colors.ENDC} {message}") def log_error(message): """顯示錯誤日誌""" print(f"{Colors.RED}[ERROR]{Colors.ENDC} {message}") def show_banner(): """顯示啟動橫幅""" print(f""" {Colors.BOLD}╔═══════════════════════════════════════════════════════════╗ ║ PANJIT Document Translator V2 ║ ║ 正在啟動服務... ║ ╚═══════════════════════════════════════════════════════════╝{Colors.ENDC} """) def check_environment(): """檢查環境配置""" log_info("檢查環境配置...") # 檢查 Python 版本 if sys.version_info < (3, 10): log_error(f"Python 版本過低: {sys.version}") log_error("需要 Python 3.10 或更高版本") sys.exit(1) log_success(f"Python 版本: {sys.version.split()[0]} ✓") # 檢查必要檔案 required_files = ['app.py', 'celery_app.py', 'requirements.txt'] for file in required_files: if not Path(file).exists(): log_error(f"找不到必要檔案: {file}") sys.exit(1) log_success("必要檔案檢查完成 ✓") # 檢查環境變數 if not Path('.env').exists(): log_warning("找不到 .env 檔案,將使用預設配置") else: log_success("找到 .env 配置檔案 ✓") # 檢查必要目錄 directories = ['uploads', 'logs', 'static'] for directory in directories: Path(directory).mkdir(parents=True, exist_ok=True) log_success("目錄結構檢查完成 ✓") print() def start_flask_app(): """啟動 Flask Web 服務""" log_info("啟動 Flask Web 服務...") # 從環境變數讀取配置 host = os.environ.get('HOST', '0.0.0.0') port = int(os.environ.get('PORT', 12010)) # 使用 gunicorn 啟動(生產環境) if os.environ.get('FLASK_ENV') == 'production': workers = int(os.environ.get('GUNICORN_WORKERS', 4)) cmd = [ 'gunicorn', '--bind', f'{host}:{port}', '--workers', str(workers), '--timeout', '300', '--access-logfile', 'logs/access.log', '--error-logfile', 'logs/error.log', '--log-level', 'info', 'app:app' # 直接使用 app.py 中的 app 物件 ] else: # 開發環境使用 Flask 內建伺服器 cmd = ['python3', 'app.py'] try: process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) processes.append(('Flask App', process)) log_success(f"Flask 服務已啟動 (PID: {process.pid}) ✓") log_info(f"服務地址: http://{host}:{port}") return process except Exception as e: log_error(f"Flask 服務啟動失敗: {e}") sys.exit(1) def start_celery_worker(): """啟動 Celery Worker""" log_info("啟動 Celery Worker...") # 檢查 Redis 連線 redis_url = os.environ.get('REDIS_URL', 'redis://localhost:6379/0') log_info(f"Redis URL: {redis_url}") # Celery Worker 命令 cmd = [ 'celery', '-A', 'celery_app.celery', 'worker', '--loglevel=info', '--concurrency=2', '--logfile=logs/celery_worker.log' ] try: process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) processes.append(('Celery Worker', process)) log_success(f"Celery Worker 已啟動 (PID: {process.pid}) ✓") return process except Exception as e: log_error(f"Celery Worker 啟動失敗: {e}") log_warning("如果沒有 Redis 服務,翻譯功能將無法使用") return None def start_celery_beat(): """啟動 Celery Beat(定時任務)""" log_info("啟動 Celery Beat...") cmd = [ 'celery', '-A', 'celery_app.celery', 'beat', '--loglevel=info', '--logfile=logs/celery_beat.log' ] try: process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1 ) processes.append(('Celery Beat', process)) log_success(f"Celery Beat 已啟動 (PID: {process.pid}) ✓") return process except Exception as e: log_error(f"Celery Beat 啟動失敗: {e}") log_warning("定時任務將無法執行") return None def signal_handler(signum, frame): """處理終止信號""" print() log_warning("收到終止信號,正在關閉所有服務...") for name, process in processes: if process and process.poll() is None: log_info(f"停止 {name} (PID: {process.pid})...") try: process.terminate() process.wait(timeout=5) log_success(f"{name} 已停止 ✓") except subprocess.TimeoutExpired: log_warning(f"{name} 未響應,強制終止...") process.kill() log_success(f"{name} 已強制終止 ✓") except Exception as e: log_error(f"停止 {name} 時發生錯誤: {e}") log_success("\n所有服務已停止") sys.exit(0) def monitor_processes(): """監控進程狀態""" log_info("開始監控服務狀態...") print() print("=" * 60) print(f"{Colors.BOLD}服務狀態:{Colors.ENDC}") for name, process in processes: if process: status = "運行中 ✓" if process.poll() is None else "已停止 ✗" print(f" • {name:20s} PID: {process.pid:6d} {status}") print("=" * 60) print() log_success("所有服務已啟動完成!") print() log_info("按 Ctrl+C 停止所有服務") print() try: while True: time.sleep(5) # 檢查進程是否異常退出 for name, process in processes: if process and process.poll() is not None: log_error(f"{name} 異常退出 (退出碼: {process.returncode})") log_warning("正在停止其他服務...") signal_handler(signal.SIGTERM, None) sys.exit(1) except KeyboardInterrupt: signal_handler(signal.SIGINT, None) def main(): """主函數""" # 註冊信號處理 signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # 顯示橫幅 show_banner() # 檢查環境 check_environment() # 啟動服務 log_info("正在啟動所有服務...") print() # 1. 啟動 Flask Web 服務 flask_process = start_flask_app() time.sleep(2) # 等待 Flask 啟動 # 2. 啟動 Celery Worker worker_process = start_celery_worker() time.sleep(2) # 等待 Worker 啟動 # 3. 啟動 Celery Beat beat_process = start_celery_beat() time.sleep(2) # 等待 Beat 啟動 print() # 監控進程 monitor_processes() if __name__ == '__main__': try: main() except Exception as e: log_error(f"啟動失敗: {e}") import traceback traceback.print_exc() sys.exit(1)