# coding: utf-8 #------------------------------ # git WEBHOOK 部署脚本 #------------------------------ import os,sys,time,datetime,base64,subprocess if "/www/server/panel/class" not in sys.path: sys.path.insert(0, "/www/server/panel/class") if '/www/server/panel' not in sys.path: sys.path.insert(0, '/www/server/panel') import public env = os.environ.copy() if 'HOME' not in env: env['HOME'] = os.path.expanduser("~") # 全局日志收集变量 DEPLOY_LOGS = [] # 部署开始时间 DEPLOY_START_TIME = 0 def log(msg, icon="🚀"): """记录日志并收集到全局变量""" log_entry = f"[{datetime.datetime.now().strftime('%H:%M:%S')}] {icon} {msg}" print(log_entry) DEPLOY_LOGS.append(log_entry) def print_separator(): """打印分割线""" separator = "\n" + "="*60 + "\n" + f"🚀 Git 自动部署 - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + "\n" + "="*60 + "\n" print(separator) DEPLOY_LOGS.append(separator) def run_cmd(cmd, cwd=None): """执行命令并返回输出""" cmd = f'bash -l -c \'{cmd}\'' try: result = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True, env=env) if result.stdout: print(result.stdout.strip()) DEPLOY_LOGS.append(result.stdout.strip()) if result.stderr: print(result.stderr.strip()) DEPLOY_LOGS.append(result.stderr.strip()) return result.returncode == 0 except Exception as e: error_msg = f"命令执行失败: {str(e)}" log(error_msg, "❌") return False def get_site_info(site_name): """获取网站信息""" site = public.M('sites').where("name = ?", (site_name,)).find() if not site: log(f"网站不存在: {site_name}", "❌") return None site_path = site.get('path') if not os.path.exists(f"{site_path}/.git"): log(f"不是Git仓库: {site_path}", "❌") return None return {'site': site, 'path': site_path} def display_git_info(site_path): """显示git仓库信息""" try: # 获取当前分支 result = subprocess.run(f"cd {site_path} && git rev-parse --abbrev-ref HEAD", shell=True, capture_output=True, text=True, env=env) if result.returncode == 0: current_branch = result.stdout.strip() log(f"当前分支: {current_branch}") # 获取最新commit信息 result = subprocess.run(f"cd {site_path} && git log -1 --pretty=format:'%h|%s|%an'", shell=True, capture_output=True, text=True, env=env) if result.returncode == 0: commit_info = result.stdout.strip().split('|') if len(commit_info) >= 3: log(f"最新提交: {commit_info[0]} - {commit_info[1]} (by {commit_info[2]})") except: pass def execute_deploy_script(site_path, deploy_script): """执行部署脚本""" if not deploy_script: return True, "" log("执行部署脚本...") script_file = f"/tmp/deploy_{int(time.time())}.sh" try: with open(script_file, 'w') as f: f.write(f"cd {site_path}\n{deploy_script}") os.chmod(script_file, 0o755) if not run_cmd(f"bash {script_file}"): return False, "部署脚本执行失败" return True, "" finally: if os.path.exists(script_file): os.remove(script_file) def auto_deploy(config): """自动部署(无分支无commit)""" site_name = config.get('type_parm') config_branch = config.get('branch', 'main') deploy_script = config.get('deploy_script', '') log(f"自动部署: {site_name} -> {config_branch}") # 获取网站信息 site_info = get_site_info(site_name) if not site_info: return False, "网站不存在或不是Git仓库" site_path = site_info['path'] display_git_info(site_path) # 拉取代码 if not run_cmd(f"cd {site_path} && git pull origin {config_branch}"): return False, "代码拉取失败" log("🚀 仓库成功部署!") # 执行部署脚本 success, error_msg = execute_deploy_script(site_path, deploy_script) if not success: return False, error_msg # 修正权限 run_cmd(f"chown -R www:www {site_path}") log("部署完成", "✅") return True, "" def manual_branch_deploy(config, manual_branch): """手动分支部署(有分支)""" site_name = config.get('type_parm') deploy_script = config.get('deploy_script', '') log(f"手动部署分支: {site_name} -> {manual_branch}") # 获取网站信息 site_info = get_site_info(site_name) if not site_info: return False, "网站不存在或不是Git仓库" site_path = site_info['path'] display_git_info(site_path) # 切换分支并拉取代码 if not run_cmd(f"cd {site_path} && git checkout {manual_branch}"): return False, f"切换到分支失败: {manual_branch}" if not run_cmd(f"cd {site_path} && git pull origin {manual_branch}"): return False, "代码拉取失败" log("🚀 手动分支部署成功!") # 执行部署脚本 success, error_msg = execute_deploy_script(site_path, deploy_script) if not success: return False, error_msg # 修正权限 run_cmd(f"chown -R www:www {site_path}") log("部署完成", "✅") return True, "" def manual_rollback_deploy(config, manual_branch, manual_commit): """手动回滚部署(有分支有commit)""" site_name = config.get('type_parm') deploy_script = config.get('deploy_script', '') log(f"回滚代码: {site_name} -> {manual_branch} -> {manual_commit[:8]}") # 获取网站信息 site_info = get_site_info(site_name) if not site_info: return False, "网站不存在或不是Git仓库" site_path = site_info['path'] display_git_info(site_path) # 切换分支 if not run_cmd(f"cd {site_path} && git checkout {manual_branch}"): return False, f"切换到分支失败: {manual_branch}" # 回滚到指定commit if not run_cmd(f"cd {site_path} && git reset --hard {manual_commit}"): return False, "回滚失败" log(f"🚀 成功回退到commit: {manual_commit[:8]}!") # 执行部署脚本 success, error_msg = execute_deploy_script(site_path, deploy_script) if not success: return False, error_msg # 修正权限 run_cmd(f"chown -R www:www {site_path}") log("部署完成", "✅") return True, "" def save_deploy_log(config, success, error_msg="", deploy_mode=1): """保存部署日志到数据库(总是被调用)""" try: # 基础信息 git_manager_id = config.get('id', 0) webhook_name = config.get('webhook_name', '') site_name = config.get('type_parm', '') deploy_type = config.get('type', 'site') branch = config.get('branch', 'main') deploy_script = config.get('deploy_script', '') duration = time.time() - DEPLOY_START_TIME log_content = get_deploy_logs() deploy_time = int(time.time()) status = 1 if success else 0 # 获取commit信息 commit_hash, commit_message, commit_author, current_branch = "", "", "", "" if site_name: site_info = get_site_info(site_name) if site_info: commit_hash, commit_message, commit_author, current_branch = get_commit_info(site_info['path']) # 插入日志记录 public.M('git_deploy_logs').insert({ 'git_manager_id': git_manager_id, 'webhook_name': webhook_name, 'site_name': site_name, 'type': deploy_type, 'status': status, 'deploy_time': deploy_time, 'duration': round(duration, 2), 'commit_hash': commit_hash, 'branch': current_branch, 'log_content': log_content, 'error_msg': error_msg, 'deploy_script': deploy_script, 'deploy_mode': deploy_mode, 'commit_message': commit_message, 'commit_author': commit_author }) # 更新数据库中的分支信息(如果发生了变化) if branch != current_branch: public.M('git_manager').where('id=?', (git_manager_id,)).update({'branch': current_branch}) log(f"更新配置分支: {branch} -> {current_branch}", "📝") mode_name = {1: "自动部署", 2: "手动分支部署", 3: "手动回滚部署"}.get(deploy_mode, "未知") log(f"部署日志已保存 ({mode_name}, git_manager_id: {git_manager_id})", "📋") except Exception as e: log(f"保存部署日志失败: {str(e)}", "⚠️") def get_commit_info(site_path): """获取commit信息""" commit_hash = "" commit_message = "" commit_author = "" branch = "" try: # 获取commit hash result = subprocess.run(f"cd {site_path} && git rev-parse HEAD", shell=True, capture_output=True, text=True, env=env) if result.returncode == 0: commit_hash = result.stdout.strip() # 获取commit message和author result = subprocess.run(f"cd {site_path} && git log -1 --pretty=format:'%s|%an' {commit_hash}", shell=True, capture_output=True, text=True, env=env) if result.returncode == 0: log_info = result.stdout.strip().split('|') if len(log_info) >= 2: commit_message = log_info[0] commit_author = log_info[1] # 获取当前分支 result = subprocess.run(f"cd {site_path} && git rev-parse --abbrev-ref HEAD", shell=True, capture_output=True, text=True, env=env) if result.returncode == 0: branch = result.stdout.strip() except: pass return commit_hash, commit_message, commit_author, branch def get_deploy_logs(): """获取完整的部署日志""" return "\n".join(DEPLOY_LOGS) def prepare_deployment(): """准备部署:解析参数、确定模式、获取配置""" # 解析参数 if len(sys.argv) < 2: log("缺少参数", "❌") return None, None, None, None, None try: webhook_name = base64.b64decode(sys.argv[1]).decode('utf-8') # 解析手动部署参数 manual_branch = None manual_commit = None if len(sys.argv) >= 3: manual_branch = sys.argv[2] log(f"检测到手动分支参数: {manual_branch}") if len(sys.argv) >= 4: manual_commit = sys.argv[3] log(f"检测到手动回滚参数: {manual_commit[:8]}") # 确定部署模式 if manual_commit: deploy_mode, mode_name = 3, "手动回滚部署" elif manual_branch: deploy_mode, mode_name = 2, "手动分支部署" else: deploy_mode, mode_name = 1, "自动部署" # 获取配置 config = public.M('git_manager').where("webhook_name = ?", (webhook_name,)).find() if not config: log(f"未找到配置: {webhook_name}", "❌") return None, None, None, None, None deploy_type = config.get('type', 'site') log(f"部署类型: {deploy_type}") return config, manual_branch, manual_commit, deploy_mode, mode_name except Exception as e: log(f"参数解析失败: {str(e)}", "❌") return None, None, None, None, None def execute_deployment(config, manual_branch, manual_commit, deploy_mode): """执行部署""" deploy_type = config.get('type', 'site') if deploy_type == 'site': if deploy_mode == 1: return auto_deploy(config) elif deploy_mode == 2: return manual_branch_deploy(config, manual_branch) elif deploy_mode == 3: return manual_rollback_deploy(config, manual_branch, manual_commit) elif deploy_type == 'project': log("项目类型部署,跳过...") return True, "" else: log(f"未知部署类型: {deploy_type}", "❌") return False, f"未知部署类型: {deploy_type}" if __name__ == '__main__': # 记录开始时间 DEPLOY_START_TIME = time.time() # 准备部署 config, manual_branch, manual_commit, deploy_mode, mode_name = prepare_deployment() if not config: sys.exit(1) print_separator() webhook_name = config.get('webhook_name', '') log(f"开始部署: {webhook_name} ({mode_name})") try: # 执行部署 success, error_msg = execute_deployment(config, manual_branch, manual_commit, deploy_mode) except Exception as e: error_msg = str(e) log(f"部署异常: {error_msg}", "❌") success = False # 保存部署日志(总是被调用) # config['branch'] = manual_branch or config.get('branch', '') save_deploy_log(config, success, error_msg, deploy_mode) sys.exit(0 if success else 1)