From 6d222792b2d3588c4286fd980ed3c3ebcce1ef2e Mon Sep 17 00:00:00 2001 From: BBear0115 <2267057785@qq.com> Date: Mon, 13 Apr 2026 00:01:17 +0800 Subject: [PATCH] feat: add comprehensive deployment scripts with SSL certificate handling - Add deploy.sh: main deployment script with automatic SSL certificate fixes - Add scripts/fix_ssl_server.sh: server-side SSL certificate repair - Add scripts/diagnose.sh: diagnostic tool for deployment issues - Add scripts/README.md: detailed documentation and troubleshooting guide - Update CLAUDE.md: document GitHub Potential Users feature and public site Key features: - Automatic Python SSL certificate detection and repair (macOS/Linux) - Detailed error handling for 8 common deployment issues - Pre-flight checks and post-deployment verification - Support for Chinese error messages and fix instructions - Color-coded logging for better readability --- CLAUDE.md | 10 +- deploy.sh | 704 ++++++++++++++++++++++++++++++++++++++ scripts/README.md | 424 +++++++++++++++++++++++ scripts/diagnose.sh | 371 ++++++++++++++++++++ scripts/fix_ssl_server.sh | 113 ++++++ 5 files changed, 1620 insertions(+), 2 deletions(-) create mode 100755 deploy.sh create mode 100644 scripts/README.md create mode 100755 scripts/diagnose.sh create mode 100755 scripts/fix_ssl_server.sh diff --git a/CLAUDE.md b/CLAUDE.md index d12820e..af5e04a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,11 +31,14 @@ OpenCMO is an open-source AI Chief Marketing Officer — a multi-agent system fo - `report_pipeline.py` — Multi-agent deep report pipeline (6 phases): Reflection → Insight Distiller → Outline Planner → Section Writers → Section Grader → Report Synthesizer. Uses `asyncio.Semaphore` to limit concurrent LLM calls (`_MAX_CONCURRENT_LLM_CALLS = 5`) - `llm.py` — Centralized LLM client with per-request key isolation via ContextVar. Solves BYOK concurrency bug. Key resolution: ContextVar → os.environ → DB settings. Includes retry logic with exponential backoff - `background/` — Background task queue system for long-running operations (scans, reports). Tasks have status tracking and progress events +- `agents/github.py` + `tools/github_discovery.py` — GitHub Potential Users (developer outreach): discovers users from seed profiles via social graph, enriches profiles in background (bio, repos, languages, stars), scores by outreach priority, generates personalized messages (email/Twitter/GitHub issue). All outreach queued for approval, never auto-sent +- `services/github_service.py` + `storage/github.py` — GitHub lead management: CRUD for leads, batch scoring, opt-out tracking, enrichment status ### Frontend layers -- `pages/` — Route-level components (Dashboard, SEO, GEO, SERP, Community, Graph, Chat, Approvals, Monitors) -- `components/` — Organized by domain: `charts/` (recharts + react-force-graph-3d), `chat/` (SSE streaming), `monitors/`, `auth/`, `layout/`, `dashboard/`, `project/` +- `pages/` — Route-level components (Dashboard, SEO, GEO, SERP, Community, Graph, Chat, Approvals, Monitors, GitHubLeadsPage). Also includes public marketing pages: `LandingPage.tsx`, `BlogPage.tsx` +- `components/` — Organized by domain: `charts/` (recharts + react-force-graph-3d), `chat/` (SSE streaming), `monitors/`, `auth/`, `layout/`, `dashboard/`, `project/`, `marketing/` (PublicSiteHeader, SectionReveal for landing page) +- `content/marketing.ts` — Centralized content for public site: landing page copy, blog articles, FAQs, navigation items. Supports i18n - `hooks/` — TanStack Query hooks per domain (`useProjects`, `useSeoData`, `useGraphData`, etc.). Stale time 30s, retry 1. `useChat` manages local state + SSE via async generator - `api/client.ts` — `apiFetch()` adds Bearer token, dispatches `opencmo:unauthorized` on 401. Domain modules export typed wrappers around `apiJson()` - `i18n/` — React context-based EN + ZH translations @@ -53,6 +56,8 @@ OpenCMO is an open-source AI Chief Marketing Officer — a multi-agent system fo - **Frontend proxies `/api` to `http://127.0.0.1:8080` in dev (vite.config.ts) - **Report generation optimization**: Data aggregation parallelized with `asyncio.gather()`. LLM concurrency limited to 5 simultaneous calls via `asyncio.Semaphore`. Grader threshold at 3.5/5.0 with max 1 retry to balance quality and speed - **BYOK (Bring Your Own Key)**: Per-request API keys isolated via ContextVar in `llm.py`. Never use `os.environ` for request-scoped keys to avoid concurrency bugs +- **Public marketing site**: Landing page and blog served at root `/` for unauthenticated users. Static fallback HTML for crawlers (SEO). Authenticated app at `/app/*`. Content centralized in `frontend/src/content/marketing.ts` +- **GitHub Potential Users workflow**: 1) Discover from seed username → 2) Background enrichment (async) → 3) Score leads by priority → 4) Generate personalized outreach → 5) Human approval required before sending ## Commands @@ -104,6 +109,7 @@ Key optional variables — see `.env.example` for full list: - `DATAFORSEO_LOGIN/PASSWORD` — SERP tracking - `OPENCMO_AUTO_PUBLISH=1` + Reddit/Twitter credentials — auto-publishing - `OPENCMO_SMTP_*` + `OPENCMO_REPORT_EMAIL` — email reports +- `GITHUB_TOKEN` — GitHub API access for Potential Users feature (discovery, enrichment, outreach) ## Performance Optimization Guidelines diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..2879870 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,704 @@ +#!/bin/bash + +################################################################################ +# OpenCMO Deployment Script +# Handles Python SSL certificates, dependencies, and deployment to BWG server +################################################################################ + +set -e # Exit on error (disabled in sections with custom error handling) + +# Color codes for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Deployment configuration +REMOTE_HOST="97.64.16.217" +REMOTE_PORT="2222" +REMOTE_USER="root" +REMOTE_PATH="/opt/OpenCMO" +SERVICE_NAME="opencmo" + +################################################################################ +# Logging functions +################################################################################ + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_step() { + echo -e "\n${GREEN}==>${NC} $1" +} + +################################################################################ +# Error handling functions +################################################################################ + +handle_ssl_error() { + log_error "SSL 证书验证失败" + echo "可能的原因:" + echo " 1. Python 未安装系统 CA 证书" + echo " 2. certifi 包版本过旧" + echo " 3. 系统时间不正确" + echo "" + echo "尝试修复..." + + # 尝试修复 1: 安装/更新 certifi + log_info "更新 certifi 包..." + if pip install --upgrade certifi pip setuptools 2>/dev/null; then + log_success "certifi 更新成功" + else + log_warning "certifi 更新失败,尝试其他方法" + fi + + # 尝试修复 2: 安装系统 CA 证书到 Python + if [[ "$OSTYPE" == "darwin"* ]]; then + log_info "检测到 macOS,运行 Install Certificates.command..." + PYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2) + CERT_SCRIPT="/Applications/Python ${PYTHON_VERSION}/Install Certificates.command" + if [ -f "$CERT_SCRIPT" ]; then + bash "$CERT_SCRIPT" 2>/dev/null || log_warning "证书安装脚本执行失败" + else + log_warning "未找到 Python 证书安装脚本" + log_info "尝试手动安装证书..." + pip install --upgrade certifi + python3 -c "import certifi; print(certifi.where())" + fi + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + log_info "检测到 Linux,更新系统 CA 证书..." + if command -v update-ca-certificates &> /dev/null; then + sudo update-ca-certificates 2>/dev/null || log_warning "CA 证书更新失败" + fi + fi + + # 尝试修复 3: 设置环境变量 + log_info "配置 SSL 环境变量..." + export REQUESTS_CA_BUNDLE=$(python3 -c "import certifi; print(certifi.where())" 2>/dev/null || echo "") + export SSL_CERT_FILE=$REQUESTS_CA_BUNDLE + + if [ -n "$REQUESTS_CA_BUNDLE" ]; then + log_success "SSL 证书路径: $REQUESTS_CA_BUNDLE" + else + log_error "无法获取 SSL 证书路径" + return 1 + fi +} + +handle_pip_install_error() { + local exit_code=$1 + log_error "依赖安装失败 (退出码: $exit_code)" + + echo "可能的原因:" + echo " 1. 网络连接问题" + echo " 2. PyPI 镜像源不可用" + echo " 3. 依赖冲突" + echo " 4. 磁盘空间不足" + echo "" + + # 检查磁盘空间 + log_info "检查磁盘空间..." + df -h . | tail -1 + + # 尝试使用国内镜像源 + log_info "尝试使用清华大学 PyPI 镜像源..." + if pip install -e ".[all]" -i https://pypi.tuna.tsinghua.edu.cn/simple 2>&1 | tee /tmp/pip_install.log; then + log_success "使用镜像源安装成功" + return 0 + fi + + log_warning "镜像源安装失败,尝试分步安装..." + + # 分步安装核心依赖 + local core_deps=("fastapi" "uvicorn" "aiosqlite" "openai" "anthropic") + for dep in "${core_deps[@]}"; do + log_info "安装 $dep..." + if ! pip install "$dep" 2>/dev/null; then + log_error "无法安装 $dep" + return 1 + fi + done + + log_info "安装项目(跳过可选依赖)..." + pip install -e . 2>&1 | tee -a /tmp/pip_install.log +} + +handle_git_error() { + local exit_code=$1 + log_error "Git 操作失败 (退出码: $exit_code)" + + echo "可能的原因:" + echo " 1. 本地有未提交的更改" + echo " 2. 远程仓库不可达" + echo " 3. 分支冲突" + echo "" + + log_info "检查 Git 状态..." + git status + + echo "" + echo "建议操作:" + echo " 1. 提交或暂存本地更改: git add . && git commit -m 'your message'" + echo " 2. 或者放弃本地更改: git reset --hard HEAD" + echo " 3. 检查远程连接: git remote -v" +} + +handle_ssh_error() { + local exit_code=$1 + log_error "SSH 连接失败 (退出码: $exit_code)" + + echo "可能的原因:" + echo " 1. SSH 密钥未配置" + echo " 2. 服务器不可达" + echo " 3. 端口被防火墙阻止" + echo " 4. 用户权限不足" + echo "" + + log_info "测试 SSH 连接..." + if ssh -p "$REMOTE_PORT" -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "echo 'SSH 连接成功'" 2>&1; then + log_success "SSH 连接正常" + else + log_error "SSH 连接失败" + echo "" + echo "修复步骤:" + echo " 1. 检查 SSH 密钥: ssh-add -l" + echo " 2. 添加密钥: ssh-add ~/.ssh/id_rsa" + echo " 3. 测试连接: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST" + echo " 4. 检查防火墙: telnet $REMOTE_HOST $REMOTE_PORT" + return 1 + fi +} + +handle_frontend_build_error() { + local exit_code=$1 + log_error "前端构建失败 (退出码: $exit_code)" + + echo "可能的原因:" + echo " 1. Node.js 版本不兼容" + echo " 2. 依赖未安装或版本冲突" + echo " 3. 内存不足 (需要 >2GB)" + echo " 4. TypeScript 类型错误" + echo "" + + log_info "检查 Node.js 版本..." + node --version + npm --version + + log_info "检查可用内存..." + if [[ "$OSTYPE" == "darwin"* ]]; then + vm_stat | grep "Pages free" + else + free -h | grep Mem + fi + + echo "" + echo "修复步骤:" + echo " 1. 清理缓存: cd frontend && rm -rf node_modules dist && npm install" + echo " 2. 增加 Node 内存: export NODE_OPTIONS='--max-old-space-size=4096'" + echo " 3. 检查类型错误: npm run type-check" + echo " 4. 跳过类型检查构建: npm run build -- --mode production" +} + +handle_service_error() { + local exit_code=$1 + log_error "服务启动失败 (退出码: $exit_code)" + + echo "可能的原因:" + echo " 1. 端口 8080 已被占用" + echo " 2. 环境变量未配置" + echo " 3. 数据库文件损坏" + echo " 4. 依赖缺失" + echo "" + + log_info "检查服务状态..." + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "systemctl status $SERVICE_NAME" || true + + log_info "检查服务日志..." + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "journalctl -u $SERVICE_NAME -n 50 --no-pager" || true + + echo "" + echo "修复步骤:" + echo " 1. 检查端口占用: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'lsof -i:8080'" + echo " 2. 检查配置文件: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'cat $REMOTE_PATH/.env'" + echo " 3. 手动启动测试: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'cd $REMOTE_PATH && opencmo-web'" + echo " 4. 重置数据库: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'rm ~/.opencmo/data.db'" +} + +################################################################################ +# Pre-flight checks +################################################################################ + +preflight_checks() { + log_step "执行部署前检查" + + local has_error=0 + + # Check if we're in a git repository + if ! git rev-parse --git-dir > /dev/null 2>&1; then + log_error "当前目录不是 Git 仓库" + has_error=1 + else + log_success "Git 仓库检查通过" + fi + + # Check if we're on main branch + local current_branch=$(git branch --show-current) + if [ "$current_branch" != "main" ]; then + log_warning "当前分支是 '$current_branch',不是 'main'" + read -p "是否继续部署? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "部署已取消" + exit 0 + fi + else + log_success "分支检查通过 (main)" + fi + + # Check for uncommitted changes + if ! git diff-index --quiet HEAD --; then + log_warning "存在未提交的更改" + git status --short + read -p "是否继续部署? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "部署已取消" + exit 0 + fi + else + log_success "工作区检查通过 (无未提交更改)" + fi + + # Check Python version + if ! command -v python3 &> /dev/null; then + log_error "未找到 python3" + has_error=1 + else + local python_version=$(python3 --version | cut -d' ' -f2) + log_success "Python 版本: $python_version" + fi + + # Check Node.js version + if ! command -v node &> /dev/null; then + log_error "未找到 node" + has_error=1 + else + local node_version=$(node --version) + log_success "Node.js 版本: $node_version" + fi + + # Check SSH connectivity + log_info "测试 SSH 连接到服务器..." + if ssh -p "$REMOTE_PORT" -o ConnectTimeout=10 "$REMOTE_USER@$REMOTE_HOST" "echo 'SSH OK'" > /dev/null 2>&1; then + log_success "SSH 连接正常" + else + log_error "无法连接到服务器" + handle_ssh_error 1 + has_error=1 + fi + + # Check SSL certificates + log_info "检查 Python SSL 证书..." + if python3 -c "import ssl; import certifi; print(certifi.where())" > /dev/null 2>&1; then + local cert_path=$(python3 -c "import certifi; print(certifi.where())") + log_success "SSL 证书路径: $cert_path" + else + log_warning "SSL 证书检查失败,将在安装时修复" + handle_ssl_error || log_warning "SSL 证书修复失败,继续部署..." + fi + + if [ $has_error -eq 1 ]; then + log_error "部署前检查失败,请修复上述问题后重试" + exit 1 + fi + + log_success "所有部署前检查通过" +} + +################################################################################ +# Fix SSL certificates +################################################################################ + +fix_ssl_certificates() { + log_step "修复 Python SSL 证书" + + set +e # Disable exit on error for this section + + # Update certifi + log_info "更新 certifi 包..." + if pip install --upgrade certifi pip setuptools 2>&1 | tee /tmp/certifi_install.log; then + log_success "certifi 更新成功" + else + log_warning "certifi 更新失败" + cat /tmp/certifi_install.log + handle_ssl_error + fi + + # Platform-specific certificate installation + if [[ "$OSTYPE" == "darwin"* ]]; then + log_info "macOS 系统,安装证书到 Python..." + + # Find Python installation + PYTHON_VERSION=$(python3 --version | cut -d' ' -f2 | cut -d'.' -f1,2) + CERT_SCRIPT="/Applications/Python ${PYTHON_VERSION}/Install Certificates.command" + + if [ -f "$CERT_SCRIPT" ]; then + log_info "运行 Python 证书安装脚本..." + bash "$CERT_SCRIPT" 2>&1 | tee /tmp/cert_install.log || log_warning "证书安装脚本执行失败" + else + log_warning "未找到证书安装脚本: $CERT_SCRIPT" + log_info "使用 certifi 作为证书源..." + fi + + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + log_info "Linux 系统,更新系统证书..." + if command -v update-ca-certificates &> /dev/null; then + sudo update-ca-certificates 2>&1 | tee /tmp/cert_update.log || log_warning "证书更新失败" + fi + fi + + # Set environment variables + log_info "配置 SSL 环境变量..." + CERT_PATH=$(python3 -c "import certifi; print(certifi.where())" 2>/dev/null) + + if [ -n "$CERT_PATH" ] && [ -f "$CERT_PATH" ]; then + export REQUESTS_CA_BUNDLE="$CERT_PATH" + export SSL_CERT_FILE="$CERT_PATH" + export CURL_CA_BUNDLE="$CERT_PATH" + + log_success "SSL 证书配置完成" + log_info "证书路径: $CERT_PATH" + + # Add to shell profile for persistence + if [ -f ~/.zshrc ]; then + if ! grep -q "REQUESTS_CA_BUNDLE" ~/.zshrc; then + echo "" >> ~/.zshrc + echo "# OpenCMO SSL certificates" >> ~/.zshrc + echo "export REQUESTS_CA_BUNDLE=\"$CERT_PATH\"" >> ~/.zshrc + echo "export SSL_CERT_FILE=\"$CERT_PATH\"" >> ~/.zshrc + log_info "已添加环境变量到 ~/.zshrc" + fi + fi + + else + log_error "无法获取有效的证书路径" + return 1 + fi + + # Test SSL connection + log_info "测试 SSL 连接..." + if python3 -c "import urllib.request; urllib.request.urlopen('https://pypi.org')" 2>/dev/null; then + log_success "SSL 连接测试通过" + else + log_warning "SSL 连接测试失败,但继续部署..." + fi + + set -e # Re-enable exit on error +} + +################################################################################ +# Install dependencies +################################################################################ + +install_dependencies() { + log_step "安装 Python 依赖" + + set +e # Disable exit on error + + log_info "安装项目依赖 (包含所有可选依赖)..." + if pip install -e ".[all]" 2>&1 | tee /tmp/pip_install.log; then + log_success "依赖安装成功" + else + local exit_code=$? + log_error "依赖安装失败" + cat /tmp/pip_install.log + handle_pip_install_error $exit_code + + # Check if installation succeeded after retry + if [ $? -ne 0 ]; then + log_error "依赖安装失败,无法继续部署" + exit 1 + fi + fi + + # Initialize crawl4ai + log_info "初始化 crawl4ai..." + if command -v crawl4ai-setup &> /dev/null; then + if crawl4ai-setup 2>&1 | tee /tmp/crawl4ai_setup.log; then + log_success "crawl4ai 初始化成功" + else + log_warning "crawl4ai 初始化失败,但继续部署..." + cat /tmp/crawl4ai_setup.log + fi + else + log_warning "未找到 crawl4ai-setup 命令,跳过初始化" + fi + + set -e # Re-enable exit on error +} + +################################################################################ +# Build frontend +################################################################################ + +build_frontend() { + log_step "构建前端" + + if [ ! -d "frontend" ]; then + log_error "未找到 frontend 目录" + exit 1 + fi + + cd frontend + + set +e # Disable exit on error + + # Install npm dependencies + log_info "安装 npm 依赖..." + if npm install 2>&1 | tee /tmp/npm_install.log; then + log_success "npm 依赖安装成功" + else + log_error "npm 依赖安装失败" + cat /tmp/npm_install.log + cd .. + exit 1 + fi + + # Build frontend + log_info "构建前端 (这可能需要几分钟)..." + + # Increase Node.js memory limit + export NODE_OPTIONS="--max-old-space-size=4096" + + if npm run build 2>&1 | tee /tmp/npm_build.log; then + log_success "前端构建成功" + else + local exit_code=$? + log_error "前端构建失败" + cat /tmp/npm_build.log + handle_frontend_build_error $exit_code + cd .. + exit 1 + fi + + # Verify build output + if [ ! -d "dist" ] || [ -z "$(ls -A dist)" ]; then + log_error "构建输出目录为空" + cd .. + exit 1 + fi + + log_success "前端构建完成,输出目录: frontend/dist" + + cd .. + + set -e # Re-enable exit on error +} + +################################################################################ +# Deploy to server +################################################################################ + +deploy_to_server() { + log_step "部署到服务器" + + set +e # Disable exit on error + + # Push code to git + log_info "推送代码到 Git 仓库..." + if git push origin main 2>&1 | tee /tmp/git_push.log; then + log_success "代码推送成功" + else + local exit_code=$? + log_warning "代码推送失败" + cat /tmp/git_push.log + handle_git_error $exit_code + + read -p "是否继续部署? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + log_info "部署已取消" + exit 0 + fi + fi + + # Deploy backend + log_info "部署后端代码到服务器..." + + local deploy_cmd=" + set -e + cd $REMOTE_PATH || exit 1 + echo '拉取最新代码...' + git pull origin main || exit 1 + echo '安装依赖...' + pip install -e '.[all]' -q || exit 1 + echo '重启服务...' + systemctl restart $SERVICE_NAME || exit 1 + sleep 2 + echo '检查服务状态...' + systemctl is-active $SERVICE_NAME || exit 1 + " + + if ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "$deploy_cmd" 2>&1 | tee /tmp/deploy_backend.log; then + log_success "后端部署成功" + else + local exit_code=$? + log_error "后端部署失败" + cat /tmp/deploy_backend.log + handle_service_error $exit_code + exit 1 + fi + + # Deploy frontend + log_info "部署前端静态文件到服务器..." + + if rsync -avz --delete frontend/dist/ "$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/frontend/dist/" -e "ssh -p $REMOTE_PORT" 2>&1 | tee /tmp/deploy_frontend.log; then + log_success "前端部署成功" + else + log_error "前端部署失败" + cat /tmp/deploy_frontend.log + exit 1 + fi + + set -e # Re-enable exit on error +} + +################################################################################ +# Verify deployment +################################################################################ + +verify_deployment() { + log_step "验证部署" + + set +e # Disable exit on error + + # Check service status + log_info "检查服务状态..." + if ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "systemctl is-active $SERVICE_NAME" > /dev/null 2>&1; then + log_success "服务运行正常" + else + log_error "服务未运行" + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "systemctl status $SERVICE_NAME" + exit 1 + fi + + # Check HTTP endpoint + log_info "检查 HTTP 端点..." + local health_check=$(ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/api/v1/health" 2>/dev/null) + + if [ "$health_check" = "200" ]; then + log_success "健康检查通过 (HTTP 200)" + else + log_warning "健康检查返回: HTTP $health_check" + fi + + # Show recent logs + log_info "最近的服务日志:" + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "journalctl -u $SERVICE_NAME -n 20 --no-pager" + + set -e # Re-enable exit on error + + log_success "部署验证完成" +} + +################################################################################ +# Main deployment flow +################################################################################ + +main() { + echo "================================" + echo "OpenCMO 部署脚本" + echo "================================" + echo "" + + # Parse command line arguments + local skip_checks=0 + local skip_frontend=0 + local skip_backend=0 + + while [[ $# -gt 0 ]]; do + case $1 in + --skip-checks) + skip_checks=1 + shift + ;; + --skip-frontend) + skip_frontend=1 + shift + ;; + --skip-backend) + skip_backend=1 + shift + ;; + --help) + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " --skip-checks 跳过部署前检查" + echo " --skip-frontend 跳过前端构建" + echo " --skip-backend 跳过后端部署" + echo " --help 显示此帮助信息" + exit 0 + ;; + *) + log_error "未知选项: $1" + echo "使用 --help 查看帮助" + exit 1 + ;; + esac + done + + # Run deployment steps + if [ $skip_checks -eq 0 ]; then + preflight_checks + else + log_warning "跳过部署前检查" + fi + + fix_ssl_certificates + + if [ $skip_backend -eq 0 ]; then + install_dependencies + else + log_warning "跳过后端依赖安装" + fi + + if [ $skip_frontend -eq 0 ]; then + build_frontend + else + log_warning "跳过前端构建" + fi + + deploy_to_server + verify_deployment + + echo "" + log_success "=========================================" + log_success "部署完成!" + log_success "=========================================" + echo "" + echo "访问地址: https://aidcmo.com" + echo "" + echo "有用的命令:" + echo " 查看日志: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'journalctl -u $SERVICE_NAME -f'" + echo " 重启服务: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'systemctl restart $SERVICE_NAME'" + echo " 检查状态: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'systemctl status $SERVICE_NAME'" + echo "" +} + +# Run main function +main "$@" diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..04880a9 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,424 @@ +# OpenCMO 部署脚本使用指南 + +本目录包含 OpenCMO 项目的部署和诊断脚本,重点解决 Python SSL 证书问题。 + +## 脚本列表 + +### 1. `deploy.sh` - 主部署脚本 + +完整的自动化部署脚本,包含详细的错误处理和修复流程。 + +**功能:** +- 部署前检查(Git、Python、Node.js、SSH 连接) +- 自动修复 Python SSL 证书问题 +- 安装 Python 依赖 +- 构建前端 +- 部署到 BWG 服务器 +- 部署后验证 + +**使用方法:** + +```bash +# 完整部署 +./deploy.sh + +# 跳过部署前检查 +./deploy.sh --skip-checks + +# 仅部署后端(跳过前端构建) +./deploy.sh --skip-frontend + +# 仅部署前端(跳过后端) +./deploy.sh --skip-backend + +# 查看帮助 +./deploy.sh --help +``` + +**部署流程:** + +1. **部署前检查** + - 验证 Git 仓库状态 + - 检查 Python/Node.js 版本 + - 测试 SSH 连接 + - 检查 SSL 证书配置 + +2. **修复 SSL 证书** + - 更新 certifi 包 + - macOS: 运行 Python 证书安装脚本 + - Linux: 更新系统 CA 证书 + - 设置环境变量(REQUESTS_CA_BUNDLE, SSL_CERT_FILE) + - 测试 SSL 连接 + +3. **安装依赖** + - 安装 Python 包(包含所有可选依赖) + - 初始化 crawl4ai + - 失败时自动尝试国内镜像源 + +4. **构建前端** + - 安装 npm 依赖 + - 构建生产版本(增加 Node.js 内存限制) + - 验证构建输出 + +5. **部署到服务器** + - 推送代码到 Git + - SSH 到服务器拉取最新代码 + - 安装服务器端依赖 + - 重启 systemd 服务 + - rsync 前端静态文件 + +6. **验证部署** + - 检查服务状态 + - HTTP 健康检查 + - 显示最近日志 + +### 2. `scripts/fix_ssl_server.sh` - 服务器端 SSL 修复 + +在 BWG 服务器上运行,修复 Python SSL 证书问题。 + +**使用方法:** + +```bash +# 上传并运行 +scp -P 2222 scripts/fix_ssl_server.sh root@97.64.16.217:/tmp/ +ssh -p 2222 root@97.64.16.217 'bash /tmp/fix_ssl_server.sh' +``` + +**功能:** +- 更新系统 CA 证书 +- 更新 Python certifi 包 +- 配置 systemd 服务环境变量 +- 添加环境变量到 ~/.bashrc +- 测试 SSL 连接 + +### 3. `scripts/diagnose.sh` - 诊断工具 + +快速诊断部署问题,提供详细的系统状态和修复建议。 + +**使用方法:** + +```bash +# 完整诊断 +./scripts/diagnose.sh + +# 仅诊断本地环境 +./scripts/diagnose.sh local + +# 仅诊断远程服务器 +./scripts/diagnose.sh remote + +# 仅诊断网络连接 +./scripts/diagnose.sh network + +# 显示常见问题修复方法 +./scripts/diagnose.sh fixes +``` + +**诊断内容:** + +**本地环境:** +- Git 仓库状态 +- Python 版本和 SSL 证书 +- Node.js/npm 版本 +- 前端构建状态 +- 磁盘空间和内存 + +**远程服务器:** +- SSH 连接状态 +- 服务运行状态 +- 健康检查端点 +- 端口监听状态 +- 代码版本 +- SSL 证书配置 +- 磁盘和内存使用 +- 最近的服务日志 +- Nginx 状态 +- HTTPS 证书过期时间 + +**网络连接:** +- 公网访问测试 +- API 健康检查 +- DNS 解析 +- Ping 测试 + +## 常见问题和解决方案 + +### 1. SSL 证书验证失败 + +**错误信息:** +``` +SSL: CERTIFICATE_VERIFY_FAILED +``` + +**原因:** +- Python 未安装系统 CA 证书 +- certifi 包版本过旧 +- 系统时间不正确 + +**解决方案:** + +本地修复: +```bash +# 方法 1: 运行部署脚本(自动修复) +./deploy.sh + +# 方法 2: 手动修复 +pip install --upgrade certifi pip setuptools + +# macOS 特定 +/Applications/Python\ 3.x/Install\ Certificates.command + +# 设置环境变量 +export REQUESTS_CA_BUNDLE=$(python3 -c "import certifi; print(certifi.where())") +export SSL_CERT_FILE=$REQUESTS_CA_BUNDLE +``` + +服务器修复: +```bash +scp -P 2222 scripts/fix_ssl_server.sh root@97.64.16.217:/tmp/ +ssh -p 2222 root@97.64.16.217 'bash /tmp/fix_ssl_server.sh' +ssh -p 2222 root@97.64.16.217 'systemctl restart opencmo' +``` + +### 2. 依赖安装失败 + +**错误信息:** +``` +ERROR: Could not find a version that satisfies the requirement +``` + +**解决方案:** + +```bash +# 使用国内镜像源 +pip install -e ".[all]" -i https://pypi.tuna.tsinghua.edu.cn/simple + +# 检查磁盘空间 +df -h + +# 清理 pip 缓存 +pip cache purge + +# 分步安装 +pip install fastapi uvicorn aiosqlite openai anthropic +pip install -e . +``` + +### 3. 前端构建内存不足 + +**错误信息:** +``` +FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory +``` + +**解决方案:** + +```bash +# 增加 Node.js 内存限制 +export NODE_OPTIONS='--max-old-space-size=4096' +cd frontend && npm run build + +# 清理后重建 +cd frontend +rm -rf node_modules dist +npm install +npm run build +``` + +### 4. SSH 连接失败 + +**错误信息:** +``` +Permission denied (publickey) +``` + +**解决方案:** + +```bash +# 检查 SSH 密钥 +ssh-add -l + +# 添加密钥 +ssh-add ~/.ssh/id_rsa + +# 测试连接 +ssh -p 2222 root@97.64.16.217 + +# 使用密码登录(如果密钥失败) +ssh -p 2222 -o PreferredAuthentications=password root@97.64.16.217 +``` + +### 5. 服务启动失败 + +**错误信息:** +``` +Job for opencmo.service failed +``` + +**解决方案:** + +```bash +# 查看详细日志 +ssh -p 2222 root@97.64.16.217 'journalctl -u opencmo -n 100 --no-pager' + +# 检查端口占用 +ssh -p 2222 root@97.64.16.217 'lsof -i:8080' + +# 手动启动测试 +ssh -p 2222 root@97.64.16.217 'cd /opt/OpenCMO && opencmo-web' + +# 检查配置文件 +ssh -p 2222 root@97.64.16.217 'cat /opt/OpenCMO/.env' + +# 重置数据库(谨慎使用) +ssh -p 2222 root@97.64.16.217 'cp ~/.opencmo/data.db ~/.opencmo/data.db.backup' +ssh -p 2222 root@97.64.16.217 'rm ~/.opencmo/data.db' +ssh -p 2222 root@97.64.16.217 'systemctl restart opencmo' +``` + +### 6. Git 推送失败 + +**错误信息:** +``` +error: failed to push some refs +``` + +**解决方案:** + +```bash +# 查看状态 +git status + +# 提交本地更改 +git add . +git commit -m "your message" +git push + +# 拉取远程更改 +git pull --rebase origin main +git push + +# 强制推送(谨慎使用) +git push -f origin main +``` + +### 7. Nginx 502 错误 + +**原因:** +- 后端服务未运行 +- 端口配置错误 +- 其他 server block 冲突 + +**解决方案:** + +```bash +# 检查服务状态 +ssh -p 2222 root@97.64.16.217 'systemctl status opencmo' + +# 检查 Nginx 配置 +ssh -p 2222 root@97.64.16.217 'nginx -t' + +# 查看 Nginx 错误日志 +ssh -p 2222 root@97.64.16.217 'tail -50 /var/log/nginx/error.log' + +# 检查冲突的 server block +ssh -p 2222 root@97.64.16.217 'grep -r "server_name.*aidcmo.com" /etc/nginx/conf.d/' + +# 重启 Nginx +ssh -p 2222 root@97.64.16.217 'systemctl restart nginx' +``` + +## 部署检查清单 + +部署前确认: + +- [ ] 本地代码已提交到 Git +- [ ] 所有测试通过 +- [ ] .env 文件配置正确 +- [ ] Python 版本 >= 3.9 +- [ ] Node.js 版本 >= 18 +- [ ] SSH 密钥已配置 +- [ ] 磁盘空间充足(本地 >5GB,服务器 >2GB) + +部署后验证: + +- [ ] 服务状态正常:`systemctl status opencmo` +- [ ] 健康检查通过:`curl http://127.0.0.1:8080/api/v1/health` +- [ ] 公网可访问:`curl https://aidcmo.com` +- [ ] 前端加载正常 +- [ ] 登录功能正常 +- [ ] 查看日志无错误:`journalctl -u opencmo -n 50` + +## 有用的命令 + +```bash +# 查看实时日志 +ssh -p 2222 root@97.64.16.217 'journalctl -u opencmo -f' + +# 重启服务 +ssh -p 2222 root@97.64.16.217 'systemctl restart opencmo' + +# 检查服务状态 +ssh -p 2222 root@97.64.16.217 'systemctl status opencmo' + +# 查看端口占用 +ssh -p 2222 root@97.64.16.217 'lsof -i:8080' + +# 查看磁盘使用 +ssh -p 2222 root@97.64.16.217 'df -h' + +# 查看内存使用 +ssh -p 2222 root@97.64.16.217 'free -h' + +# 备份数据库 +ssh -p 2222 root@97.64.16.217 'cp ~/.opencmo/data.db ~/.opencmo/data.db.$(date +%Y%m%d_%H%M%S)' + +# 查看 Nginx 日志 +ssh -p 2222 root@97.64.16.217 'tail -f /var/log/nginx/access.log' +ssh -p 2222 root@97.64.16.217 'tail -f /var/log/nginx/error.log' + +# 测试 Nginx 配置 +ssh -p 2222 root@97.64.16.217 'nginx -t' + +# 重载 Nginx +ssh -p 2222 root@97.64.16.217 'systemctl reload nginx' +``` + +## 紧急回滚 + +如果部署后出现严重问题: + +```bash +# 1. 回滚代码 +ssh -p 2222 root@97.64.16.217 " + cd /opt/OpenCMO && + git log --oneline -5 && + git reset --hard && + systemctl restart opencmo +" + +# 2. 恢复数据库备份 +ssh -p 2222 root@97.64.16.217 " + ls -lh ~/.opencmo/data.db* && + cp ~/.opencmo/data.db.backup ~/.opencmo/data.db && + systemctl restart opencmo +" + +# 3. 恢复前端 +rsync -avz --delete frontend/dist.backup/ root@97.64.16.217:/opt/OpenCMO/frontend/dist/ -e "ssh -p 2222" +``` + +## 技术支持 + +如果遇到脚本无法解决的问题: + +1. 运行诊断工具:`./scripts/diagnose.sh` +2. 查看详细日志:`ssh -p 2222 root@97.64.16.217 'journalctl -u opencmo -n 200'` +3. 检查 GitHub Issues:https://github.com/anthropics/opencmo/issues +4. 联系开发团队 + +## 脚本维护 + +这些脚本会随着项目演进而更新。如果发现问题或有改进建议,请提交 PR 或 Issue。 + +**最后更新:** 2026-04-12 diff --git a/scripts/diagnose.sh b/scripts/diagnose.sh new file mode 100755 index 0000000..d676f1c --- /dev/null +++ b/scripts/diagnose.sh @@ -0,0 +1,371 @@ +#!/bin/bash + +################################################################################ +# OpenCMO Deployment Diagnostics Script +# Quick health check for deployment issues +################################################################################ + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[✓]${NC} $1" +} + +log_error() { + echo -e "${RED}[✗]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[!]${NC} $1" +} + +log_section() { + echo "" + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN}$1${NC}" + echo -e "${GREEN}========================================${NC}" +} + +REMOTE_HOST="97.64.16.217" +REMOTE_PORT="2222" +REMOTE_USER="root" +SERVICE_NAME="opencmo" + +################################################################################ +# Local diagnostics +################################################################################ + +diagnose_local() { + log_section "本地环境诊断" + + # Git status + echo -n "Git 仓库: " + if git rev-parse --git-dir > /dev/null 2>&1; then + log_success "正常" + echo " 分支: $(git branch --show-current)" + echo " 最新提交: $(git log -1 --oneline)" + + if git diff-index --quiet HEAD --; then + log_success "工作区干净" + else + log_warning "存在未提交的更改" + git status --short | head -10 + fi + else + log_error "不是 Git 仓库" + fi + + # Python + echo -n "Python: " + if command -v python3 &> /dev/null; then + local version=$(python3 --version) + log_success "$version" + else + log_error "未安装" + fi + + # Python SSL certificates + echo -n "Python SSL 证书: " + if python3 -c "import ssl; import certifi; print(certifi.where())" > /dev/null 2>&1; then + local cert_path=$(python3 -c "import certifi; print(certifi.where())") + log_success "$cert_path" + + # Test SSL connection + echo -n "SSL 连接测试: " + if python3 -c "import urllib.request; urllib.request.urlopen('https://pypi.org', timeout=5)" 2>/dev/null; then + log_success "通过" + else + log_error "失败" + echo " 修复: ./deploy.sh 会自动修复此问题" + fi + else + log_error "未配置" + echo " 修复: pip install --upgrade certifi" + fi + + # Node.js + echo -n "Node.js: " + if command -v node &> /dev/null; then + local version=$(node --version) + log_success "$version" + else + log_error "未安装" + fi + + # npm + echo -n "npm: " + if command -v npm &> /dev/null; then + local version=$(npm --version) + log_success "$version" + else + log_error "未安装" + fi + + # Frontend build + if [ -d "frontend/dist" ]; then + echo -n "前端构建: " + local file_count=$(find frontend/dist -type f | wc -l | tr -d ' ') + log_success "存在 ($file_count 个文件)" + else + echo -n "前端构建: " + log_warning "不存在 (需要运行 cd frontend && npm run build)" + fi + + # Disk space + echo -n "磁盘空间: " + local available=$(df -h . | tail -1 | awk '{print $4}') + log_info "$available 可用" + + # Memory + echo -n "可用内存: " + if [[ "$OSTYPE" == "darwin"* ]]; then + local free_mem=$(vm_stat | grep "Pages free" | awk '{print $3}' | sed 's/\.//') + local free_gb=$((free_mem * 4096 / 1024 / 1024 / 1024)) + log_info "${free_gb}GB" + else + local free_mem=$(free -h | grep Mem | awk '{print $4}') + log_info "$free_mem" + fi +} + +################################################################################ +# Remote diagnostics +################################################################################ + +diagnose_remote() { + log_section "远程服务器诊断" + + # SSH connectivity + echo -n "SSH 连接: " + if ssh -p "$REMOTE_PORT" -o ConnectTimeout=5 "$REMOTE_USER@$REMOTE_HOST" "echo 'OK'" > /dev/null 2>&1; then + log_success "正常" + else + log_error "失败" + echo " 检查: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST" + return 1 + fi + + # Server info + log_info "服务器信息:" + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" " + echo ' 操作系统: '$(cat /etc/os-release | grep PRETTY_NAME | cut -d'\"' -f2) + echo ' 内核: '$(uname -r) + echo ' 运行时间: '$(uptime -p) + " 2>/dev/null + + # Service status + echo -n "服务状态: " + if ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "systemctl is-active $SERVICE_NAME" > /dev/null 2>&1; then + log_success "运行中" + else + log_error "未运行" + echo " 查看详情: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'systemctl status $SERVICE_NAME'" + fi + + # Service health + echo -n "健康检查: " + local health_code=$(ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/api/v1/health" 2>/dev/null) + if [ "$health_code" = "200" ]; then + log_success "HTTP $health_code" + else + log_error "HTTP $health_code" + fi + + # Port listening + echo -n "端口 8080: " + if ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "lsof -i:8080" > /dev/null 2>&1; then + log_success "监听中" + else + log_error "未监听" + fi + + # Code version + echo -n "代码版本: " + local remote_commit=$(ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "cd /opt/OpenCMO && git log -1 --oneline" 2>/dev/null) + if [ -n "$remote_commit" ]; then + log_info "$remote_commit" + else + log_error "无法获取" + fi + + # Python SSL on server + echo -n "服务器 SSL 证书: " + local server_cert=$(ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "python3 -c 'import certifi; print(certifi.where())'" 2>/dev/null) + if [ -n "$server_cert" ]; then + log_success "$server_cert" + else + log_error "未配置" + echo " 修复: scp -P $REMOTE_PORT scripts/fix_ssl_server.sh $REMOTE_USER@$REMOTE_HOST:/tmp/ && ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'bash /tmp/fix_ssl_server.sh'" + fi + + # Disk space on server + echo -n "服务器磁盘: " + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "df -h / | tail -1 | awk '{print \"使用 \" \$3 \" / \" \$2 \" (\" \$5 \")\"}'" 2>/dev/null + + # Memory on server + echo -n "服务器内存: " + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "free -h | grep Mem | awk '{print \"使用 \" \$3 \" / \" \$2}'" 2>/dev/null + + # Recent logs + log_info "最近的服务日志 (最后 10 行):" + ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "journalctl -u $SERVICE_NAME -n 10 --no-pager" 2>/dev/null | sed 's/^/ /' + + # Nginx status + echo -n "Nginx 状态: " + if ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "systemctl is-active nginx" > /dev/null 2>&1; then + log_success "运行中" + else + log_error "未运行" + fi + + # SSL certificate expiry + echo -n "HTTPS 证书: " + local cert_expiry=$(ssh -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "echo | openssl s_client -servername aidcmo.com -connect aidcmo.com:443 2>/dev/null | openssl x509 -noout -dates | grep notAfter | cut -d= -f2" 2>/dev/null) + if [ -n "$cert_expiry" ]; then + log_info "过期时间: $cert_expiry" + else + log_warning "无法获取" + fi +} + +################################################################################ +# Network diagnostics +################################################################################ + +diagnose_network() { + log_section "网络诊断" + + # Public site accessibility + echo -n "公网访问 (https://aidcmo.com): " + local http_code=$(curl -s -o /dev/null -w '%{http_code}' -m 10 https://aidcmo.com 2>/dev/null) + if [ "$http_code" = "200" ]; then + log_success "HTTP $http_code" + else + log_error "HTTP $http_code" + fi + + # API health endpoint + echo -n "API 健康检查: " + local api_code=$(curl -s -o /dev/null -w '%{http_code}' -m 10 https://aidcmo.com/api/v1/health 2>/dev/null) + if [ "$api_code" = "200" ]; then + log_success "HTTP $api_code" + else + log_error "HTTP $api_code" + fi + + # DNS resolution + echo -n "DNS 解析: " + local resolved_ip=$(dig +short aidcmo.com | tail -1) + if [ "$resolved_ip" = "$REMOTE_HOST" ]; then + log_success "$resolved_ip" + else + log_warning "解析为 $resolved_ip (期望 $REMOTE_HOST)" + fi + + # Ping test + echo -n "Ping 测试: " + if ping -c 1 -W 2 "$REMOTE_HOST" > /dev/null 2>&1; then + log_success "可达" + else + log_error "不可达" + fi +} + +################################################################################ +# Common issues and fixes +################################################################################ + +show_common_fixes() { + log_section "常见问题修复" + + echo "1. SSL 证书问题" + echo " 本地修复: ./deploy.sh 会自动修复" + echo " 服务器修复: scp -P $REMOTE_PORT scripts/fix_ssl_server.sh $REMOTE_USER@$REMOTE_HOST:/tmp/ && ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'bash /tmp/fix_ssl_server.sh'" + echo "" + + echo "2. 服务未运行" + echo " 重启服务: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'systemctl restart $SERVICE_NAME'" + echo " 查看日志: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'journalctl -u $SERVICE_NAME -f'" + echo "" + + echo "3. 前端构建失败" + echo " 清理重建: cd frontend && rm -rf node_modules dist && npm install && npm run build" + echo " 增加内存: export NODE_OPTIONS='--max-old-space-size=4096' && npm run build" + echo "" + + echo "4. 端口被占用" + echo " 查看占用: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'lsof -i:8080'" + echo " 杀死进程: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'kill -9 \$(lsof -t -i:8080)'" + echo "" + + echo "5. Git 推送失败" + echo " 查看状态: git status" + echo " 提交更改: git add . && git commit -m 'your message' && git push" + echo " 强制推送: git push -f origin main (谨慎使用)" + echo "" + + echo "6. 数据库问题" + echo " 备份数据库: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'cp ~/.opencmo/data.db ~/.opencmo/data.db.backup'" + echo " 重置数据库: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'rm ~/.opencmo/data.db && systemctl restart $SERVICE_NAME'" + echo "" + + echo "7. Nginx 配置问题" + echo " 测试配置: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'nginx -t'" + echo " 重载配置: ssh -p $REMOTE_PORT $REMOTE_USER@$REMOTE_HOST 'systemctl reload nginx'" + echo "" +} + +################################################################################ +# Main +################################################################################ + +main() { + echo "================================" + echo "OpenCMO 部署诊断工具" + echo "================================" + + case "${1:-all}" in + local) + diagnose_local + ;; + remote) + diagnose_remote + ;; + network) + diagnose_network + ;; + fixes) + show_common_fixes + ;; + all) + diagnose_local + diagnose_remote + diagnose_network + show_common_fixes + ;; + *) + echo "用法: $0 [local|remote|network|fixes|all]" + echo "" + echo "选项:" + echo " local - 仅诊断本地环境" + echo " remote - 仅诊断远程服务器" + echo " network - 仅诊断网络连接" + echo " fixes - 显示常见问题修复方法" + echo " all - 完整诊断 (默认)" + exit 1 + ;; + esac + + echo "" + log_success "诊断完成" +} + +main "$@" diff --git a/scripts/fix_ssl_server.sh b/scripts/fix_ssl_server.sh new file mode 100755 index 0000000..b43b538 --- /dev/null +++ b/scripts/fix_ssl_server.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +################################################################################ +# Server-side SSL Certificate Fix Script +# Run this on the BWG server to fix Python SSL certificate issues +################################################################################ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +log_step() { + echo -e "\n${GREEN}==>${NC} $1" +} + +log_step "修复服务器 Python SSL 证书" + +# Update system CA certificates +log_info "更新系统 CA 证书..." +if command -v update-ca-certificates &> /dev/null; then + update-ca-certificates 2>&1 || log_error "CA 证书更新失败" + log_success "系统 CA 证书已更新" +fi + +# Update certifi package +log_info "更新 Python certifi 包..." +pip install --upgrade certifi pip setuptools 2>&1 || log_error "certifi 更新失败" +log_success "certifi 已更新" + +# Get certificate path +CERT_PATH=$(python3 -c "import certifi; print(certifi.where())" 2>/dev/null) + +if [ -n "$CERT_PATH" ] && [ -f "$CERT_PATH" ]; then + log_success "证书路径: $CERT_PATH" + + # Set environment variables in systemd service + log_info "配置 systemd 服务环境变量..." + + SERVICE_FILE="/etc/systemd/system/opencmo.service" + + if [ -f "$SERVICE_FILE" ]; then + # Backup original service file + cp "$SERVICE_FILE" "${SERVICE_FILE}.backup" + + # Check if Environment variables already exist + if grep -q "REQUESTS_CA_BUNDLE" "$SERVICE_FILE"; then + log_info "环境变量已存在,更新中..." + sed -i "s|Environment=\"REQUESTS_CA_BUNDLE=.*\"|Environment=\"REQUESTS_CA_BUNDLE=$CERT_PATH\"|g" "$SERVICE_FILE" + sed -i "s|Environment=\"SSL_CERT_FILE=.*\"|Environment=\"SSL_CERT_FILE=$CERT_PATH\"|g" "$SERVICE_FILE" + else + log_info "添加环境变量到服务文件..." + # Add Environment variables after [Service] section + sed -i "/\[Service\]/a Environment=\"REQUESTS_CA_BUNDLE=$CERT_PATH\"\nEnvironment=\"SSL_CERT_FILE=$CERT_PATH\"" "$SERVICE_FILE" + fi + + log_success "服务文件已更新" + + # Reload systemd + log_info "重载 systemd 配置..." + systemctl daemon-reload + log_success "systemd 配置已重载" + + else + log_error "未找到服务文件: $SERVICE_FILE" + fi + + # Add to .bashrc for interactive sessions + if [ -f ~/.bashrc ]; then + if ! grep -q "REQUESTS_CA_BUNDLE" ~/.bashrc; then + echo "" >> ~/.bashrc + echo "# Python SSL certificates" >> ~/.bashrc + echo "export REQUESTS_CA_BUNDLE=\"$CERT_PATH\"" >> ~/.bashrc + echo "export SSL_CERT_FILE=\"$CERT_PATH\"" >> ~/.bashrc + log_success "环境变量已添加到 ~/.bashrc" + fi + fi + +else + log_error "无法获取证书路径" + exit 1 +fi + +# Test SSL connection +log_info "测试 SSL 连接..." +if python3 -c "import urllib.request; urllib.request.urlopen('https://pypi.org', timeout=10)" 2>/dev/null; then + log_success "SSL 连接测试通过" +else + log_error "SSL 连接测试失败" + exit 1 +fi + +log_success "SSL 证书修复完成" +echo "" +echo "下一步:" +echo " 1. 重启服务: systemctl restart opencmo" +echo " 2. 检查状态: systemctl status opencmo" +echo " 3. 查看日志: journalctl -u opencmo -f"