diff --git a/.devops/monitor.py b/.devops/monitor.py index 0727ee2..28403aa 100644 --- a/.devops/monitor.py +++ b/.devops/monitor.py @@ -11,8 +11,11 @@ import time import yaml import subprocess import socket +import threading +import json from datetime import datetime from pathlib import Path +from flask import Flask, request, jsonify, render_template_string # 添加当前目录到 Python 路径 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -659,6 +662,62 @@ class GitMonitor: at_all=True ) + def deploy_by_name(self, repo_name): + """通过项目名称触发部署 + + 参数: + repo_name: 项目名称 + + 返回: + (success, message): 成功标志和消息 + """ + repos = self.config.get('repositories', []) + + # 查找匹配的仓库配置 + repo_config = None + for repo in repos: + if repo['name'] == repo_name: + repo_config = repo + break + + if not repo_config: + return False, f"未找到项目: {repo_name}" + + Logger.info(f"HTTP触发部署: {repo_name}") + + # 获取最新的commit hash + repo_url = repo_config['url'] + current_commit = self.get_remote_commit(repo_url, self.global_branch) + + if current_commit: + self.last_commits[repo_name] = current_commit + + # 执行部署 + success = self.deploy(repo_config) + + if success: + return True, f"项目 {repo_name} 部署成功" + else: + return False, f"项目 {repo_name} 部署失败" + + def get_all_projects(self): + """获取所有可部署的项目列表 + + 返回: + 项目列表,每个项目包含 name, type, docker_service + """ + repos = self.config.get('repositories', []) + projects = [] + + for repo in repos: + projects.append({ + 'name': repo['name'], + 'type': repo['type'], + 'docker_service': repo['docker_service'] + }) + + return projects + def run(self): """持续监听运行""" poll_interval = self.config['monitor']['poll_interval'] @@ -675,6 +734,365 @@ class GitMonitor: Logger.error(f"监听异常: {e}") +class DeploymentServer: + """HTTP部署服务器""" + + def __init__(self, monitor, port=9999): + """初始化HTTP服务器 + + 参数: + monitor: GitMonitor实例 + port: HTTP服务器端口,默认9999 + """ + self.monitor = monitor + self.port = port + self.app = Flask(__name__) + self._setup_routes() + + def _setup_routes(self): + """设置路由""" + + @self.app.route('/') + def index(): + """根路径 - 显示操作说明""" + projects = self.monitor.get_all_projects() + + html = ''' + + + + + DevOps 部署系统 + + + +

🚀 RuoYi Cloud DevOps 部署系统

+
+

📖 使用说明

+

触发部署:点击下方表格中的"部署链接",或直接访问 http://IP:9999/项目名称

+

示例:http://IP:9999/tuoheng-device 触发 tuoheng-device 项目部署

+

查看日志:点击这里查看部署日志

+
+

📦 可部署项目列表

+ + + + + + + + + + + ''' + + for project in projects: + type_class = f"type-{project['type']}" + html += f''' + + + + + + + ''' + + html += ''' + +
项目名称类型Docker服务部署链接
{project['name']}{project['type'].upper()}{project['docker_service']}/{project['name']}
+ + + ''' + return html + + @self.app.route('/logs') + def view_logs(): + """查看日志页面""" + html = ''' + + + + + DevOps 日志查看 + + + +
+
+

📋 DevOps 部署日志

+ ← 返回首页 +
+
+ + + +
+
+
+
正在加载日志...
+
+ + + + ''' + return html + + @self.app.route('/api/logs') + def get_logs(): + """获取日志内容API""" + try: + log_file = self.monitor.config.get('logging', {}).get('file', '.devops/logs/devops.log') + log_path = Path(log_file) + + if not log_path.exists(): + return jsonify({'logs': [], 'message': '日志文件不存在'}) + + # 读取最后1000行日志 + with open(log_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + # 只返回最后1000行 + recent_lines = lines[-1000:] if len(lines) > 1000 else lines + return jsonify({'logs': [line.rstrip() for line in recent_lines]}) + except Exception as e: + return jsonify({'logs': [], 'error': str(e)}) + + @self.app.route('/') + def deploy_project(project_name): + """触发项目部署""" + # 避免与 /logs 路由冲突 + if project_name == 'logs' or project_name == 'api': + return jsonify({'code': 404, 'message': '路径不存在'}) + + Logger.info(f"收到HTTP部署请求: {project_name}") + + # 在后台线程中执行部署 + def deploy_task(): + success, message = self.monitor.deploy_by_name(project_name) + Logger.info(f"部署结果: {message}") + + thread = threading.Thread(target=deploy_task) + thread.daemon = True + thread.start() + + return jsonify({ + 'code': 200, + 'message': f'已触发 {project_name} 部署,请查看日志了解部署进度', + 'project': project_name + }) + + def run(self): + """启动HTTP服务器""" + Logger.info(f"HTTP部署服务器启动在端口 {self.port}") + self.app.run(host='0.0.0.0', port=self.port, debug=False, use_reloader=False) + + def main(): """主函数""" import argparse @@ -682,6 +1100,8 @@ def main(): parser = argparse.ArgumentParser(description='Git 仓库监听器') parser.add_argument('--config', default='.devops/config.yaml', help='配置文件路径') parser.add_argument('--once', action='store_true', help='只执行一次检查') + parser.add_argument('--http-port', type=int, default=9999, help='HTTP服务器端口(默认9999)') + parser.add_argument('--no-http', action='store_true', help='禁用HTTP服务器') args = parser.parse_args() @@ -690,6 +1110,15 @@ def main(): if args.once: monitor.run_once() else: + # 启动HTTP服务器(在单独的线程中) + if not args.no_http: + server = DeploymentServer(monitor, port=args.http_port) + http_thread = threading.Thread(target=server.run) + http_thread.daemon = True + http_thread.start() + Logger.info(f"✓ HTTP部署服务器已启动: http://0.0.0.0:{args.http_port}") + + # 启动Git监听 monitor.run() diff --git a/.devops/pmstart.sh b/.devops/pmstart.sh index 315fe07..df7458d 100755 --- a/.devops/pmstart.sh +++ b/.devops/pmstart.sh @@ -42,6 +42,10 @@ if ! python3 -c "import yaml" 2>/dev/null; then echo "安装 PyYAML..." pip3 install --user PyYAML fi +if ! python3 -c "import flask" 2>/dev/null; then + echo "安装 Flask..." + pip3 install --user flask +fi echo "✓ Python 依赖检查完成" # 5. 删除已存在的 PM2 进程 diff --git a/.devops/start.sh b/.devops/start.sh index 34668e3..6aa2a15 100755 --- a/.devops/start.sh +++ b/.devops/start.sh @@ -44,12 +44,20 @@ if [ -z "$VIRTUAL_ENV" ]; then echo "警告: 未找到 PyYAML,尝试安装..." pip3 install --user PyYAML || echo "请手动安装: pip3 install --user PyYAML" fi + if ! python3 -c "import flask" 2>/dev/null; then + echo "警告: 未找到 Flask,尝试安装..." + pip3 install --user flask || echo "请手动安装: pip3 install --user flask" + fi else echo "检测到虚拟环境: $VIRTUAL_ENV" if ! python3 -c "import yaml" 2>/dev/null; then echo "安装 PyYAML..." pip install PyYAML fi + if ! python3 -c "import flask" 2>/dev/null; then + echo "安装 Flask..." + pip install flask + fi fi # 进入项目根目录