diff --git a/.devops/README.md b/.devops/README.md new file mode 100644 index 0000000..542d821 --- /dev/null +++ b/.devops/README.md @@ -0,0 +1,209 @@ +# RuoYi-Cloud DevOps 自动化部署系统 + +## 简介 + +这是一个基于 Python 的 Git 仓库监听和自动化部署系统,用于监听多个 Git 子仓库的提交,并自动触发构建和部署流程。 + +## 功能特性 + +- ✅ 监听多个 Git 仓库的指定分支 +- ✅ 检测到新提交自动触发部署 +- ✅ 支持 Java 和 Node.js 项目构建 +- ✅ 自动复制构建产物到 Docker 目录 +- ✅ 自动执行 Docker Compose 部署 +- ✅ 自动提交子模块更新到主仓库 +- ✅ 完整的日志记录 + +## 目录结构 + +``` +.devops/ +├── config.yaml # 配置文件 +├── monitor.py # Git 监听器 +├── deployer.py # 部署执行器 +├── start.sh # 启动脚本 +├── scripts/ # 部署脚本目录 +│ ├── deploy-java.sh # Java 服务部署脚本 +│ ├── deploy-ui.sh # UI 部署脚本 +│ └── deploy-common.sh # 通用函数库 +└── logs/ # 日志目录 +``` + +## 环境要求 + +- Python 3.6+ +- Git +- Docker & Docker Compose +- Maven (Java 项目) +- Node.js & npm (前端项目) + +## 安装依赖 + +```bash +# 安装 Python 依赖 +pip3 install PyYAML +``` + +## 配置说明 + +编辑 `.devops/config.yaml` 文件: + +### 1. 仓库配置 + +每个仓库需要配置以下信息: + +```yaml +repositories: + - name: ruoyi-auth # 仓库名称 + url: http://... # Git 仓库 URL + branch: main # 监听的分支 + path: ruoyi-auth # 在主仓库中的路径 + type: java # 项目类型 (java/nodejs) + deploy_script: deploy-java.sh # 部署脚本 + build_commands: # 构建命令列表 + - mvn clean package -DskipTests + artifact_path: target/*.jar # 构建产物路径 + docker_path: docker/ruoyi/auth/jar # Docker 目录 + docker_service: ruoyi-auth # Docker Compose 服务名 +``` + +### 2. 监听配置 + +```yaml +monitor: + poll_interval: 60 # 轮询间隔(秒) + enabled_repos: [] # 监听的仓库列表(空=全部) +``` + +### 3. 部署配置 + +```yaml +deploy: + docker_compose_path: ./docker/docker-compose.yml + auto_commit: true # 是否自动提交子模块更新 + commit_message: "自动更新子模块: {repo_name} 到最新版本" +``` + +## 使用方法 + +### 1. 启动持续监听 + +```bash +# 使用启动脚本 +bash .devops/start.sh + +# 或直接运行 Python +python3 .devops/monitor.py +``` + +### 2. 执行一次检查 + +```bash +python3 .devops/monitor.py --once +``` + +### 3. 指定配置文件 + +```bash +python3 .devops/monitor.py --config /path/to/config.yaml +``` + +## 工作流程 + +1. **监听器检测到子仓库有新提交** +2. **进入 runtime 目录** +3. **克隆/更新主仓库**(如果不存在) +4. **初始化所有子模块** +5. **进入特定子模块目录** +6. **拉取最新代码** +7. **执行构建命令**(mvn/npm) +8. **复制构建产物到 docker 目录** +9. **执行 docker-compose 部署** +10. **回到主仓库,提交子模块更新** +11. **推送到远程主仓库** + +## 日志 + +日志文件位置:`.devops/logs/devops.log` + +日志级别可在配置文件中设置: +- DEBUG:详细调试信息 +- INFO:一般信息(默认) +- WARNING:警告信息 +- ERROR:错误信息 + +## 部署脚本说明 + +### deploy-java.sh + +Java 服务部署脚本,执行以下操作: +1. 重新构建 Docker 镜像 +2. 启动 Docker 服务 +3. 检查服务状态 + +### deploy-ui.sh + +前端 UI 部署脚本,执行以下操作: +1. 重启 Nginx 服务 +2. 检查服务状态 + +### deploy-common.sh + +通用函数库,提供: +- 日志输出函数 +- 命令检查函数 +- Docker 检查函数 +- 健康检查函数 + +## 故障排查 + +### 1. 监听器无法启动 + +检查 Python 依赖是否安装: +```bash +pip3 list | grep PyYAML +``` + +### 2. 构建失败 + +查看日志文件: +```bash +tail -f .devops/logs/devops.log +``` + +### 3. Docker 部署失败 + +检查 Docker 服务是否运行: +```bash +docker info +docker-compose ps +``` + +### 4. Git 操作失败 + +检查 Git 配置和权限: +```bash +git config --list +ssh -T git@your-git-server +``` + +## 注意事项 + +1. 确保有足够的磁盘空间用于 runtime 目录 +2. 首次运行会克隆主仓库,可能需要较长时间 +3. 构建过程可能需要较长时间,请耐心等待 +4. 建议在测试环境先验证配置正确性 +5. 定期清理 runtime 目录和日志文件 + +## 扩展功能 + +可以根据需要添加: +- 钉钉/企业微信通知 +- Web 管理界面 +- 部署回滚功能 +- 健康检查和监控 +- 多环境支持 + +## 许可证 + +本项目遵循 MIT 许可证 diff --git a/.devops/config.yaml b/.devops/config.yaml new file mode 100644 index 0000000..1f1f921 --- /dev/null +++ b/.devops/config.yaml @@ -0,0 +1,210 @@ +# DevOps 自动化部署配置文件 + +# Git 仓库配置 +repositories: + # 认证服务 + - name: ruoyi-auth + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-auth.git + branch: main + path: ruoyi-auth + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/auth/jar + docker_service: ruoyi-auth + + # 网关服务 + - name: ruoyi-gateway + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-gateway.git + branch: main + path: ruoyi-gateway + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/gateway/jar + docker_service: ruoyi-gateway + + # 前端UI + - name: ruoyi-ui + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-ui.git + branch: main + path: ruoyi-ui + type: nodejs + deploy_script: deploy-ui.sh + build_commands: + - npm install + - npm run build:prod + artifact_path: dist + docker_path: docker/nginx/html/dist + docker_service: ruoyi-nginx + + # 系统服务 + - name: ruoyi-system + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-system.git + branch: main + path: ruoyi-modules/ruoyi-system + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/system/jar + docker_service: ruoyi-modules-system + + # 文件服务 + - name: ruoyi-file + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-file.git + branch: main + path: ruoyi-modules/ruoyi-file + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/file/jar + docker_service: ruoyi-modules-file + + # 代码生成 + - name: ruoyi-gen + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-gen.git + branch: main + path: ruoyi-modules/ruoyi-gen + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/gen/jar + docker_service: ruoyi-modules-gen + + # 定时任务 + - name: ruoyi-job + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-job.git + branch: main + path: ruoyi-modules/ruoyi-job + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/job/jar + docker_service: ruoyi-modules-job + + # 监控服务 + - name: ruoyi-monitor + url: http://th.local.t-aaron.com:13000/THENG/a-ruoyi-visual.git + branch: main + path: ruoyi-visual/ruoyi-monitor + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/visual/monitor/jar + docker_service: ruoyi-visual-monitor + + # 设备服务 + - name: tuoheng-device + url: http://th.local.t-aaron.com:13000/THENG/a-tuoheng-device.git + branch: main + path: ruoyi-modules/tuoheng-device + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/device/jar + docker_service: tuoheng-modules-device + + # 审批服务 + - name: tuoheng-approval + url: http://th.local.t-aaron.com:13000/THENG/a-tuoheng-approval.git + branch: main + path: ruoyi-modules/tuoheng-approval + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/approval/jar + docker_service: tuoheng-modules-approval + + # 航线服务 + - name: tuoheng-airline + url: http://th.local.t-aaron.com:13000/THENG/a-tuoheng-airline.git + branch: main + path: ruoyi-modules/tuoheng-airline + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/airline/jar + docker_service: tuoheng-modules-airline + + # 任务服务 + - name: tuoheng-task + url: http://th.local.t-aaron.com:13000/THENG/a-tuoheng-task.git + branch: main + path: ruoyi-modules/tuoheng-task + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/task/jar + docker_service: tuoheng-modules-task + + # FMS服务 + - name: tuoheng-fms + url: http://th.local.t-aaron.com:13000/THENG/a-tuoheng-fms.git + branch: main + path: ruoyi-modules/tuoheng-fms + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/fms/jar + docker_service: tuoheng-modules-fms + + # 媒体服务 + - name: tuoheng-media + url: http://th.local.t-aaron.com:13000/THENG/a-tuoheng-media.git + branch: main + path: ruoyi-modules/tuoheng-media + type: java + deploy_script: deploy-java.sh + build_commands: + - mvn clean package -DskipTests + artifact_path: target/*.jar + docker_path: docker/ruoyi/modules/media/jar + docker_service: tuoheng-modules-media + +# 主仓库配置 +main_repository: + url: http://th.local.t-aaron.com:13000/THENG/a-cloud-all.git + branch: main + runtime_path: ./runtime + +# 监听配置 +monitor: + poll_interval: 60 # 轮询间隔(秒) + enabled_repos: [] # 空数组表示监听所有仓库,或指定具体仓库名称列表 + +# 部署配置 +deploy: + docker_compose_path: ./docker/docker-compose.yml + auto_commit: true # 是否自动提交子模块更新到主仓库 + commit_message: "自动更新子模块: {repo_name} 到最新版本" + +# 日志配置 +logging: + level: INFO # DEBUG, INFO, WARNING, ERROR + file: .devops/logs/devops.log + max_size: 10485760 # 10MB + backup_count: 5 diff --git a/.devops/deployer.py b/.devops/deployer.py new file mode 100644 index 0000000..85cf0c8 --- /dev/null +++ b/.devops/deployer.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +部署执行器 +负责执行具体的部署任务 +""" + +import os +import sys +import logging +import subprocess +import shutil +import glob +from pathlib import Path + + +class Deployer: + """部署执行器""" + + def __init__(self, config): + """初始化部署器""" + self.config = config + self.logger = logging.getLogger('Deployer') + self.runtime_path = Path(config['main_repository']['runtime_path']) + self.main_repo_url = config['main_repository']['url'] + self.main_repo_branch = config['main_repository']['branch'] + + def run_command(self, cmd, cwd=None, timeout=600): + """执行命令""" + self.logger.info(f"执行命令: {cmd}") + try: + result = subprocess.run( + cmd, + shell=True, + cwd=cwd, + capture_output=True, + text=True, + timeout=timeout + ) + + if result.stdout: + self.logger.debug(f"输出: {result.stdout}") + + if result.returncode != 0: + self.logger.error(f"命令执行失败: {result.stderr}") + return False + + return True + except subprocess.TimeoutExpired: + self.logger.error(f"命令执行超时: {cmd}") + return False + except Exception as e: + self.logger.error(f"命令执行异常: {e}") + return False + + def ensure_main_repo(self): + """确保主仓库存在并是最新的""" + repo_path = self.runtime_path / 'a-cloud-all' + + if not repo_path.exists(): + self.logger.info("主仓库不存在,开始克隆...") + self.runtime_path.mkdir(parents=True, exist_ok=True) + + cmd = f"git clone --recurse-submodules {self.main_repo_url} {repo_path}" + if not self.run_command(cmd, cwd=self.runtime_path): + self.logger.error("克隆主仓库失败") + return False + + self.logger.info("主仓库克隆成功") + else: + self.logger.info("主仓库已存在,更新代码...") + + # 切换到指定分支 + if not self.run_command(f"git checkout {self.main_repo_branch}", cwd=repo_path): + return False + + # 拉取最新代码 + if not self.run_command("git pull", cwd=repo_path): + return False + + # 更新所有子模块 + if not self.run_command("git submodule update --init --recursive", cwd=repo_path): + return False + + self.logger.info("主仓库更新成功") + + return True + + def update_submodule(self, repo_config): + """更新指定的子模块""" + repo_path = self.runtime_path / 'a-cloud-all' + submodule_path = repo_path / repo_config['path'] + + self.logger.info(f"更新子模块: {repo_config['name']}") + + # 进入子模块目录 + if not submodule_path.exists(): + self.logger.error(f"子模块目录不存在: {submodule_path}") + return False + + # 切换到指定分支 + branch = repo_config['branch'] + if not self.run_command(f"git checkout {branch}", cwd=submodule_path): + return False + + # 拉取最新代码 + if not self.run_command("git pull origin " + branch, cwd=submodule_path): + return False + + self.logger.info(f"子模块更新成功: {repo_config['name']}") + return True + + def build_project(self, repo_config): + """构建项目""" + repo_path = self.runtime_path / 'a-cloud-all' + submodule_path = repo_path / repo_config['path'] + + self.logger.info(f"开始构建: {repo_config['name']}") + + # 执行构建命令 + for cmd in repo_config['build_commands']: + self.logger.info(f"执行构建命令: {cmd}") + if not self.run_command(cmd, cwd=submodule_path, timeout=1800): + self.logger.error(f"构建失败: {cmd}") + return False + + self.logger.info(f"构建成功: {repo_config['name']}") + return True + + def copy_artifacts(self, repo_config): + """复制构建产物到 docker 目录""" + repo_path = self.runtime_path / 'a-cloud-all' + submodule_path = repo_path / repo_config['path'] + + self.logger.info(f"复制构建产物: {repo_config['name']}") + + # 获取构建产物路径 + artifact_pattern = submodule_path / repo_config['artifact_path'] + artifacts = glob.glob(str(artifact_pattern)) + + if not artifacts: + self.logger.error(f"未找到构建产物: {artifact_pattern}") + return False + + # 目标目录 + docker_path = repo_path / repo_config['docker_path'] + docker_path.mkdir(parents=True, exist_ok=True) + + # 复制文件 + for artifact in artifacts: + artifact_path = Path(artifact) + if artifact_path.is_file(): + dest = docker_path / artifact_path.name + shutil.copy2(artifact, dest) + self.logger.info(f"复制文件: {artifact_path.name}") + elif artifact_path.is_dir(): + # 如果是目录(如 dist),清空目标目录后复制 + if docker_path.exists(): + for item in docker_path.iterdir(): + if item.name != '.gitkeep': + if item.is_dir(): + shutil.rmtree(item) + else: + item.unlink() + shutil.copytree(artifact, docker_path, dirs_exist_ok=True) + self.logger.info(f"复制目录: {artifact_path.name}") + + self.logger.info("构建产物复制完成") + return True + + def run_deploy_script(self, repo_config): + """执行部署脚本""" + repo_path = self.runtime_path / 'a-cloud-all' + script_name = repo_config['deploy_script'] + script_path = repo_path / '.devops' / 'scripts' / script_name + + if not script_path.exists(): + self.logger.error(f"部署脚本不存在: {script_path}") + return False + + self.logger.info(f"执行部署脚本: {script_name}") + + # 准备脚本参数 + docker_service = repo_config.get('docker_service', '') + docker_compose_path = self.config['deploy']['docker_compose_path'] + + # 执行脚本 + cmd = f"bash {script_path} {repo_config['name']} {docker_service} {docker_compose_path}" + + if not self.run_command(cmd, cwd=repo_path, timeout=600): + self.logger.error("部署脚本执行失败") + return False + + self.logger.info("部署脚本执行成功") + return True + + def commit_submodule_update(self, repo_config): + """提交子模块更新到主仓库""" + if not self.config['deploy'].get('auto_commit', False): + self.logger.info("自动提交已禁用,跳过") + return True + + repo_path = self.runtime_path / 'a-cloud-all' + + self.logger.info("提交子模块更新到主仓库") + + # 添加子模块更改 + submodule_path = repo_config['path'] + if not self.run_command(f"git add {submodule_path}", cwd=repo_path): + return False + + # 检查是否有更改 + result = subprocess.run( + "git diff --cached --quiet", + shell=True, + cwd=repo_path + ) + + if result.returncode == 0: + self.logger.info("没有需要提交的更改") + return True + + # 提交更改 + commit_msg = self.config['deploy']['commit_message'].format( + repo_name=repo_config['name'] + ) + + if not self.run_command(f'git commit -m "{commit_msg}"', cwd=repo_path): + return False + + # 推送到远程 + if not self.run_command(f"git push origin {self.main_repo_branch}", cwd=repo_path): + self.logger.warning("推送失败,但部署已完成") + return True + + self.logger.info("子模块更新已提交并推送") + return True + + def deploy(self, repo_config): + """执行完整的部署流程""" + self.logger.info(f"=" * 60) + self.logger.info(f"开始部署: {repo_config['name']}") + self.logger.info(f"=" * 60) + + try: + # 1. 确保主仓库存在 + if not self.ensure_main_repo(): + return False + + # 2. 更新子模块 + if not self.update_submodule(repo_config): + return False + + # 3. 构建项目 + if not self.build_project(repo_config): + return False + + # 4. 复制构建产物 + if not self.copy_artifacts(repo_config): + return False + + # 5. 执行部署脚本 + if not self.run_deploy_script(repo_config): + return False + + # 6. 提交子模块更新 + if not self.commit_submodule_update(repo_config): + return False + + self.logger.info(f"部署完成: {repo_config['name']}") + return True + + except Exception as e: + self.logger.error(f"部署过程中发生异常: {e}", exc_info=True) + return False diff --git a/.devops/monitor.py b/.devops/monitor.py new file mode 100644 index 0000000..a25f7c1 --- /dev/null +++ b/.devops/monitor.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Git 仓库监听器 +监听多个 Git 仓库的指定分支,检测到新提交时触发部署 +""" + +import os +import sys +import time +import yaml +import logging +import subprocess +from datetime import datetime +from pathlib import Path + +# 添加当前目录到 Python 路径 +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from deployer import Deployer + + +class GitMonitor: + """Git 仓库监听器""" + + def __init__(self, config_path='.devops/config.yaml'): + """初始化监听器""" + self.config_path = config_path + self.config = self._load_config() + self._setup_logging() + self.deployer = Deployer(self.config) + self.last_commits = {} # 存储每个仓库的最后一次提交 hash + + self.logger.info("Git 监听器初始化完成") + + def _load_config(self): + """加载配置文件""" + with open(self.config_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + + def _setup_logging(self): + """设置日志""" + log_config = self.config.get('logging', {}) + log_level = getattr(logging, log_config.get('level', 'INFO')) + log_file = log_config.get('file', '.devops/logs/devops.log') + + # 确保日志目录存在 + os.makedirs(os.path.dirname(log_file), exist_ok=True) + + # 配置日志格式 + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + # 文件处理器 + file_handler = logging.FileHandler(log_file, encoding='utf-8') + file_handler.setFormatter(formatter) + + # 控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + + # 配置 logger + self.logger = logging.getLogger('GitMonitor') + self.logger.setLevel(log_level) + self.logger.addHandler(file_handler) + self.logger.addHandler(console_handler) + + def get_remote_commit(self, repo_url, branch): + """获取远程仓库的最新提交 hash""" + try: + cmd = f"git ls-remote {repo_url} refs/heads/{branch}" + result = subprocess.run( + cmd, shell=True, capture_output=True, text=True, timeout=30 + ) + if result.returncode == 0 and result.stdout: + commit_hash = result.stdout.split()[0] + return commit_hash + return None + except Exception as e: + self.logger.error(f"获取远程提交失败 {repo_url}: {e}") + return None + + def check_repository(self, repo_config): + """检查单个仓库是否有新提交""" + repo_name = repo_config['name'] + repo_url = repo_config['url'] + branch = repo_config['branch'] + + self.logger.debug(f"检查仓库: {repo_name}") + + # 获取最新提交 + current_commit = self.get_remote_commit(repo_url, branch) + if not current_commit: + self.logger.warning(f"无法获取 {repo_name} 的最新提交") + return False + + # 检查是否有新提交 + last_commit = self.last_commits.get(repo_name) + if last_commit is None: + # 首次检查,记录当前提交 + self.last_commits[repo_name] = current_commit + self.logger.info(f"初始化 {repo_name} 提交记录: {current_commit[:8]}") + return False + + if current_commit != last_commit: + self.logger.info( + f"检测到 {repo_name} 有新提交: {last_commit[:8]} -> {current_commit[:8]}" + ) + self.last_commits[repo_name] = current_commit + return True + + return False + + def get_enabled_repos(self): + """获取需要监听的仓库列表""" + enabled = self.config['monitor'].get('enabled_repos', []) + all_repos = self.config['repositories'] + + if not enabled: + # 空列表表示监听所有仓库 + return all_repos + + # 只返回启用的仓库 + return [repo for repo in all_repos if repo['name'] in enabled] + + def run_once(self): + """执行一次检查""" + repos = self.get_enabled_repos() + self.logger.info(f"开始检查 {len(repos)} 个仓库...") + + for repo_config in repos: + try: + if self.check_repository(repo_config): + # 检测到新提交,触发部署 + self.logger.info(f"触发部署: {repo_config['name']}") + success = self.deployer.deploy(repo_config) + + if success: + self.logger.info(f"部署成功: {repo_config['name']}") + else: + self.logger.error(f"部署失败: {repo_config['name']}") + except Exception as e: + self.logger.error(f"处理仓库 {repo_config['name']} 时出错: {e}", exc_info=True) + + def run(self): + """持续监听运行""" + poll_interval = self.config['monitor']['poll_interval'] + self.logger.info(f"开始监听 Git 仓库,轮询间隔: {poll_interval} 秒") + self.logger.info("按 Ctrl+C 停止监听") + + try: + while True: + self.run_once() + time.sleep(poll_interval) + except KeyboardInterrupt: + self.logger.info("收到停止信号,退出监听") + except Exception as e: + self.logger.error(f"监听过程中发生错误: {e}", exc_info=True) + + +def main(): + """主函数""" + import argparse + + parser = argparse.ArgumentParser(description='Git 仓库监听器') + parser.add_argument( + '--config', + default='.devops/config.yaml', + help='配置文件路径' + ) + parser.add_argument( + '--once', + action='store_true', + help='只执行一次检查,不持续监听' + ) + + args = parser.parse_args() + + monitor = GitMonitor(args.config) + + if args.once: + monitor.run_once() + else: + monitor.run() + + +if __name__ == '__main__': + main() diff --git a/.devops/scripts/deploy-common.sh b/.devops/scripts/deploy-common.sh new file mode 100755 index 0000000..48f7987 --- /dev/null +++ b/.devops/scripts/deploy-common.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# 通用函数库 +# 提供部署脚本使用的通用函数 + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 日志函数 +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查命令是否存在 +check_command() { + if ! command -v $1 &> /dev/null; then + log_error "命令不存在: $1" + return 1 + fi + return 0 +} + +# 检查 Docker 服务是否运行 +check_docker() { + if ! docker info &> /dev/null; then + log_error "Docker 未运行" + return 1 + fi + return 0 +} + +# 等待服务健康检查 +wait_for_healthy() { + local service=$1 + local max_wait=${2:-60} + local count=0 + + log_info "等待服务健康检查: $service" + + while [ $count -lt $max_wait ]; do + if docker-compose ps $service | grep -q "Up (healthy)"; then + log_info "服务已就绪: $service" + return 0 + fi + sleep 2 + count=$((count + 2)) + done + + log_warn "服务健康检查超时: $service" + return 1 +} diff --git a/.devops/scripts/deploy-java.sh b/.devops/scripts/deploy-java.sh new file mode 100755 index 0000000..5b1458f --- /dev/null +++ b/.devops/scripts/deploy-java.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Java 服务部署脚本 +# 参数: $1=服务名称, $2=docker服务名, $3=docker-compose路径 + +set -e # 遇到错误立即退出 + +SERVICE_NAME=$1 +DOCKER_SERVICE=$2 +DOCKER_COMPOSE_PATH=$3 + +echo "==========================================" +echo "部署 Java 服务: $SERVICE_NAME" +echo "Docker 服务: $DOCKER_SERVICE" +echo "==========================================" + +# 检查参数 +if [ -z "$SERVICE_NAME" ] || [ -z "$DOCKER_SERVICE" ]; then + echo "错误: 缺少必要参数" + echo "用法: $0 <服务名称> " + exit 1 +fi + +# 获取脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +echo "项目根目录: $PROJECT_ROOT" + +# 进入 docker 目录 +cd "$PROJECT_ROOT/docker" + +echo "重新构建 Docker 镜像..." +docker-compose build --no-cache "$DOCKER_SERVICE" + +echo "启动服务..." +docker-compose up -d "$DOCKER_SERVICE" + +echo "等待服务启动..." +sleep 5 + +echo "检查服务状态..." +docker-compose ps "$DOCKER_SERVICE" + +echo "==========================================" +echo "部署完成: $SERVICE_NAME" +echo "==========================================" diff --git a/.devops/scripts/deploy-ui.sh b/.devops/scripts/deploy-ui.sh new file mode 100755 index 0000000..73494fe --- /dev/null +++ b/.devops/scripts/deploy-ui.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# UI 前端部署脚本 +# 参数: $1=服务名称, $2=docker服务名, $3=docker-compose路径 + +set -e # 遇到错误立即退出 + +SERVICE_NAME=$1 +DOCKER_SERVICE=$2 +DOCKER_COMPOSE_PATH=$3 + +echo "==========================================" +echo "部署前端服务: $SERVICE_NAME" +echo "Docker 服务: $DOCKER_SERVICE" +echo "==========================================" + +# 检查参数 +if [ -z "$SERVICE_NAME" ] || [ -z "$DOCKER_SERVICE" ]; then + echo "错误: 缺少必要参数" + echo "用法: $0 <服务名称> " + exit 1 +fi + +# 获取脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +echo "项目根目录: $PROJECT_ROOT" + +# 进入 docker 目录 +cd "$PROJECT_ROOT/docker" + +echo "重启 Nginx 服务..." +docker-compose restart "$DOCKER_SERVICE" + +echo "等待服务启动..." +sleep 3 + +echo "检查服务状态..." +docker-compose ps "$DOCKER_SERVICE" + +echo "==========================================" +echo "部署完成: $SERVICE_NAME" +echo "==========================================" diff --git a/.devops/start.sh b/.devops/start.sh new file mode 100755 index 0000000..5458337 --- /dev/null +++ b/.devops/start.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# DevOps 监听器启动脚本 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +echo "==========================================" +echo "RuoYi-Cloud DevOps 自动化部署系统" +echo "==========================================" + +# 检查 Python 环境 +if ! command -v python3 &> /dev/null; then + echo "错误: 未找到 python3" + exit 1 +fi + +# 检查依赖 +echo "检查 Python 依赖..." +pip3 list | grep -q PyYAML || pip3 install PyYAML + +# 进入项目根目录 +cd "$PROJECT_ROOT" + +# 启动监听器 +echo "启动 Git 监听器..." +python3 .devops/monitor.py "$@"