feat:内建重启前主动备份插件

This commit is contained in:
jxxghp
2025-06-16 08:57:21 +08:00
parent 0fa49bb9c6
commit c06001d921
4 changed files with 110 additions and 112 deletions
+107
View File
@@ -1,5 +1,6 @@
import json import json
import re import re
import shutil
from pathlib import Path from pathlib import Path
from typing import Union, Optional from typing import Union, Optional
@@ -42,11 +43,117 @@ class SystemChain(ChainBase):
"channel": channel.value, "channel": channel.value,
"userid": userid "userid": userid
}, self._restart_file) }, self._restart_file)
# 主动备份一次插件
self.backup_plugins()
# 设置停止标志,通知所有模块准备停止 # 设置停止标志,通知所有模块准备停止
global_vars.stop_system() global_vars.stop_system()
# 重启 # 重启
SystemHelper.restart() 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: def __get_version_message(self) -> str:
""" """
获取版本信息文本 获取版本信息文本
-3
View File
@@ -109,7 +109,6 @@ class SystemHelper:
try: try:
# 检查容器是否配置了自动重启策略 # 检查容器是否配置了自动重启策略
has_restart_policy = SystemHelper._check_restart_policy() has_restart_policy = SystemHelper._check_restart_policy()
if has_restart_policy: if has_restart_policy:
# 有重启策略,使用优雅退出方式 # 有重启策略,使用优雅退出方式
logger.info("检测到容器配置了自动重启策略,使用优雅重启方式...") logger.info("检测到容器配置了自动重启策略,使用优雅重启方式...")
@@ -120,7 +119,6 @@ class SystemHelper:
# 没有重启策略,使用Docker API强制重启 # 没有重启策略,使用Docker API强制重启
logger.info("容器未配置自动重启策略,使用Docker API重启...") logger.info("容器未配置自动重启策略,使用Docker API重启...")
return SystemHelper._docker_api_restart() return SystemHelper._docker_api_restart()
except Exception as err: except Exception as err:
logger.error(f"重启失败: {str(err)}") logger.error(f"重启失败: {str(err)}")
# 降级为Docker API重启 # 降级为Docker API重启
@@ -141,7 +139,6 @@ class SystemHelper:
# 重启容器 # 重启容器
client.containers.get(container_id).restart() client.containers.get(container_id).restart()
return True, "" return True, ""
except Exception as docker_err: except Exception as docker_err:
return False, f"重启时发生错误:{str(docker_err)}" return False, f"重启时发生错误:{str(docker_err)}"
+3 -3
View File
@@ -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.memory_initializer import init_memory_manager, stop_memory_manager
from app.startup.modules_initializer import init_modules, stop_modules from app.startup.modules_initializer import init_modules, stop_modules
from app.startup.monitor_initializer import stop_monitor, init_monitor 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.routers_initializer import init_routers
from app.startup.scheduler_initializer import stop_scheduler, init_scheduler, init_plugin_scheduler from app.startup.scheduler_initializer import stop_scheduler, init_scheduler, init_plugin_scheduler
from app.startup.workflow_initializer import init_workflow, stop_workflow from app.startup.workflow_initializer import init_workflow, stop_workflow
@@ -41,7 +41,7 @@ async def lifespan(app: FastAPI):
# 初始化路由 # 初始化路由
init_routers(app) init_routers(app)
# 恢复插件备份 # 恢复插件备份
restore_plugins() SystemChain().restore_plugins()
# 初始化插件 # 初始化插件
init_plugins() init_plugins()
# 初始化定时器 # 初始化定时器
@@ -70,7 +70,7 @@ async def lifespan(app: FastAPI):
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
# 备份插件 # 备份插件
backup_plugins() SystemChain().backup_plugins()
# 停止内存管理器 # 停止内存管理器
stop_memory_manager() stop_memory_manager()
# 停止工作流 # 停止工作流
-106
View File
@@ -1,11 +1,7 @@
import asyncio import asyncio
import shutil
from app.core.config import settings
from app.core.plugin import PluginManager from app.core.plugin import PluginManager
from app.log import logger from app.log import logger
from app.utils.system import SystemUtils
from app.helper.system import SystemHelper
async def sync_plugins() -> bool: async def sync_plugins() -> bool:
@@ -79,105 +75,3 @@ def stop_plugins():
plugin_manager.stop_monitor() plugin_manager.stop_monitor()
except Exception as e: except Exception as e:
logger.error(f"停止插件时发生错误:{e}", exc_info=True) 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)}")