diff --git a/app/chain/system.py b/app/chain/system.py index c309d3a2..399ed00c 100644 --- a/app/chain/system.py +++ b/app/chain/system.py @@ -1,5 +1,6 @@ import json import re +import shutil from pathlib import Path from typing import Union, Optional @@ -42,11 +43,117 @@ class SystemChain(ChainBase): "channel": channel.value, "userid": userid }, self._restart_file) + # 主动备份一次插件 + self.backup_plugins() # 设置停止标志,通知所有模块准备停止 global_vars.stop_system() # 重启 SystemHelper.restart() + @staticmethod + def backup_plugins(): + """ + 备份插件到用户配置目录(仅docker环境) + """ + + # 非docker环境不处理 + if not SystemUtils.is_docker(): + return + + try: + # 使用绝对路径确保准确性 + plugins_dir = settings.ROOT_PATH / "app" / "plugins" + backup_dir = settings.CONFIG_PATH / "plugins_backup" + + if not plugins_dir.exists(): + logger.info("插件目录不存在,跳过备份") + return + + # 确保备份目录存在 + backup_dir.mkdir(parents=True, exist_ok=True) + + # 需要排除的文件和目录 + exclude_items = {"__init__.py", "__pycache__", ".DS_Store"} + + # 遍历插件目录,备份除排除项外的所有内容 + for item in plugins_dir.iterdir(): + if item.name in exclude_items: + continue + + target_path = backup_dir / item.name + + # 如果是目录 + if item.is_dir(): + if target_path.exists(): + continue + shutil.copytree(item, target_path) + logger.info(f"已备份插件目录: {item.name}") + # 如果是文件 + elif item.is_file(): + if target_path.exists(): + continue + shutil.copy2(item, target_path) + logger.info(f"已备份插件文件: {item.name}") + + logger.info(f"插件备份完成,备份位置: {backup_dir}") + + except Exception as e: + logger.error(f"插件备份失败: {str(e)}") + + @staticmethod + def restore_plugins(): + """ + 从备份恢复插件到app/plugins目录,恢复完成后删除备份(仅docker环境) + """ + + # 非docker环境不处理 + if not SystemUtils.is_docker(): + return + + # 使用绝对路径确保准确性 + plugins_dir = settings.ROOT_PATH / "app" / "plugins" + backup_dir = settings.CONFIG_PATH / "plugins_backup" + + if not backup_dir.exists(): + logger.info("插件备份目录不存在,跳过恢复") + return + + # 系统被重置才恢复插件 + if SystemHelper().is_system_reset(): + + # 确保插件目录存在 + plugins_dir.mkdir(parents=True, exist_ok=True) + + # 遍历备份目录,恢复所有内容 + restored_count = 0 + for item in backup_dir.iterdir(): + target_path = plugins_dir / item.name + try: + # 如果是目录,且目录内有内容 + if item.is_dir() and any(item.iterdir()): + if target_path.exists(): + shutil.rmtree(target_path) + shutil.copytree(item, target_path) + logger.info(f"已恢复插件目录: {item.name}") + restored_count += 1 + # 如果是文件 + elif item.is_file(): + shutil.copy2(item, target_path) + logger.info(f"已恢复插件文件: {item.name}") + restored_count += 1 + except Exception as e: + logger.error(f"恢复插件 {item.name} 时发生错误: {str(e)}") + continue + + logger.info(f"插件恢复完成,共恢复 {restored_count} 个项目") + + # 删除备份目录 + try: + shutil.rmtree(backup_dir) + logger.info(f"已删除插件备份目录: {backup_dir}") + except Exception as e: + logger.warning(f"删除备份目录失败: {str(e)}") + def __get_version_message(self) -> str: """ 获取版本信息文本 diff --git a/app/helper/system.py b/app/helper/system.py index 88b42a9b..bcbe459a 100644 --- a/app/helper/system.py +++ b/app/helper/system.py @@ -109,7 +109,6 @@ class SystemHelper: try: # 检查容器是否配置了自动重启策略 has_restart_policy = SystemHelper._check_restart_policy() - if has_restart_policy: # 有重启策略,使用优雅退出方式 logger.info("检测到容器配置了自动重启策略,使用优雅重启方式...") @@ -120,7 +119,6 @@ class SystemHelper: # 没有重启策略,使用Docker API强制重启 logger.info("容器未配置自动重启策略,使用Docker API重启...") return SystemHelper._docker_api_restart() - except Exception as err: logger.error(f"重启失败: {str(err)}") # 降级为Docker API重启 @@ -141,7 +139,6 @@ class SystemHelper: # 重启容器 client.containers.get(container_id).restart() return True, "" - except Exception as docker_err: return False, f"重启时发生错误:{str(docker_err)}" diff --git a/app/startup/lifecycle.py b/app/startup/lifecycle.py index c06d2bf8..64e0844f 100644 --- a/app/startup/lifecycle.py +++ b/app/startup/lifecycle.py @@ -8,7 +8,7 @@ from app.startup.command_initializer import init_command, stop_command, restart_ from app.startup.memory_initializer import init_memory_manager, stop_memory_manager from app.startup.modules_initializer import init_modules, stop_modules from app.startup.monitor_initializer import stop_monitor, init_monitor -from app.startup.plugins_initializer import init_plugins, stop_plugins, sync_plugins, backup_plugins, restore_plugins +from app.startup.plugins_initializer import init_plugins, stop_plugins, sync_plugins from app.startup.routers_initializer import init_routers from app.startup.scheduler_initializer import stop_scheduler, init_scheduler, init_plugin_scheduler from app.startup.workflow_initializer import init_workflow, stop_workflow @@ -41,7 +41,7 @@ async def lifespan(app: FastAPI): # 初始化路由 init_routers(app) # 恢复插件备份 - restore_plugins() + SystemChain().restore_plugins() # 初始化插件 init_plugins() # 初始化定时器 @@ -70,7 +70,7 @@ async def lifespan(app: FastAPI): except Exception as e: print(str(e)) # 备份插件 - backup_plugins() + SystemChain().backup_plugins() # 停止内存管理器 stop_memory_manager() # 停止工作流 diff --git a/app/startup/plugins_initializer.py b/app/startup/plugins_initializer.py index e79a3bf9..0c59f631 100644 --- a/app/startup/plugins_initializer.py +++ b/app/startup/plugins_initializer.py @@ -1,11 +1,7 @@ import asyncio -import shutil -from app.core.config import settings from app.core.plugin import PluginManager from app.log import logger -from app.utils.system import SystemUtils -from app.helper.system import SystemHelper async def sync_plugins() -> bool: @@ -79,105 +75,3 @@ def stop_plugins(): plugin_manager.stop_monitor() except Exception as e: logger.error(f"停止插件时发生错误:{e}", exc_info=True) - - -def backup_plugins(): - """ - 备份插件到用户配置目录(仅docker环境) - """ - - # 非docker环境不处理 - if not SystemUtils.is_docker(): - return - - try: - # 使用绝对路径确保准确性 - plugins_dir = settings.ROOT_PATH / "app" / "plugins" - backup_dir = settings.CONFIG_PATH / "plugins_backup" - - if not plugins_dir.exists(): - logger.info("插件目录不存在,跳过备份") - return - - # 确保备份目录存在 - backup_dir.mkdir(parents=True, exist_ok=True) - - # 需要排除的文件和目录 - exclude_items = {"__init__.py", "__pycache__", ".DS_Store"} - - # 遍历插件目录,备份除排除项外的所有内容 - for item in plugins_dir.iterdir(): - if item.name in exclude_items: - continue - - target_path = backup_dir / item.name - - # 如果是目录 - if item.is_dir(): - if target_path.exists(): - shutil.rmtree(target_path) - shutil.copytree(item, target_path) - logger.info(f"已备份插件目录: {item.name}") - # 如果是文件 - elif item.is_file(): - shutil.copy2(item, target_path) - logger.info(f"已备份插件文件: {item.name}") - - logger.info(f"插件备份完成,备份位置: {backup_dir}") - - except Exception as e: - logger.error(f"插件备份失败: {str(e)}") - - -def restore_plugins(): - """ - 从备份恢复插件到app/plugins目录,恢复完成后删除备份(仅docker环境) - """ - - # 非docker环境不处理 - if not SystemUtils.is_docker(): - return - - # 使用绝对路径确保准确性 - plugins_dir = settings.ROOT_PATH / "app" / "plugins" - backup_dir = settings.CONFIG_PATH / "plugins_backup" - - if not backup_dir.exists(): - logger.info("插件备份目录不存在,跳过恢复") - return - - # 系统被重置才恢复插件 - if SystemHelper().is_system_reset(): - - # 确保插件目录存在 - plugins_dir.mkdir(parents=True, exist_ok=True) - - # 遍历备份目录,恢复所有内容 - restored_count = 0 - for item in backup_dir.iterdir(): - target_path = plugins_dir / item.name - try: - # 如果是目录,且目录内有内容 - if item.is_dir() and any(item.iterdir()): - if target_path.exists(): - shutil.rmtree(target_path) - shutil.copytree(item, target_path) - logger.info(f"已恢复插件目录: {item.name}") - restored_count += 1 - # 如果是文件 - elif item.is_file(): - shutil.copy2(item, target_path) - logger.info(f"已恢复插件文件: {item.name}") - restored_count += 1 - except Exception as e: - logger.error(f"恢复插件 {item.name} 时发生错误: {str(e)}") - continue - - logger.info(f"插件恢复完成,共恢复 {restored_count} 个项目") - - # 删除备份目录 - try: - shutil.rmtree(backup_dir) - logger.info(f"已删除插件备份目录: {backup_dir}") - except Exception as e: - logger.warning(f"删除备份目录失败: {str(e)}")