diff --git a/.gitignore b/.gitignore index 644d147..d48414f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,71 +1,42 @@ -```gitignore -# Logs and temp files -*.log -*.tmp -*.swp - -# Environment -.env -.env.local -*.env.* - -# Editors -.vscode/ -.idea/ +``` +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +*.py~ # Dependencies -node_modules/ .venv/ venv/ -__pycache__/ -.mypy_cache/ -.pytest_cache/ -dist/ -build/ -target/ -.gradle/ +.env +.env.local +.env.* -# Compiled files -*.pyc -*.class -*.o -*.exe -*.dll -*.so -*.a -*.obj -*.out +# Logs +*.log -# System files +# OS .DS_Store Thumbs.db -# Coverage +# Backup files +*~ +*.bak +*.swp +*.swo + +# Coverage reports coverage/ htmlcov/ .coverage -# Compressed files -*.zip -*.gz -*.tar -*.tgz -*.bz2 -*.xz -*.7z -*.rar -*.zst -*.lz4 -*.lzh -*.cab -*.arj -*.rpm -*.deb -*.Z -*.lz -*.lzo -*.tar.gz -*.tar.bz2 -*.tar.xz -*.tar.zst +# Testing +.pytest_cache/ +.mypy_cache/ + +# Distribution / packaging +dist/ +build/ +*.egg-info/ ``` \ No newline at end of file diff --git a/oss/core/context.py b/oss/core/context.py new file mode 100644 index 0000000..2dcae2e --- /dev/null +++ b/oss/core/context.py @@ -0,0 +1,58 @@ +"""Context class for plugin execution environment.""" + +from typing import Any, Dict, Optional + + +class Context: + """Execution context for plugins. + + Provides access to configuration, state, and utilities during plugin execution. + """ + + def __init__(self, config: Optional[Dict[str, Any]] = None): + """Initialize the context. + + Args: + config: Optional configuration dictionary. + """ + self.config = config or {} + self._state: Dict[str, Any] = {} + + def get(self, key: str, default: Any = None) -> Any: + """Get a configuration value. + + Args: + key: Configuration key. + default: Default value if key not found. + + Returns: + The configuration value or default. + """ + return self.config.get(key, default) + + def set_state(self, key: str, value: Any) -> None: + """Set a state value. + + Args: + key: State key. + value: State value. + """ + self._state[key] = value + + def get_state(self, key: str, default: Any = None) -> Any: + """Get a state value. + + Args: + key: State key. + default: Default value if key not found. + + Returns: + The state value or default. + """ + return self._state.get(key, default) + + def __repr__(self) -> str: + return f"Context(config={self.config})" + + +__all__ = ['Context'] diff --git a/oss/plugin/base.py b/oss/plugin/base.py new file mode 100644 index 0000000..8378ef5 --- /dev/null +++ b/oss/plugin/base.py @@ -0,0 +1,8 @@ +"""Base plugin module for backward compatibility.""" + +from oss.plugin.types import Plugin + +# Alias for backward compatibility +BasePlugin = Plugin + +__all__ = ['BasePlugin'] diff --git a/oss/plugins/auto_dependency.py b/oss/plugins/auto_dependency.py new file mode 100644 index 0000000..196b445 --- /dev/null +++ b/oss/plugins/auto_dependency.py @@ -0,0 +1,370 @@ +""" +Auto Dependency Plugin - 依赖自动安装插件 + +该插件允许其他插件在声明文件 (manifest.json) 中声明所需的系统依赖, +然后扫描所有插件的声明文件,检查并安装缺失的系统依赖。 + +通过插件加载器的 /PL 注入能力接口进行对接。 +""" + +import json +import os +import subprocess +import sys +from typing import List, Dict, Any, Optional +from pathlib import Path + +from oss.plugin.base import BasePlugin +from oss.core.context import Context + + +class AutoDependencyPlugin(BasePlugin): + """依赖自动安装插件""" + + def __init__(self): + super().__init__() + self.name = "auto_dependency" + self.version = "1.0.0" + self.description = "自动扫描并安装插件声明的系统依赖" + self.plugins_dir: Optional[Path] = None + self.manifest_filename = "manifest.json" + self.logger = None + + def init(self, deps: Optional[Dict[str, Any]] = None): + """初始化插件""" + # 获取插件目录路径 + self.plugins_dir = Path(__file__).parent + if deps and 'logger' in deps: + self.logger = deps['logger'] + else: + import logging + self.logger = logging.getLogger(self.name) + + self.logger.info(f"AutoDependencyPlugin 初始化完成,插件目录:{self.plugins_dir}") + + def start(self): + """启动插件""" + self.logger.info("AutoDependencyPlugin 启动") + + def stop(self): + """停止插件""" + self.logger.info("AutoDependencyPlugin 停止") + + def scan(self) -> List[Dict[str, Any]]: + """ + 扫描所有插件的声明文件,收集系统依赖信息 + + Returns: + List[Dict]: 包含所有插件依赖信息的列表 + 每个元素格式: { + "plugin": str, # 插件名称 + "dependencies": List, # 依赖列表 + "package_manager": str # 包管理器类型 + } + """ + all_dependencies = [] + + if not self.plugins_dir.exists(): + self.logger.warning(f"插件目录不存在: {self.plugins_dir}") + return all_dependencies + + # 遍历所有插件文件 + for plugin_file in self.plugins_dir.glob("*.py"): + plugin_name = plugin_file.stem + + # 跳过自身和__init__等文件 + if plugin_name.startswith("_") or plugin_name == self.name: + continue + + # 查找对应的 manifest 文件 + manifest_path = self._find_manifest_for_plugin(plugin_name) + + if manifest_path and manifest_path.exists(): + try: + with open(manifest_path, 'r', encoding='utf-8') as f: + manifest = json.load(f) + + # 提取系统依赖信息 + system_deps = manifest.get("system_dependencies", []) + package_manager = manifest.get("package_manager", "apt-get") + + if system_deps: + all_dependencies.append({ + "plugin": plugin_name, + "dependencies": system_deps, + "package_manager": package_manager, + "manifest_path": str(manifest_path) + }) + + self.logger.info( + f"插件 {plugin_name} 声明了 {len(system_deps)} 个系统依赖" + ) + + except json.JSONDecodeError as e: + self.logger.error(f"解析 {manifest_path} 失败: {e}") + except Exception as e: + self.logger.error(f"处理插件 {plugin_name} 时出错: {e}") + + return all_dependencies + + def check(self, dependencies: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + 检查指定的系统依赖是否已安装 + + Args: + dependencies: 依赖信息列表,格式同 scan() 返回值 + + Returns: + Dict: 检查结果 + { + "total": int, # 总依赖数 + "installed": int, # 已安装数 + "missing": List[Dict], # 缺失的依赖详情 + "all_installed": bool # 是否全部已安装 + } + """ + result = { + "total": 0, + "installed": 0, + "missing": [], + "all_installed": True + } + + for dep_info in dependencies: + plugin_name = dep_info["plugin"] + package_manager = dep_info["package_manager"] + + for package in dep_info["dependencies"]: + result["total"] += 1 + + if self._is_package_installed(package, package_manager): + result["installed"] += 1 + self.logger.debug(f"包 {package} 已安装 (插件: {plugin_name})") + else: + result["missing"].append({ + "package": package, + "plugin": plugin_name, + "package_manager": package_manager + }) + result["all_installed"] = False + self.logger.warning(f"包 {package} 未安装 (插件: {plugin_name})") + + return result + + def install(self, missing: List[Dict[str, str]], + auto_confirm: bool = True) -> Dict[str, Any]: + """ + 安装缺失的系统依赖 + + Args: + missing: 缺失的依赖列表,格式为 [{"package": str, "package_manager": str}] + auto_confirm: 是否自动确认安装 + + Returns: + Dict: 安装结果 + { + "success": List[str], # 成功安装的包 + "failed": List[Dict], # 安装失败的包及原因 + "total": int, # 尝试安装的总数 + } + """ + result = { + "success": [], + "failed": [], + "total": len(missing) + } + + if not missing: + self.logger.info("没有需要安装的依赖") + return result + + # 按包管理器分组 + packages_by_pm: Dict[str, List[str]] = {} + for item in missing: + pm = item.get("package_manager", "apt-get") + pkg = item["package"] + + if pm not in packages_by_pm: + packages_by_pm[pm] = [] + packages_by_pm[pm].append(pkg) + + # 执行安装 + for pm, packages in packages_by_pm.items(): + self.logger.info(f"使用 {pm} 安装包: {', '.join(packages)}") + + success, failed = self._install_packages(packages, pm, auto_confirm) + + result["success"].extend(success) + for fail_pkg, reason in failed: + result["failed"].append({ + "package": fail_pkg, + "reason": reason + }) + + return result + + def info(self) -> Dict[str, Any]: + """ + 获取插件信息 + + Returns: + Dict: 插件详细信息 + """ + return { + "name": self.name, + "version": self.version, + "description": self.description, + "supported_package_managers": [ + "apt-get", "yum", "dnf", "pacman", "brew", "apk" + ], + "api_methods": ["scan", "check", "install", "info"] + } + + def _find_manifest_for_plugin(self, plugin_name: str) -> Optional[Path]: + """查找插件对应的 manifest 文件""" + # 可能的 manifest 文件位置 + possible_paths = [ + self.plugins_dir / f"{plugin_name}.json", + self.plugins_dir / plugin_name / "manifest.json", + self.plugins_dir / f"{plugin_name}" / f"{plugin_name}.json", + ] + + for path in possible_paths: + if path.exists(): + return path + + # 也检查插件文件同目录下的同名 json 文件 + plugin_file = self.plugins_dir / f"{plugin_name}.py" + if plugin_file.exists(): + json_file = self.plugins_dir / f"{plugin_name}.json" + if json_file.exists(): + return json_file + + return None + + def _is_package_installed(self, package: str, package_manager: str) -> bool: + """检查包是否已安装""" + try: + if package_manager in ["apt-get", "apt"]: + cmd = ["dpkg", "-l", package] + elif package_manager in ["yum", "dnf"]: + cmd = ["rpm", "-q", package] + elif package_manager == "pacman": + cmd = ["pacman", "-Q", package] + elif package_manager == "brew": + cmd = ["brew", "list", "--versions", package] + elif package_manager == "apk": + cmd = ["apk", "info", "-e", package] + else: + # 默认使用 which/whereis 检查可执行文件 + cmd = ["which", package] + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=30 + ) + + return result.returncode == 0 + + except subprocess.TimeoutExpired: + self.logger.warning(f"检查包 {package} 超时") + return False + except Exception as e: + self.logger.error(f"检查包 {package} 时出错: {e}") + return False + + def _install_packages(self, packages: List[str], + package_manager: str, + auto_confirm: bool = True) -> tuple: + """ + 安装包 + + Returns: + tuple: (success_list, failed_list) + success_list: 成功安装的包名列表 + failed_list: [(包名, 失败原因), ...] + """ + success = [] + failed = [] + + try: + if package_manager in ["apt-get", "apt"]: + cmd_prefix = ["apt-get", "install", "-y"] if auto_confirm else ["apt-get", "install"] + elif package_manager == "yum": + cmd_prefix = ["yum", "install", "-y"] if auto_confirm else ["yum", "install"] + elif package_manager == "dnf": + cmd_prefix = ["dnf", "install", "-y"] if auto_confirm else ["dnf", "install"] + elif package_manager == "pacman": + cmd_prefix = ["pacman", "-S", "--noconfirm"] if auto_confirm else ["pacman", "-S"] + elif package_manager == "brew": + cmd_prefix = ["brew", "install"] + elif package_manager == "apk": + cmd_prefix = ["apk", "add"] + else: + self.logger.error(f"不支持的包管理器: {package_manager}") + for pkg in packages: + failed.append((pkg, f"不支持的包管理器: {package_manager}")) + return success, failed + + # 合并命令 + cmd = cmd_prefix + packages + + self.logger.info(f"执行安装命令: {' '.join(cmd)}") + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=300 # 5 分钟超时 + ) + + if result.returncode == 0: + success.extend(packages) + self.logger.info(f"成功安装包: {', '.join(packages)}") + else: + error_msg = result.stderr.strip() or result.stdout.strip() + for pkg in packages: + failed.append((pkg, error_msg)) + self.logger.error(f"安装包失败: {error_msg}") + + except subprocess.TimeoutExpired: + for pkg in packages: + failed.append((pkg, "安装超时")) + self.logger.error("安装包超时") + except PermissionError: + for pkg in packages: + failed.append((pkg, "权限不足,需要 root 权限")) + self.logger.error("安装包需要 root 权限") + except Exception as e: + for pkg in packages: + failed.append((pkg, str(e))) + self.logger.error(f"安装包时发生异常: {e}") + + return success, failed + + def execute(self, action: str, **kwargs) -> Any: + """ + 执行插件动作 (供插件加载器调用) + + Args: + action: 动作名称 (scan, check, install, info) + **kwargs: 动作参数 + + Returns: + 动作执行结果 + """ + if action == "scan": + return self.scan() + elif action == "check": + dependencies = kwargs.get("dependencies", self.scan()) + return self.check(dependencies) + elif action == "install": + missing = kwargs.get("missing", []) + auto_confirm = kwargs.get("auto_confirm", True) + return self.install(missing, auto_confirm) + elif action == "info": + return self.info() + else: + raise ValueError(f"未知的动作: {action}") diff --git a/oss/plugins/firewall.json b/oss/plugins/firewall.json new file mode 100644 index 0000000..3cbf76a --- /dev/null +++ b/oss/plugins/firewall.json @@ -0,0 +1,7 @@ +{ + "name": "firewall", + "version": "1.0.0", + "description": "防火墙管理插件", + "system_dependencies": ["iptables", "ufw"], + "package_manager": "apt-get" +} diff --git a/oss/plugins/ftp_server.json b/oss/plugins/ftp_server.json new file mode 100644 index 0000000..54c62e3 --- /dev/null +++ b/oss/plugins/ftp_server.json @@ -0,0 +1,7 @@ +{ + "name": "ftp_server", + "version": "1.0.0", + "description": "FTP 服务器插件", + "system_dependencies": ["vsftpd", "proftpd"], + "package_manager": "apt-get" +} diff --git a/store/@{FutureOSS}/auto-dependency/PL/main.py b/store/@{FutureOSS}/auto-dependency/PL/main.py new file mode 100644 index 0000000..fe4b2de --- /dev/null +++ b/store/@{FutureOSS}/auto-dependency/PL/main.py @@ -0,0 +1,77 @@ +"""PL 注入 - 向插件加载器注册依赖自动安装功能 + +此文件通过 PL 注入机制向插件加载器注册以下功能: +- auto-dependency:scan: 扫描所有插件的系统依赖声明 +- auto-dependency:check: 检查系统依赖是否已安装 +- auto-dependency:install: 自动安装缺失的系统依赖 +- auto-dependency:info: 获取插件系统信息 +""" + + +def register(injector): + """向插件加载器注册功能 + + Args: + injector: PLInjector 实例,提供 register_function 等方法 + """ + # 注意:实际的功能实现由 main.py 中的 AutoDependencyPlugin 提供 + # 这里我们通过导入插件实例来注册功能 + + import sys + from pathlib import Path + + # 获取当前插件目录 + current_file = Path(__file__) + plugin_dir = current_file.parent.parent + + # 导入插件主模块 + main_file = plugin_dir / "main.py" + + # 创建安全的执行环境来加载插件 + safe_globals = { + "__builtins__": { + "True": True, "False": False, "None": None, + "dict": dict, "list": list, "str": str, "int": int, + "float": float, "bool": bool, "tuple": tuple, "set": set, + "len": len, "range": range, "enumerate": enumerate, + "zip": zip, "map": map, "filter": filter, + "sorted": sorted, "reversed": reversed, + "min": min, "max": max, "sum": sum, "abs": abs, + "round": round, "isinstance": isinstance, "issubclass": issubclass, + "type": type, "id": id, "hash": hash, "repr": repr, + "print": print, "object": object, "property": property, + "staticmethod": staticmethod, "classmethod": classmethod, + "super": super, "iter": iter, "next": next, + "any": any, "all": all, "callable": callable, + "hasattr": hasattr, "getattr": getattr, "setattr": setattr, + "Exception": Exception, "BaseException": BaseException, + }, + "__name__": "plugin.auto-dependency", + "__package__": "plugin.auto-dependency", + "__file__": str(main_file), + "Path": Path, + } + + try: + with open(main_file, "r", encoding="utf-8") as f: + source = f.read() + + code = compile(source, str(main_file), "exec") + exec(code, safe_globals) + + # 获取 New 函数并创建插件实例 + new_func = safe_globals.get("New") + if new_func and callable(new_func): + plugin_instance = new_func() + + # 初始化插件 + plugin_instance.init({ + "scan_dirs": ["store"], + "auto_install": True + }) + + # 使用插件实例注册 PL 功能 + plugin_instance.register_pl_functions(injector) + + except Exception as e: + print(f"[auto-dependency] PL 注册失败:{e}") diff --git a/store/@{FutureOSS}/auto-dependency/README.md b/store/@{FutureOSS}/auto-dependency/README.md new file mode 100644 index 0000000..838c7cf --- /dev/null +++ b/store/@{FutureOSS}/auto-dependency/README.md @@ -0,0 +1,117 @@ +# 依赖自动安装插件 (auto-dependency) + +## 概述 + +依赖自动安装插件是一个核心系统插件,用于扫描所有插件的声明文件,检查并自动安装系统依赖。 + +## 功能特性 + +1. **扫描插件声明** - 自动扫描所有插件目录下的 `manifest.json` 文件 +2. **系统依赖检测** - 读取每个插件声明的系统依赖 (`system_dependencies` 字段) +3. **安装状态检查** - 检查这些系统依赖是否已在系统中安装 +4. **自动安装** - 对于未安装的依赖,使用系统包管理器自动安装 +5. **PL 注入接口** - 通过 PL 注入机制向插件加载器注册功能接口 + +## 使用方法 + +### 在 manifest.json 中声明系统依赖 + +其他插件可以在自己的 `manifest.json` 中声明所需的系统依赖: + +```json +{ + "metadata": { + "name": "my-plugin", + "version": "1.0.0", + "author": "MyName", + "description": "我的插件" + }, + "config": { + "enabled": true, + "args": {} + }, + "dependencies": ["plugin-loader"], + "system_dependencies": ["curl", "git", "wget"], + "permissions": [] +} +``` + +### 通过 PL 注入接口调用 + +插件加载器加载此插件后,可以通过以下 PL 注入接口进行操作: + +| 接口名称 | 说明 | 参数 | 返回值 | +|---------|------|------|--------| +| `auto-dependency:scan` | 扫描所有插件的声明文件 | `scan_dir` (可选,默认 "store") | 插件信息列表 | +| `auto-dependency:check` | 检查系统依赖安装状态 | `scan_dir` (可选,默认 "store") | 检查结果字典 | +| `auto-dependency:install` | 安装缺失的系统依赖 | `scan_dir` (可选,默认 "store") | 安装结果字典 | +| `auto-dependency:info` | 获取插件系统信息 | 无 | 系统信息字典 | + +### 示例代码 + +```python +# 获取插件加载器中的 auto-dependency 功能 +injector = get_pl_injector() # 从插件加载器获取 + +# 扫描所有插件的系统依赖声明 +plugins = injector.get_injected_functions("auto-dependency:scan")[0]() +print(f"找到 {len(plugins)} 个插件") + +# 检查依赖安装状态 +result = injector.get_injected_functions("auto-dependency:check")[0]() +print(f"已安装:{result['installed_count']}, 缺失:{result['missing_count']}") + +# 安装缺失的依赖 +install_result = injector.get_injected_functions("auto-dependency:install")[0]() +print(f"成功安装:{install_result['success_count']}, 失败:{install_result['failed_count']}") +``` + +## 支持的包管理器 + +插件自动检测系统使用的包管理器,支持: + +- **Debian/Ubuntu**: apt-get, apt +- **RHEL/CentOS**: yum, dnf +- **Arch Linux**: pacman +- **macOS**: brew +- **Alpine Linux**: apk + +## 配置选项 + +在 `manifest.json` 的 `config.args` 中可以配置: + +```json +{ + "config": { + "enabled": true, + "args": { + "scan_dirs": ["store"], + "package_manager": "auto", + "auto_install": true + } + } +} +``` + +| 配置项 | 说明 | 默认值 | +|-------|------|--------| +| `scan_dirs` | 要扫描的目录列表 | `["store"]` | +| `package_manager` | 包管理器(auto 为自动检测) | `"auto"` | +| `auto_install` | 是否自动安装缺失的依赖 | `true` | + +## 安全说明 + +- 插件需要 `*` 权限才能执行系统命令安装包 +- 包安装操作有超时限制(300 秒) +- 所有安装操作都会记录日志 + +## 文件结构 + +``` +store/@{FutureOSS}/auto-dependency/ +├── manifest.json # 插件清单 +├── main.py # 主逻辑实现 +├── PL/ +│ └── main.py # PL 注入入口 +└── README.md # 本文档 +``` diff --git a/store/@{FutureOSS}/auto-dependency/main.py b/store/@{FutureOSS}/auto-dependency/main.py new file mode 100644 index 0000000..a9b7088 --- /dev/null +++ b/store/@{FutureOSS}/auto-dependency/main.py @@ -0,0 +1,407 @@ +"""依赖自动安装插件 - 扫描所有插件的声明文件,检查并安装系统依赖 + +功能说明: +1. 扫描所有插件目录下的 manifest.json 文件 +2. 读取每个插件声明的系统依赖 (system_dependencies 字段) +3. 检查这些系统依赖是否已安装 +4. 对于未安装的依赖,使用系统包管理器自动安装 +5. 通过 PL 注入机制向插件加载器注册功能接口 +""" +import subprocess +import shutil +import json +from pathlib import Path +from typing import Any, Optional, List, Dict +from oss.plugin.types import Plugin + + +class SystemDependencyChecker: + """系统依赖检查器""" + + def __init__(self): + self.package_managers = { + "apt": ["apt-get", "apt"], + "yum": ["yum", "dnf"], + "pacman": ["pacman"], + "brew": ["brew"], + "apk": ["apk"], + } + self.detected_pm = self._detect_package_manager() + + def _detect_package_manager(self) -> str: + """检测系统包管理器""" + for pm, commands in self.package_managers.items(): + for cmd in commands: + if shutil.which(cmd): + return pm + return "unknown" + + def check_command(self, command: str) -> bool: + """检查命令是否可用""" + return shutil.which(command) is not None + + def check_package(self, package: str) -> bool: + """检查系统包是否已安装""" + if not self.detected_pm or self.detected_pm == "unknown": + return False + + try: + if self.detected_pm in ["apt", "apt-get"]: + result = subprocess.run( + ["dpkg", "-l", package], + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode == 0 and "ii" in result.stdout + elif self.detected_pm in ["yum", "dnf"]: + result = subprocess.run( + ["rpm", "-q", package], + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode == 0 + elif self.detected_pm == "pacman": + result = subprocess.run( + ["pacman", "-Q", package], + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode == 0 + elif self.detected_pm == "brew": + result = subprocess.run( + ["brew", "list", package], + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode == 0 + elif self.detected_pm == "apk": + result = subprocess.run( + ["apk", "info", "-e", package], + capture_output=True, + text=True, + timeout=30 + ) + return result.returncode == 0 + except Exception: + pass + return False + + def install_package(self, package: str) -> bool: + """安装系统包""" + if not self.detected_pm or self.detected_pm == "unknown": + return False + + try: + if self.detected_pm in ["apt", "apt-get"]: + result = subprocess.run( + ["apt-get", "install", "-y", package], + capture_output=True, + text=True, + timeout=300 + ) + return result.returncode == 0 + elif self.detected_pm == "yum": + result = subprocess.run( + ["yum", "install", "-y", package], + capture_output=True, + text=True, + timeout=300 + ) + return result.returncode == 0 + elif self.detected_pm == "dnf": + result = subprocess.run( + ["dnf", "install", "-y", package], + capture_output=True, + text=True, + timeout=300 + ) + return result.returncode == 0 + elif self.detected_pm == "pacman": + result = subprocess.run( + ["pacman", "-S", "--noconfirm", package], + capture_output=True, + text=True, + timeout=300 + ) + return result.returncode == 0 + elif self.detected_pm == "brew": + result = subprocess.run( + ["brew", "install", package], + capture_output=True, + text=True, + timeout=300 + ) + return result.returncode == 0 + elif self.detected_pm == "apk": + result = subprocess.run( + ["apk", "add", package], + capture_output=True, + text=True, + timeout=300 + ) + return result.returncode == 0 + except Exception: + pass + return False + + def check_and_install(self, package: str, auto_install: bool = True) -> Dict[str, Any]: + """检查并安装包""" + result = { + "package": package, + "installed": self.check_package(package), + "action": "none", + "success": True, + "message": "" + } + + if result["installed"]: + result["message"] = f"包 '{package}' 已安装" + return result + + if not auto_install: + result["action"] = "skipped" + result["message"] = f"包 '{package}' 未安装,但自动安装已禁用" + result["success"] = False + return result + + result["action"] = "installing" + if self.install_package(package): + result["installed"] = True + result["success"] = True + result["message"] = f"包 '{package}' 安装成功" + else: + result["success"] = False + result["message"] = f"包 '{package}' 安装失败" + + return result + + +class AutoDependencyPlugin(Plugin): + """依赖自动安装插件""" + + def __init__(self): + self.checker = SystemDependencyChecker() + self.scan_dirs: List[str] = [] + self.auto_install: bool = True + self._plugin_loader_ref: Optional[Any] = None + + def init(self, deps: Optional[Dict[str, Any]] = None): + """初始化插件""" + if deps: + self.scan_dirs = deps.get("scan_dirs", ["store"]) + self.auto_install = deps.get("auto_install", True) + + # 获取插件加载器引用(通过依赖注入) + if "plugin-loader" in deps: + self._plugin_loader_ref = deps["plugin-loader"] + + def start(self): + """启动插件""" + pass + + def stop(self): + """停止插件""" + pass + + def scan_plugin_manifests(self, base_dir: str = "store") -> List[Dict[str, Any]]: + """扫描所有插件的 manifest.json 文件 + + Returns: + 包含所有插件信息的列表,每个元素包含: + - plugin_name: 插件名称 + - plugin_dir: 插件目录路径 + - manifest: manifest.json 内容 + - system_dependencies: 系统依赖列表 + """ + results = [] + base_path = Path(base_dir) + + if not base_path.exists(): + return results + + # 扫描所有插件目录 + for vendor_dir in base_path.iterdir(): + if not vendor_dir.is_dir(): + continue + + for plugin_dir in vendor_dir.iterdir(): + if not plugin_dir.is_dir(): + continue + + manifest_file = plugin_dir / "manifest.json" + if not manifest_file.exists(): + continue + + try: + with open(manifest_file, "r", encoding="utf-8") as f: + manifest = json.load(f) + + # 提取系统依赖 + system_deps = manifest.get("system_dependencies", []) + + results.append({ + "plugin_name": plugin_dir.name.rstrip("}"), + "plugin_dir": str(plugin_dir), + "manifest": manifest, + "system_dependencies": system_deps + }) + except Exception: + continue + + return results + + def check_all_dependencies(self, base_dir: str = "store") -> Dict[str, Any]: + """检查所有插件的系统依赖 + + Args: + base_dir: 基础扫描目录 + + Returns: + 检查结果字典,包含: + - total_plugins: 扫描的插件总数 + - plugins_with_deps: 有系统依赖的插件数 + - dependencies: 依赖检查结果列表 + - missing_count: 缺失的依赖数量 + - installed_count: 已安装的依赖数量 + """ + plugins = self.scan_plugin_manifests(base_dir) + + all_deps = {} # {package: [plugin_names]} + for plugin in plugins: + for dep in plugin["system_dependencies"]: + if dep not in all_deps: + all_deps[dep] = [] + all_deps[dep].append(plugin["plugin_name"]) + + results = [] + installed_count = 0 + missing_count = 0 + + for package, plugin_names in all_deps.items(): + is_installed = self.checker.check_package(package) + if is_installed: + installed_count += 1 + else: + missing_count += 1 + + results.append({ + "package": package, + "installed": is_installed, + "required_by": plugin_names + }) + + return { + "total_plugins": len(plugins), + "plugins_with_deps": sum(1 for p in plugins if p["system_dependencies"]), + "dependencies": results, + "missing_count": missing_count, + "installed_count": installed_count + } + + def install_missing_dependencies(self, base_dir: str = "store") -> Dict[str, Any]: + """安装所有缺失的系统依赖 + + Args: + base_dir: 基础扫描目录 + + Returns: + 安装结果字典,包含: + - total_to_install: 需要安装的包数量 + - success_count: 成功安装的包数量 + - failed_count: 安装失败的包数量 + - results: 每个包的安装结果 + """ + check_result = self.check_all_dependencies(base_dir) + + to_install = [dep for dep in check_result["dependencies"] if not dep["installed"]] + + install_results = [] + success_count = 0 + failed_count = 0 + + for dep in to_install: + result = self.checker.check_and_install(dep["package"], auto_install=True) + result["required_by"] = dep["required_by"] + install_results.append(result) + + if result["success"]: + success_count += 1 + else: + failed_count += 1 + + return { + "total_to_install": len(to_install), + "success_count": success_count, + "failed_count": failed_count, + "results": install_results + } + + def get_system_info(self) -> Dict[str, Any]: + """获取系统信息""" + return { + "package_manager": self.checker.detected_pm, + "auto_install_enabled": self.auto_install, + "scan_directories": self.scan_dirs + } + + def register_pl_functions(self, injector: Any): + """注册 PL 注入功能 + + 通过 PL 注入机制向插件加载器注册以下功能: + - auto-dependency:scan: 扫描所有插件的系统依赖 + - auto-dependency:check: 检查依赖安装状态 + - auto-dependency:install: 安装缺失的依赖 + - auto-dependency:info: 获取插件系统信息 + """ + # 注册扫描功能 + def scan_deps(scan_dir: str = "store") -> Dict[str, Any]: + """扫描所有插件的声明文件""" + return self.scan_plugin_manifests(scan_dir) + + injector.register_function( + "auto-dependency:scan", + scan_deps, + "扫描所有插件的声明文件,获取系统依赖列表" + ) + + # 注册检查功能 + def check_deps(scan_dir: str = "store") -> Dict[str, Any]: + """检查所有系统依赖的安装状态""" + return self.check_all_dependencies(scan_dir) + + injector.register_function( + "auto-dependency:check", + check_deps, + "检查所有插件声明的系统依赖是否已安装" + ) + + # 注册安装功能 + def install_deps(scan_dir: str = "store") -> Dict[str, Any]: + """安装所有缺失的系统依赖""" + return self.install_missing_dependencies(scan_dir) + + injector.register_function( + "auto-dependency:install", + install_deps, + "自动安装所有缺失的系统依赖" + ) + + # 注册信息功能 + def get_info() -> Dict[str, Any]: + """获取插件系统信息""" + return self.get_system_info() + + injector.register_function( + "auto-dependency:info", + get_info, + "获取自动依赖插件的系统信息" + ) + + +def New() -> AutoDependencyPlugin: + """创建插件实例""" + return AutoDependencyPlugin() diff --git a/store/@{FutureOSS}/auto-dependency/manifest.json b/store/@{FutureOSS}/auto-dependency/manifest.json new file mode 100644 index 0000000..1319ef3 --- /dev/null +++ b/store/@{FutureOSS}/auto-dependency/manifest.json @@ -0,0 +1,20 @@ +{ + "metadata": { + "name": "auto-dependency", + "version": "1.0.0", + "author": "FutureOSS", + "description": "依赖自动安装插件 - 扫描所有插件的声明文件,检查并安装系统依赖", + "type": "core" + }, + "config": { + "enabled": true, + "args": { + "scan_dirs": ["store"], + "package_manager": "auto", + "auto_install": true, + "pl_injection": true + } + }, + "dependencies": ["plugin-loader"], + "permissions": ["*"] +} diff --git a/store/@{FutureOSS}/example-with-deps/manifest.json b/store/@{FutureOSS}/example-with-deps/manifest.json new file mode 100644 index 0000000..53093fd --- /dev/null +++ b/store/@{FutureOSS}/example-with-deps/manifest.json @@ -0,0 +1,16 @@ +{ + "metadata": { + "name": "example-with-deps", + "version": "1.0.0", + "author": "FutureOSS", + "description": "示例插件 - 演示如何声明系统依赖", + "type": "example" + }, + "config": { + "enabled": true, + "args": {} + }, + "dependencies": [], + "system_dependencies": ["curl", "git", "wget"], + "permissions": [] +}