# -*- coding: utf-8 -*- """ CCA 部署脚本 — 通过 paramiko 上传文件到腾讯云服务器 """ import sys sys.stdout.reconfigure(encoding='utf-8') import os import paramiko from pathlib import Path HOST = '101.42.29.217' PORT = 22 USER = 'root' PASS = 'liutong65' CCA_ROOT = Path(__file__).resolve().parent.parent SRC_DIR = CCA_ROOT / 'src' DEPLOY_DIR = CCA_ROOT / 'deploy' # 服务器目标路径 SERVER_CCA_SRC = '/workspace/military_tech_voice/backend/cca_src' SERVER_FRONTEND = '/workspace/military_tech_voice/frontend' SERVER_WWW = '/var/www/voice' SERVER_BACKEND = '/workspace/military_tech_voice/backend' SERVER_ROUTES = f'{SERVER_BACKEND}/app/routes' # 需要上传的 src 模块 SRC_MODULES = [ 'asr_client.py', 'line_breaker.py', 'ai_line_breaker.py', 'ai_proofreader.py', 'term_normalizer.py', 'hotword_extractor.py', 'srt_writer.py', 'segment_splitter.py', ] def connect(): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(HOST, PORT, USER, PASS) sftp = ssh.open_sftp() return ssh, sftp def run(ssh, cmd): print(f' $ {cmd}') _, stdout, stderr = ssh.exec_command(cmd) out = stdout.read().decode('utf-8', errors='replace').strip() err = stderr.read().decode('utf-8', errors='replace').strip() if out: print(f' {out[:500]}') if err: print(f' [stderr] {err[:500]}') return out def upload(sftp, local_path, remote_path): print(f' ↑ {Path(local_path).name} → {remote_path}') sftp.put(str(local_path), remote_path) def main(): print('=== CCA 部署开始 ===\n') ssh, sftp = connect() print('[1/7] 连接成功\n') # Step 2: 创建 cca_src 目录 + 上传源码 print('[2/7] 上传 CCA 源码模块...') run(ssh, f'mkdir -p {SERVER_CCA_SRC}') for module in SRC_MODULES: local = SRC_DIR / module if local.exists(): upload(sftp, local, f'{SERVER_CCA_SRC}/{module}') else: print(f' ⚠ 跳过(不存在): {module}') print() # Step 3: 上传 cca_route.py 到 app/routes/ print('[3/7] 上传 cca_route.py...') run(ssh, f'mkdir -p {SERVER_ROUTES}') upload(sftp, DEPLOY_DIR / 'cca_route.py', f'{SERVER_ROUTES}/cca.py') # 确保 __init__.py 存在 run(ssh, f'touch {SERVER_ROUTES}/__init__.py') print() # Step 4: 上传 cca.html 到前端目录 print('[4/7] 上传 cca.html...') upload(sftp, DEPLOY_DIR / 'cca.html', f'{SERVER_FRONTEND}/cca.html') upload(sftp, DEPLOY_DIR / 'cca.html', f'{SERVER_WWW}/cca.html') print() # Step 5: 配置 .env(追加 CCA 相关变量) print('[5/7] 配置 .env...') env_path = f'{SERVER_BACKEND}/.env' existing_env = '' try: with sftp.open(env_path, 'r') as f: existing_env = f.read().decode('utf-8', errors='replace') except FileNotFoundError: pass env_additions = [] if 'XFYUN_APP_ID' not in existing_env: env_additions.append('# === CCA 唱词助手凭证 ===') env_additions.append('# 讯飞录音文件转写(账号1-默认)') env_additions.append('XFYUN_APP_ID=4c423e35') env_additions.append('XFYUN_SECRET_KEY=b9e0b97d5dda072c9b4b8fb59e7e3d22') env_additions.append('# 讯飞备用账号2') env_additions.append('# XFYUN_APP_ID=52ae3024') env_additions.append('# XFYUN_SECRET_KEY=d65de0eb282a4339e2b1e14fd119e42e') if 'DEEPSEEK_API_KEY' not in existing_env: env_additions.append('# DeepSeek(校对+折行+热词)') env_additions.append('DEEPSEEK_API_KEY=sk-01a7868a88a04ab494e4f05c1f3f06e2') if env_additions: with sftp.open(env_path, 'a') as f: f.write('\n' + '\n'.join(env_additions) + '\n') print(' .env 已追加 CCA 凭证') else: print(' .env 已有 CCA 凭证,跳过') print() # Step 6: 注册 CCA 蓝图到 Flask main.py print('[6/7] 注册 CCA 蓝图...') main_py_path = f'{SERVER_BACKEND}/app/main.py' with sftp.open(main_py_path, 'r') as f: main_content = f.read().decode('utf-8', errors='replace') if 'cca' not in main_content.lower(): # 找到最后一个 register_blueprint 的位置,在其后追加 lines = main_content.split('\n') insert_idx = -1 for i, line in enumerate(lines): if 'register_blueprint' in line: insert_idx = i if insert_idx >= 0: indent = ' ' # 匹配现有缩进 # 检查现有蓝图注册的缩进 existing_line = lines[insert_idx] indent = existing_line[:len(existing_line) - len(existing_line.lstrip())] cca_lines = [ '', f'{indent}# CCA 唱词助手', f'{indent}from app.routes.cca import bp as cca_bp', f'{indent}app.register_blueprint(cca_bp)', ] for j, cca_line in enumerate(cca_lines): lines.insert(insert_idx + 1 + j, cca_line) new_content = '\n'.join(lines) with sftp.open(main_py_path, 'w') as f: f.write(new_content) print(' 已注册 CCA 蓝图') else: print(' ⚠ 未找到 register_blueprint,请手动注册') else: print(' CCA 蓝图已注册,跳过') print() # Step 7: 安装依赖 + 修改 index.html + 重启 print('[7/7] 安装依赖、修改首页、重启服务...') # 安装 Python 依赖 run(ssh, f'{SERVER_BACKEND}/venv/bin/pip install openai python-docx 2>&1 | tail -5') # 修改 index.html 添加 CCA 入口按钮 index_path = f'{SERVER_FRONTEND}/index.html' with sftp.open(index_path, 'r') as f: index_content = f.read().decode('utf-8', errors='replace') if '唱词助手' not in index_content and 'cca.html' not in index_content: # 在导航栏中找到合适位置插入按钮 # 典型位置:header 或 nav 区域的最后一个链接之后 if ' 或 前插入 for marker in ['', '']: if marker in index_content: btn_html = ' 唱词助手\n' index_content = index_content.replace(marker, btn_html + ' ' + marker) break else: # 备用:在 body 开头加一个浮动按钮 btn_html = '
唱词助手
\n' index_content = index_content.replace('', '\n' + btn_html) with sftp.open(index_path, 'w') as f: f.write(index_content) # 同步到 /var/www/voice/ run(ssh, f'cp {SERVER_FRONTEND}/index.html {SERVER_WWW}/index.html') print(' index.html 已添加唱词助手入口') else: print(' index.html 已有唱词助手入口,跳过') # 增大 Nginx 上传限制(音频文件可能较大) nginx_conf = '/etc/nginx/nginx.conf' with sftp.open(nginx_conf, 'r') as f: nginx_content = f.read().decode('utf-8', errors='replace') if 'client_max_body_size' not in nginx_content: nginx_content = nginx_content.replace( 'http {', 'http {\n client_max_body_size 200m;' ) with sftp.open(nginx_conf, 'w') as f: f.write(nginx_content) run(ssh, 'nginx -t && nginx -s reload') print(' Nginx 已增大上传限制至 200MB') else: print(' Nginx 上传限制已配置') # 重启 Flask 服务 print('\n 重启 Flask...') # 查找并重启 Flask 进程 run(ssh, 'pkill -f "flask run" || pkill -f "gunicorn" || pkill -f "python.*app" || true') # 给进程时间退出 import time time.sleep(2) # 检查服务启动方式 service_out = run(ssh, 'systemctl list-units --type=service | grep -i voice || systemctl list-units --type=service | grep -i flask || true') if 'voice' in service_out.lower() or 'flask' in service_out.lower(): service_name = service_out.split()[0] if service_out else '' if service_name: run(ssh, f'systemctl restart {service_name}') else: # 直接后台启动 run(ssh, f'cd {SERVER_BACKEND} && source venv/bin/activate && nohup python -m flask run --host=0.0.0.0 --port=5000 > /tmp/flask_cca.log 2>&1 &') print() print('=== CCA 部署完成 ===') print(f'访问地址: http://{HOST}/cca.html') print(f'API 地址: http://{HOST}/api/cca/') sftp.close() ssh.close() if __name__ == '__main__': main()