Title: Implement minimal core framework with PL injection and update build config
Key features implemented: - Updated package metadata and dependencies in PKG-INFO, setup files - Added main.py entry point for backward compatibility with README launch method - Enhanced CLI with config options, system info command, and proper signal handling - Implemented minimal PluginManager loading only plugin-loader core plugin - Refactored PluginLoader to follow minimal core design, removed sandbox/isolation complexity - Updated auto-dependency plugin with safer PL injection mechanism and disabled pl_injection - Removed legacy plugin files (firewall, frp_proxy, ftp_server, multi_lang_deploy, ops_toolbox, security_gateway) as functionality moved to core plugin system - Improved gitignore with comprehensive ignore patterns The changes implement a minimal core framework design where only the plugin-loader is directly loaded by the core, with all other plugins managed through the PL injection mechanism, significantly simplifying the architecture.
This commit is contained in:
59
.gitignore
vendored
59
.gitignore
vendored
@@ -1,38 +1,39 @@
|
||||
```
|
||||
# Python cache files
|
||||
```gitignore
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
# Dependencies and build artifacts
|
||||
dist/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
target/
|
||||
node_modules/
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
.env.local
|
||||
*.env.*
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Logs and temporary files
|
||||
*.log
|
||||
*.tmp
|
||||
*.swp
|
||||
|
||||
# Editor/IDE files
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Coverage reports
|
||||
coverage/
|
||||
htmlcov/
|
||||
.coverage
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*
|
||||
|
||||
# MyPy cache
|
||||
.mypy_cache/
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Pytest cache
|
||||
.pytest_cache/
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
```
|
||||
10
future_oss.egg-info/PKG-INFO
Normal file
10
future_oss.egg-info/PKG-INFO
Normal file
@@ -0,0 +1,10 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: future-oss
|
||||
Version: 1.2.0
|
||||
Summary: Future OSS - 一切皆为插件的开发者工具运行时框架
|
||||
Requires-Python: >=3.10
|
||||
License-File: LICENSE
|
||||
Requires-Dist: click>=8.0
|
||||
Requires-Dist: pyyaml>=6.0
|
||||
Requires-Dist: websockets>=12.0
|
||||
Dynamic: license-file
|
||||
27
future_oss.egg-info/SOURCES.txt
Normal file
27
future_oss.egg-info/SOURCES.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
LICENSE
|
||||
README.md
|
||||
pyproject.toml
|
||||
future_oss.egg-info/PKG-INFO
|
||||
future_oss.egg-info/SOURCES.txt
|
||||
future_oss.egg-info/dependency_links.txt
|
||||
future_oss.egg-info/entry_points.txt
|
||||
future_oss.egg-info/requires.txt
|
||||
future_oss.egg-info/top_level.txt
|
||||
oss/__init__.py
|
||||
oss/cli.py
|
||||
oss/core/context.py
|
||||
oss/logger/logger.py
|
||||
oss/plugin/base.py
|
||||
oss/plugin/capabilities.py
|
||||
oss/plugin/loader.py
|
||||
oss/plugin/manager.py
|
||||
oss/plugin/types.py
|
||||
oss/plugins/auto_dependency.py
|
||||
oss/plugins/firewall.py
|
||||
oss/plugins/frp_proxy.py
|
||||
oss/plugins/ftp_server.py
|
||||
oss/plugins/multi_lang_deploy.py
|
||||
oss/plugins/ops_toolbox.py
|
||||
oss/plugins/security_gateway.py
|
||||
oss/shared/__init__.py
|
||||
oss/shared/router.py
|
||||
1
future_oss.egg-info/dependency_links.txt
Normal file
1
future_oss.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
future_oss.egg-info/entry_points.txt
Normal file
2
future_oss.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
oss = oss.cli:main
|
||||
3
future_oss.egg-info/requires.txt
Normal file
3
future_oss.egg-info/requires.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
click>=8.0
|
||||
pyyaml>=6.0
|
||||
websockets>=12.0
|
||||
1
future_oss.egg-info/top_level.txt
Normal file
1
future_oss.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
oss
|
||||
18
main.py
Normal file
18
main.py
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""FutureOSS 主入口 - 兼容旧版启动方式
|
||||
|
||||
此文件用于兼容 README 中描述的 `python main.py` 启动方式。
|
||||
推荐使用 `oss serve` 命令启动。
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 确保 workspace 在 Python 路径中
|
||||
workspace_dir = Path(__file__).parent.resolve()
|
||||
if str(workspace_dir) not in sys.path:
|
||||
sys.path.insert(0, str(workspace_dir))
|
||||
|
||||
from oss.cli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
oss.config.json
Normal file
10
oss.config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"HTTP_API_PORT": 8080,
|
||||
"HTTP_TCP_PORT": 8082,
|
||||
"HOST": "0.0.0.0",
|
||||
"DATA_DIR": "./data",
|
||||
"STORE_DIR": "./store",
|
||||
"LOG_LEVEL": "INFO",
|
||||
"PERMISSION_CHECK": true,
|
||||
"MAX_WORKERS": 4
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
"""Future OSS"""
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.2.0"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
45
oss/cli.py
45
oss/cli.py
@@ -1,23 +1,46 @@
|
||||
"""CLI 入口"""
|
||||
import click
|
||||
import signal
|
||||
import os
|
||||
|
||||
from oss import __version__
|
||||
from oss.logger.logger import Logger
|
||||
from oss.plugin.manager import PluginManager
|
||||
from oss.config import init_config, get_config
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
@click.option('--config', '-c', type=str, help='配置文件路径')
|
||||
@click.pass_context
|
||||
def cli(ctx, config):
|
||||
"""Future OSS - 一切皆为插件"""
|
||||
pass
|
||||
# 初始化配置
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['config'] = init_config(config)
|
||||
|
||||
|
||||
@cli.command()
|
||||
def serve():
|
||||
@click.option('--host', type=str, default=None, help='监听地址')
|
||||
@click.option('--port', type=int, default=None, help='HTTP API 端口')
|
||||
@click.option('--tcp-port', type=int, default=None, help='HTTP TCP 端口')
|
||||
@click.pass_context
|
||||
def serve(ctx, host, port, tcp_port):
|
||||
"""启动 Future OSS"""
|
||||
config = ctx.obj.get('config', get_config())
|
||||
|
||||
# 命令行参数覆盖配置
|
||||
if host:
|
||||
config.set('HOST', host)
|
||||
if port:
|
||||
config.set('HTTP_API_PORT', port)
|
||||
if tcp_port:
|
||||
config.set('HTTP_TCP_PORT', tcp_port)
|
||||
|
||||
log = Logger()
|
||||
log.info(f"Future OSS {__version__} 启动")
|
||||
log.info(f"监听地址:{config.host}:{config.http_api_port}")
|
||||
log.info(f"数据目录:{config.data_dir.absolute()}")
|
||||
log.info(f"插件仓库:{config.store_dir.absolute()}")
|
||||
|
||||
plugin_mgr = PluginManager()
|
||||
plugin_mgr.load()
|
||||
@@ -48,6 +71,22 @@ def version():
|
||||
click.echo(f"Future OSS {__version__}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def info(ctx):
|
||||
"""显示系统信息"""
|
||||
config = ctx.obj.get('config', get_config())
|
||||
click.echo(f"Future OSS {__version__}")
|
||||
click.echo(f"配置文件:{config._config_file or '无'}")
|
||||
click.echo(f"HTTP API 端口:{config.http_api_port}")
|
||||
click.echo(f"HTTP TCP 端口:{config.http_tcp_port}")
|
||||
click.echo(f"主机地址:{config.host}")
|
||||
click.echo(f"数据目录:{config.data_dir.absolute()}")
|
||||
click.echo(f"插件仓库:{config.store_dir.absolute()}")
|
||||
click.echo(f"日志级别:{config.log_level}")
|
||||
click.echo(f"权限检查:{'启用' if config.permission_check else '禁用'}")
|
||||
|
||||
|
||||
def main():
|
||||
cli()
|
||||
|
||||
|
||||
4
oss/config/__init__.py
Normal file
4
oss/config/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""配置模块"""
|
||||
from oss.config.config import Config, get_config, init_config
|
||||
|
||||
__all__ = ["Config", "get_config", "init_config"]
|
||||
144
oss/config/config.py
Normal file
144
oss/config/config.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""配置管理 - 支持环境变量和配置文件"""
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class Config:
|
||||
"""全局配置管理
|
||||
|
||||
优先级:环境变量 > 配置文件 > 默认值
|
||||
"""
|
||||
|
||||
DEFAULTS = {
|
||||
# 服务器配置
|
||||
"HTTP_API_PORT": 8080,
|
||||
"HTTP_TCP_PORT": 8082,
|
||||
"HOST": "0.0.0.0",
|
||||
|
||||
# 数据目录
|
||||
"DATA_DIR": "./data",
|
||||
"PLUGIN_STORAGE_DIR": "./data/plugin-storage",
|
||||
"SIGNATURE_KEYS_DIR": "./data/signature-verifier/keys",
|
||||
"DCIM_DIR": "./data/DCIM",
|
||||
"WEB_TOOLKIT_DIR": "./data/web-toolkit",
|
||||
"HTML_RENDER_DIR": "./data/html-render",
|
||||
|
||||
# 插件配置
|
||||
"STORE_DIR": "./store",
|
||||
"PLUGINS_DIR": "./oss/plugins",
|
||||
|
||||
# 日志配置
|
||||
"LOG_LEVEL": "INFO",
|
||||
"LOG_FORMAT": "text", # text 或 json
|
||||
|
||||
# 安全配置
|
||||
"PERMISSION_CHECK": True,
|
||||
"ENFORCE_SIGNATURE": True,
|
||||
|
||||
# 性能配置
|
||||
"MAX_WORKERS": 4,
|
||||
"ENABLE_ASYNC": False,
|
||||
}
|
||||
|
||||
def __init__(self, config_file: Optional[str] = None):
|
||||
self._config: dict[str, Any] = dict(self.DEFAULTS)
|
||||
self._config_file = config_file
|
||||
|
||||
# 加载配置文件
|
||||
if config_file:
|
||||
self._load_from_file(config_file)
|
||||
|
||||
# 环境变量覆盖
|
||||
self._load_from_env()
|
||||
|
||||
def _load_from_file(self, path: str):
|
||||
"""从 JSON 配置文件加载"""
|
||||
config_path = Path(path)
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
file_config = json.load(f)
|
||||
for key, value in file_config.items():
|
||||
if key in self.DEFAULTS:
|
||||
self._config[key] = value
|
||||
except Exception as e:
|
||||
print(f"[Config] 加载配置文件失败:{e}")
|
||||
|
||||
def _load_from_env(self):
|
||||
"""从环境变量加载"""
|
||||
for key in self.DEFAULTS.keys():
|
||||
env_value = os.environ.get(key)
|
||||
if env_value is not None:
|
||||
# 类型转换
|
||||
default_value = self.DEFAULTS[key]
|
||||
if isinstance(default_value, bool):
|
||||
self._config[key] = env_value.lower() in ('true', '1', 'yes')
|
||||
elif isinstance(default_value, int):
|
||||
try:
|
||||
self._config[key] = int(env_value)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self._config[key] = env_value
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""获取配置值"""
|
||||
return self._config.get(key, default)
|
||||
|
||||
def set(self, key: str, value: Any):
|
||||
"""设置配置值"""
|
||||
if key in self.DEFAULTS:
|
||||
self._config[key] = value
|
||||
|
||||
def all(self) -> dict[str, Any]:
|
||||
"""获取所有配置"""
|
||||
return dict(self._config)
|
||||
|
||||
@property
|
||||
def http_api_port(self) -> int:
|
||||
return int(self._config["HTTP_API_PORT"])
|
||||
|
||||
@property
|
||||
def http_tcp_port(self) -> int:
|
||||
return int(self._config["HTTP_TCP_PORT"])
|
||||
|
||||
@property
|
||||
def host(self) -> str:
|
||||
return str(self._config["HOST"])
|
||||
|
||||
@property
|
||||
def data_dir(self) -> Path:
|
||||
return Path(self._config["DATA_DIR"])
|
||||
|
||||
@property
|
||||
def store_dir(self) -> Path:
|
||||
return Path(self._config["STORE_DIR"])
|
||||
|
||||
@property
|
||||
def log_level(self) -> str:
|
||||
return str(self._config["LOG_LEVEL"])
|
||||
|
||||
@property
|
||||
def permission_check(self) -> bool:
|
||||
return bool(self._config["PERMISSION_CHECK"])
|
||||
|
||||
|
||||
# 全局配置实例
|
||||
_global_config: Optional[Config] = None
|
||||
|
||||
|
||||
def get_config() -> Config:
|
||||
"""获取全局配置实例"""
|
||||
global _global_config
|
||||
if _global_config is None:
|
||||
_global_config = Config()
|
||||
return _global_config
|
||||
|
||||
|
||||
def init_config(config_file: Optional[str] = None) -> Config:
|
||||
"""初始化全局配置"""
|
||||
global _global_config
|
||||
_global_config = Config(config_file)
|
||||
return _global_config
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,212 +1,58 @@
|
||||
"""插件加载器 - 使用进程隔离确保安全性"""
|
||||
"""插件加载器 - 专门用于加载核心插件
|
||||
|
||||
遵循「最小化核心框架」设计哲学:
|
||||
- 只负责加载可信的核心插件(来自 store/@{FutureOSS}/)
|
||||
- 所有插件都使用统一的加载机制
|
||||
- 不再区分沙箱模式和非沙箱模式
|
||||
"""
|
||||
import sys
|
||||
import importlib.util
|
||||
import multiprocessing
|
||||
from multiprocessing import Process, Pipe
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Dict
|
||||
import signal
|
||||
import os
|
||||
from multiprocessing.connection import Connection
|
||||
|
||||
from oss.plugin.types import Plugin
|
||||
from oss.config import get_config
|
||||
|
||||
|
||||
class Sandbox:
|
||||
"""沙箱隔离(已废弃,仅保留向后兼容)
|
||||
class PluginLoader:
|
||||
"""插件加载器 - 专门用于加载核心插件
|
||||
|
||||
注意:此沙箱已被证明不安全,存在逃逸漏洞。
|
||||
请使用 ProcessIsolatedLoader 进行安全的插件加载。
|
||||
遵循「最小化核心框架」设计哲学:
|
||||
- 只负责加载可信的核心插件(来自 store/@{FutureOSS}/)
|
||||
- 所有插件都使用统一的加载机制
|
||||
- 不再区分沙箱模式和非沙箱模式
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._restricted_builtins = {
|
||||
"__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,
|
||||
}
|
||||
}
|
||||
self.loaded: dict[str, Any] = {}
|
||||
self._config = get_config()
|
||||
|
||||
def get_safe_globals(self) -> dict:
|
||||
"""获取安全的 globals"""
|
||||
return dict(self._restricted_builtins)
|
||||
|
||||
|
||||
def _run_plugin_in_process(plugin_path: str, conn: Connection, timeout: float = 5.0):
|
||||
"""在独立进程中运行插件代码"""
|
||||
try:
|
||||
# 设置超时处理
|
||||
def timeout_handler(signum, frame):
|
||||
raise TimeoutError(f"Plugin execution timed out after {timeout}s")
|
||||
|
||||
signal.signal(signal.SIGALRM, timeout_handler)
|
||||
signal.alarm(int(timeout))
|
||||
|
||||
plugin_dir = Path(plugin_path)
|
||||
if not (plugin_dir / "main.py").exists():
|
||||
conn.send(("error", "Plugin main.py not found"))
|
||||
signal.alarm(0)
|
||||
return
|
||||
|
||||
# 加载模块
|
||||
module_name = f"sandbox_plugin_{os.getpid()}"
|
||||
spec = importlib.util.spec_from_file_location(module_name, str(plugin_dir / "main.py"))
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
module.__package__ = module_name
|
||||
module.__path__ = [str(plugin_dir)]
|
||||
sys.modules[spec.name] = module
|
||||
|
||||
# 执行模块
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# 检查是否有 New 函数
|
||||
if not hasattr(module, "New"):
|
||||
conn.send(("error", "Plugin missing 'New' function"))
|
||||
signal.alarm(0)
|
||||
return
|
||||
|
||||
# 创建实例
|
||||
instance = module.New()
|
||||
|
||||
# 返回成功结果(只返回基本信息,不返回可执行对象)
|
||||
conn.send(("success", {
|
||||
"name": plugin_dir.name.rstrip("}"),
|
||||
"path": str(plugin_dir),
|
||||
"has_new": True
|
||||
}))
|
||||
|
||||
signal.alarm(0)
|
||||
|
||||
except Exception as e:
|
||||
conn.send(("error", str(e)))
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
conn.close()
|
||||
|
||||
|
||||
class ProcessIsolatedLoader:
|
||||
"""进程隔离插件加载器
|
||||
|
||||
使用独立的子进程加载和运行第三方插件,确保即使插件恶意代码也无法影响主进程。
|
||||
"""
|
||||
|
||||
def __init__(self, timeout: float = 5.0):
|
||||
"""
|
||||
Args:
|
||||
timeout: 插件加载超时时间(秒)
|
||||
"""
|
||||
self.timeout = timeout
|
||||
self.loaded_plugins: Dict[str, dict] = {}
|
||||
|
||||
def load_plugin(self, plugin_dir: Path) -> Optional[dict]:
|
||||
"""在隔离进程中加载插件
|
||||
def load_core_plugin(self, plugin_name: str, store_dir: Optional[str] = None) -> Optional[dict[str, Any]]:
|
||||
"""加载核心插件(来自 store/@{FutureOSS}/)
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称(如 "plugin-loader")
|
||||
store_dir: 插件仓库目录,默认使用配置中的 STORE_DIR
|
||||
|
||||
Returns:
|
||||
插件信息字典,包含 instance、module、path、name
|
||||
"""
|
||||
if store_dir is None:
|
||||
store_dir = str(self._config.store_dir)
|
||||
plugin_dir = Path(store_dir) / "@{FutureOSS}" / plugin_name
|
||||
return self._load_plugin(plugin_name, plugin_dir)
|
||||
|
||||
def _load_plugin(self, plugin_name: str, plugin_dir: Path) -> Optional[dict[str, Any]]:
|
||||
"""加载插件(内部方法)
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
plugin_dir: 插件目录路径
|
||||
|
||||
Returns:
|
||||
插件信息字典,如果加载失败则返回 None
|
||||
"""
|
||||
parent_conn, child_conn = Pipe()
|
||||
|
||||
# 创建子进程
|
||||
process = Process(
|
||||
target=_run_plugin_in_process,
|
||||
args=(str(plugin_dir), child_conn, self.timeout),
|
||||
daemon=True
|
||||
)
|
||||
|
||||
process.start()
|
||||
process.join(timeout=self.timeout + 2) # 额外给 2 秒清理时间
|
||||
|
||||
# 处理超时
|
||||
if process.is_alive():
|
||||
process.terminate()
|
||||
process.join(timeout=1)
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
process.join(timeout=1)
|
||||
return None
|
||||
|
||||
# 获取结果
|
||||
try:
|
||||
if parent_conn.poll(timeout=1):
|
||||
status, result = parent_conn.recv()
|
||||
|
||||
if status == "success":
|
||||
plugin_name = result["name"]
|
||||
self.loaded_plugins[plugin_name] = {
|
||||
"instance": None, # 不保存实际实例,避免跨进程问题
|
||||
"module": None,
|
||||
"path": result["path"],
|
||||
"name": plugin_name,
|
||||
"isolated": True
|
||||
}
|
||||
return self.loaded_plugins[plugin_name]
|
||||
else:
|
||||
# 记录错误但不抛出异常
|
||||
print(f"Plugin load error: {result}")
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Failed to receive plugin result: {e}")
|
||||
return None
|
||||
finally:
|
||||
parent_conn.close()
|
||||
|
||||
|
||||
class PluginLoader:
|
||||
"""插件加载器(混合模式:核心插件直接加载,第三方插件进程隔离)"""
|
||||
|
||||
def __init__(self, enable_sandbox: bool = True, isolation_timeout: float = 5.0):
|
||||
self.loaded: dict[str, Any] = {}
|
||||
self.sandbox = Sandbox() if enable_sandbox else None
|
||||
# 新增:进程隔离加载器用于第三方插件
|
||||
self.isolated_loader = ProcessIsolatedLoader(timeout=isolation_timeout)
|
||||
|
||||
def load_core_plugin(self, plugin_name: str, store_dir: str = "store") -> Optional[dict[str, Any]]:
|
||||
"""加载核心插件(不受沙箱限制,可信插件)"""
|
||||
plugin_dir = Path(store_dir) / "@{FutureOSS}" / plugin_name
|
||||
return self._load_plugin(plugin_name, plugin_dir, use_sandbox=False, allow_relative=True)
|
||||
|
||||
def load_sandbox_plugin(self, plugin_dir: Path) -> Optional[dict[str, Any]]:
|
||||
"""加载第三方插件(使用进程隔离确保安全)"""
|
||||
# 使用进程隔离代替不安全的沙箱
|
||||
return self.isolated_loader.load_plugin(plugin_dir)
|
||||
|
||||
def _load_plugin(self, plugin_name: str, plugin_dir: Path, use_sandbox: bool = True, allow_relative: bool = False) -> Optional[dict[str, Any]]:
|
||||
"""加载插件(内部方法,用于核心插件)"""
|
||||
if not (plugin_dir / "main.py").exists():
|
||||
print(f"[PluginLoader] 插件不存在:{plugin_dir}")
|
||||
return None
|
||||
|
||||
# 清理插件名(去掉 } 等)
|
||||
@@ -218,34 +64,42 @@ class PluginLoader:
|
||||
module.__path__ = [str(plugin_dir)] # 启用相对导入子模块
|
||||
sys.modules[spec.name] = module
|
||||
|
||||
# 沙箱模式:限制内置函数(仅用于核心插件的额外保护)
|
||||
if use_sandbox and self.sandbox:
|
||||
safe_globals = self.sandbox.get_safe_globals()
|
||||
# 允许导入框架模块
|
||||
safe_globals["__builtins__"]["__import__"] = self._safe_import
|
||||
# 执行模块(核心插件是可信的,不需要沙箱)
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
else:
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
if not hasattr(module, "New"):
|
||||
except SyntaxError as e:
|
||||
print(f"[PluginLoader] 插件 {clean_name} 语法错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
except ImportError as e:
|
||||
print(f"[PluginLoader] 插件 {clean_name} 导入错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[PluginLoader] 加载插件 {clean_name} 失败:{type(e).__name__}: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
if not hasattr(module, "New"):
|
||||
print(f"[PluginLoader] 插件 {clean_name} 缺少 New() 函数")
|
||||
return None
|
||||
|
||||
try:
|
||||
instance = module.New()
|
||||
except TypeError as e:
|
||||
print(f"[PluginLoader] 创建插件 {clean_name} 实例失败:参数错误 - {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[PluginLoader] 创建插件 {clean_name} 实例失败:{type(e).__name__}: {e}")
|
||||
return None
|
||||
|
||||
instance = module.New()
|
||||
self.loaded[clean_name] = {
|
||||
"instance": instance,
|
||||
"module": module,
|
||||
"path": str(plugin_dir),
|
||||
"name": clean_name, # 存储清理后的名称
|
||||
"name": clean_name,
|
||||
}
|
||||
return self.loaded[clean_name]
|
||||
|
||||
@staticmethod
|
||||
def _safe_import(name: str, globals: dict = None, locals: dict = None, fromlist: tuple = (), level: int = 0):
|
||||
"""安全导入:只允许导入框架模块、标准库子集和插件自身模块"""
|
||||
allowed_prefixes = ("oss.", "json.", "time.", "datetime.", "enum.", "typing.", "dataclasses.", "pathlib.", "mimetypes.", "http.", "threading.", "socket.", "asyncio.", "websockets.", "re.", "urllib.", "shutil.", "string.", "io.", "base64.", "hashlib.", "hmac.", "secrets.", "math.", "random.", "collections.", "functools.", "itertools.", "operator.", "copy.", "pprint.", "textwrap.", "unicodedata.", "struct.", "codecs.", "locale.", "gettext.", "logging.", "warnings.", "contextlib.", "abc.", "atexit.", "traceback.", "linecache.", "tokenize.", "keyword.", "ast.", "dis.", "inspect.", "types.", "__future__.", "importlib.", "pkgutil.", "sys.", "os.", "stat.", "glob.", "tempfile.", "fnmatch.", "csv.", "configparser.", "argparse.", "html.", "xml.", "email.", "mailbox.", "mimetypes.", "binascii.", "zlib.", "gzip.", "bz2.", "lzma.", "zipfile.", "tarfile.", "sqlite3.", "zlib.")
|
||||
if any(name.startswith(p) for p in allowed_prefixes):
|
||||
return __import__(name, globals, locals, fromlist, level)
|
||||
# 允许相对导入(插件自身模块)
|
||||
if level > 0:
|
||||
return __import__(name, globals, locals, fromlist, level)
|
||||
raise ImportError(f"插件不允许导入模块:{name}")
|
||||
|
||||
@@ -1,33 +1,52 @@
|
||||
"""插件管理器 - 只加载 plugin-loader"""
|
||||
"""插件管理器 - 只加载 plugin-loader,其他所有插件由 plugin-loader 插件自行管理"""
|
||||
from typing import Any, Optional
|
||||
|
||||
from oss.plugin.loader import PluginLoader
|
||||
|
||||
|
||||
class PluginManager:
|
||||
"""管理基础插件"""
|
||||
"""极简插件管理器
|
||||
|
||||
遵循「最小化核心框架」设计哲学:
|
||||
- 核心框架只负责加载 plugin-loader 插件
|
||||
- 所有其他插件(HTTP、WebSocket、Dashboard 等)都由 plugin-loader 插件扫描和加载
|
||||
- store/@{FutureOSS}/ 是唯一的插件来源
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.loader = PluginLoader()
|
||||
self.plugin_loader: Optional[Any] = None
|
||||
|
||||
def load(self):
|
||||
"""加载基础插件"""
|
||||
# 只加载 plugin-loader,其他都是可选的
|
||||
"""仅加载 plugin-loader 核心插件
|
||||
|
||||
plugin-loader 插件会负责:
|
||||
1. 扫描 store/@{FutureOSS}/ 目录
|
||||
2. 加载所有启用的插件
|
||||
3. 处理依赖关系
|
||||
4. 执行 PL 注入机制
|
||||
"""
|
||||
# 只加载 plugin-loader,其他所有插件都由它来管理
|
||||
pl_info = self.loader.load_core_plugin("plugin-loader")
|
||||
if pl_info:
|
||||
self.plugin_loader = pl_info["instance"]
|
||||
|
||||
def start(self):
|
||||
"""启动基础插件"""
|
||||
"""启动 plugin-loader,它会初始化并启动所有其他插件"""
|
||||
if self.plugin_loader:
|
||||
# plugin-loader.init() 会扫描并加载 store/ 中的所有插件
|
||||
self.plugin_loader.init()
|
||||
# plugin-loader.start() 会按依赖顺序启动所有插件
|
||||
self.plugin_loader.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止基础插件"""
|
||||
"""停止所有插件(由 plugin-loader 统一管理)"""
|
||||
if self.plugin_loader:
|
||||
try:
|
||||
self.plugin_loader.stop()
|
||||
except Exception:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
print("[PluginManager] 用户中断停止过程")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"[PluginManager] 停止插件时出错:{type(e).__name__}: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
Binary file not shown.
@@ -1,370 +0,0 @@
|
||||
"""
|
||||
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}")
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "firewall",
|
||||
"version": "1.0.0",
|
||||
"description": "防火墙管理插件",
|
||||
"system_dependencies": ["iptables", "ufw"],
|
||||
"package_manager": "apt-get"
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 动态防火墙插件
|
||||
功能:IP 过滤、端口管理、规则引擎、攻击检测
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import ipaddress
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Set, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.firewall")
|
||||
|
||||
class FirewallPlugin(BasePlugin):
|
||||
name = "firewall"
|
||||
version = "1.1.0"
|
||||
description = "动态防火墙:智能 IP 过滤与端口管理"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.rules_file = "./config/firewall_rules.json"
|
||||
self.whitelist: Set[str] = set()
|
||||
self.blacklist: Set[str] = set()
|
||||
self.blocked_ports: Set[int] = set()
|
||||
self.allowed_ports: Set[int] = {80, 443, 22} # 默认开放端口
|
||||
self.rate_limits: Dict[str, Dict] = {}
|
||||
self.attack_log: List[Dict] = []
|
||||
|
||||
# 加载现有规则
|
||||
self.load_rules()
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("动态防火墙已启动")
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("firewall.ip.allow", self.allow_ip)
|
||||
ctx.register_command("firewall.ip.block", self.block_ip)
|
||||
ctx.register_command("firewall.ip.list", self.list_ips)
|
||||
ctx.register_command("firewall.port.open", self.open_port)
|
||||
ctx.register_command("firewall.port.close", self.close_port)
|
||||
ctx.register_command("firewall.port.list", self.list_ports)
|
||||
ctx.register_command("firewall.rule.add", self.add_rule)
|
||||
ctx.register_command("firewall.rule.list", self.list_rules)
|
||||
ctx.register_command("firewall.attack.log", self.get_attack_log)
|
||||
|
||||
def load_rules(self):
|
||||
"""加载防火墙规则"""
|
||||
if os.path.exists(self.rules_file):
|
||||
try:
|
||||
with open(self.rules_file, "r") as f:
|
||||
rules = json.load(f)
|
||||
self.whitelist = set(rules.get("whitelist", []))
|
||||
self.blacklist = set(rules.get("blacklist", []))
|
||||
self.blocked_ports = set(rules.get("blocked_ports", []))
|
||||
self.allowed_ports = set(rules.get("allowed_ports", [80, 443, 22]))
|
||||
logger.info(f"已加载 {len(self.whitelist)} 个白名单 IP, {len(self.blacklist)} 个黑名单 IP")
|
||||
except Exception as e:
|
||||
logger.error(f"加载防火墙规则失败:{e}")
|
||||
|
||||
def save_rules(self):
|
||||
"""保存防火墙规则"""
|
||||
rules = {
|
||||
"whitelist": list(self.whitelist),
|
||||
"blacklist": list(self.blacklist),
|
||||
"blocked_ports": list(self.blocked_ports),
|
||||
"allowed_ports": list(self.allowed_ports),
|
||||
"updated_at": datetime.now().isoformat()
|
||||
}
|
||||
os.makedirs(os.path.dirname(self.rules_file), exist_ok=True)
|
||||
with open(self.rules_file, "w") as f:
|
||||
json.dump(rules, f, indent=2)
|
||||
|
||||
def allow_ip(self, ctx: Context, ip: str):
|
||||
"""添加 IP 到白名单"""
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
self.whitelist.add(ip)
|
||||
self.blacklist.discard(ip) # 从黑名单移除
|
||||
self.save_rules()
|
||||
logger.info(f"IP {ip} 已加入白名单")
|
||||
return {"status": "success", "message": f"IP {ip} 已加入白名单"}
|
||||
except ValueError:
|
||||
return {"status": "error", "message": "无效的 IP 地址"}
|
||||
|
||||
def block_ip(self, ctx: Context, ip: str, reason: str = ""):
|
||||
"""添加 IP 到黑名单"""
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
self.blacklist.add(ip)
|
||||
self.whitelist.discard(ip) # 从白名单移除
|
||||
self.save_rules()
|
||||
|
||||
# 记录攻击日志
|
||||
self.attack_log.append({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"ip": ip,
|
||||
"action": "blocked",
|
||||
"reason": reason
|
||||
})
|
||||
|
||||
logger.warning(f"IP {ip} 已加入黑名单,原因:{reason}")
|
||||
return {"status": "success", "message": f"IP {ip} 已加入黑名单"}
|
||||
except ValueError:
|
||||
return {"status": "error", "message": "无效的 IP 地址"}
|
||||
|
||||
def list_ips(self, ctx: Context):
|
||||
"""列出所有 IP 规则"""
|
||||
return {
|
||||
"status": "success",
|
||||
"whitelist": list(self.whitelist),
|
||||
"blacklist": list(self.blacklist),
|
||||
"total": len(self.whitelist) + len(self.blacklist)
|
||||
}
|
||||
|
||||
def open_port(self, ctx: Context, port: int):
|
||||
"""开放端口"""
|
||||
if not (0 < port < 65536):
|
||||
return {"status": "error", "message": "无效端口号"}
|
||||
|
||||
self.allowed_ports.add(port)
|
||||
self.blocked_ports.discard(port)
|
||||
self.save_rules()
|
||||
logger.info(f"端口 {port} 已开放")
|
||||
return {"status": "success", "message": f"端口 {port} 已开放"}
|
||||
|
||||
def close_port(self, ctx: Context, port: int):
|
||||
"""关闭端口"""
|
||||
if not (0 < port < 65536):
|
||||
return {"status": "error", "message": "无效端口号"}
|
||||
|
||||
self.blocked_ports.add(port)
|
||||
self.allowed_ports.discard(port)
|
||||
self.save_rules()
|
||||
logger.info(f"端口 {port} 已关闭")
|
||||
return {"status": "success", "message": f"端口 {port} 已关闭"}
|
||||
|
||||
def list_ports(self, ctx: Context):
|
||||
"""列出端口规则"""
|
||||
return {
|
||||
"status": "success",
|
||||
"allowed_ports": sorted(list(self.allowed_ports)),
|
||||
"blocked_ports": sorted(list(self.blocked_ports))
|
||||
}
|
||||
|
||||
def add_rule(self, ctx: Context, rule_type: str, **kwargs):
|
||||
"""添加高级规则"""
|
||||
rule = {
|
||||
"type": rule_type,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
**kwargs
|
||||
}
|
||||
|
||||
if rule_type == "rate_limit":
|
||||
ip = kwargs.get("ip")
|
||||
limit = kwargs.get("limit", 100) # 每秒请求数
|
||||
self.rate_limits[ip] = {"limit": limit, "window": 1}
|
||||
logger.info(f"为 IP {ip} 设置限流:{limit} req/s")
|
||||
|
||||
return {"status": "success", "rule": rule}
|
||||
|
||||
def list_rules(self, ctx: Context):
|
||||
"""列出所有规则"""
|
||||
return {
|
||||
"status": "success",
|
||||
"whitelist_count": len(self.whitelist),
|
||||
"blacklist_count": len(self.blacklist),
|
||||
"allowed_ports_count": len(self.allowed_ports),
|
||||
"blocked_ports_count": len(self.blocked_ports),
|
||||
"rate_limits": self.rate_limits
|
||||
}
|
||||
|
||||
def get_attack_log(self, ctx: Context, limit: int = 50):
|
||||
"""获取攻击日志"""
|
||||
return {
|
||||
"status": "success",
|
||||
"logs": self.attack_log[-limit:],
|
||||
"total": len(self.attack_log)
|
||||
}
|
||||
|
||||
def check_ip(self, ip: str) -> bool:
|
||||
"""检查 IP 是否允许访问"""
|
||||
if ip in self.whitelist:
|
||||
return True
|
||||
if ip in self.blacklist:
|
||||
return False
|
||||
return True # 默认允许
|
||||
|
||||
def check_port(self, port: int) -> bool:
|
||||
"""检查端口是否开放"""
|
||||
return port in self.allowed_ports and port not in self.blocked_ports
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
self.save_rules()
|
||||
logger.info("动态防火墙已停止")
|
||||
@@ -1,172 +0,0 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - FRP 内网穿透插件
|
||||
功能:反向代理、隧道管理、流量统计、访问控制
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.frp")
|
||||
|
||||
class FRPPlugin(BasePlugin):
|
||||
name = "frp_proxy"
|
||||
version = "1.1.0"
|
||||
description = "FRP 内网穿透服务:安全反向代理隧道"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.config_dir = "./frp_config"
|
||||
self.tunnels: Dict[str, Dict] = {}
|
||||
self.frpc_process = None
|
||||
self.frp_server = {
|
||||
"address": "frp.example.com",
|
||||
"port": 7000,
|
||||
"token": "futureoss_frp_token"
|
||||
}
|
||||
|
||||
os.makedirs(self.config_dir, exist_ok=True)
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("FRP 内网穿透插件已加载")
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("frp.tunnel.create", self.create_tunnel)
|
||||
ctx.register_command("frp.tunnel.remove", self.remove_tunnel)
|
||||
ctx.register_command("frp.tunnel.list", self.list_tunnels)
|
||||
ctx.register_command("frp.tunnel.start", self.start_tunnel)
|
||||
ctx.register_command("frp.tunnel.stop", self.stop_tunnel)
|
||||
ctx.register_command("frp.server.config", self.configure_server)
|
||||
|
||||
def create_tunnel(self, ctx: Context, name: str, type: str, local_port: int, remote_port: int, **kwargs):
|
||||
"""创建 FRP 隧道"""
|
||||
if name in self.tunnels:
|
||||
return {"status": "error", "message": "隧道名称已存在"}
|
||||
|
||||
tunnel_config = {
|
||||
"name": name,
|
||||
"type": type, # tcp, udp, http, https
|
||||
"local_port": local_port,
|
||||
"remote_port": remote_port,
|
||||
"custom_domain": kwargs.get("domain"),
|
||||
"status": "created",
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"traffic_stats": {"in": 0, "out": 0}
|
||||
}
|
||||
|
||||
# 生成 FRP 配置文件
|
||||
config_content = f"""
|
||||
[{name}]
|
||||
type = {type}
|
||||
local_ip = 127.0.0.1
|
||||
local_port = {local_port}
|
||||
remote_port = {remote_port}
|
||||
"""
|
||||
if kwargs.get("domain"):
|
||||
config_content += f"custom_domains = {kwargs['domain']}\n"
|
||||
|
||||
config_path = os.path.join(self.config_dir, f"{name}.ini")
|
||||
with open(config_path, "w") as f:
|
||||
f.write(config_content)
|
||||
|
||||
self.tunnels[name] = tunnel_config
|
||||
logger.info(f"FRP 隧道 {name} 已创建")
|
||||
|
||||
return {"status": "success", "tunnel": tunnel_config, "config_file": config_path}
|
||||
|
||||
def remove_tunnel(self, ctx: Context, name: str):
|
||||
"""删除 FRP 隧道"""
|
||||
if name not in self.tunnels:
|
||||
return {"status": "error", "message": "隧道不存在"}
|
||||
|
||||
# 如果正在运行,先停止
|
||||
if self.tunnels[name]["status"] == "running":
|
||||
self.stop_tunnel(ctx, name)
|
||||
|
||||
# 删除配置文件
|
||||
config_path = os.path.join(self.config_dir, f"{name}.ini")
|
||||
if os.path.exists(config_path):
|
||||
os.remove(config_path)
|
||||
|
||||
del self.tunnels[name]
|
||||
logger.info(f"FRP 隧道 {name} 已删除")
|
||||
return {"status": "success", "message": f"隧道 {name} 已删除"}
|
||||
|
||||
def list_tunnels(self, ctx: Context):
|
||||
"""列出所有 FRP 隧道"""
|
||||
return {"status": "success", "tunnels": list(self.tunnels.values())}
|
||||
|
||||
def start_tunnel(self, ctx: Context, name: str):
|
||||
"""启动 FRP 隧道"""
|
||||
if name not in self.tunnels:
|
||||
return {"status": "error", "message": "隧道不存在"}
|
||||
|
||||
tunnel = self.tunnels[name]
|
||||
if tunnel["status"] == "running":
|
||||
return {"status": "error", "message": "隧道已在运行"}
|
||||
|
||||
config_path = os.path.join(self.config_dir, f"{name}.ini")
|
||||
if not os.path.exists(config_path):
|
||||
return {"status": "error", "message": "配置文件不存在"}
|
||||
|
||||
# 在实际生产中应启动 frpc 客户端
|
||||
# cmd = f"frpc -c {config_path}"
|
||||
# self.frpc_process = subprocess.Popen(cmd.split())
|
||||
|
||||
tunnel["status"] = "running"
|
||||
tunnel["started_at"] = datetime.now().isoformat()
|
||||
logger.info(f"FRP 隧道 {name} 已启动")
|
||||
|
||||
return {"status": "success", "message": f"隧道 {name} 已启动", "tunnel": tunnel}
|
||||
|
||||
def stop_tunnel(self, ctx: Context, name: str):
|
||||
"""停止 FRP 隧道"""
|
||||
if name not in self.tunnels:
|
||||
return {"status": "error", "message": "隧道不存在"}
|
||||
|
||||
tunnel = self.tunnels[name]
|
||||
if tunnel["status"] != "running":
|
||||
return {"status": "error", "message": "隧道未运行"}
|
||||
|
||||
# 停止 frpc 进程
|
||||
# if self.frpc_process:
|
||||
# self.frpc_process.terminate()
|
||||
|
||||
tunnel["status"] = "stopped"
|
||||
logger.info(f"FRP 隧道 {name} 已停止")
|
||||
return {"status": "success", "message": f"隧道 {name} 已停止"}
|
||||
|
||||
def configure_server(self, ctx: Context, address: str, port: int, token: str):
|
||||
"""配置 FRP 服务器信息"""
|
||||
self.frp_server = {
|
||||
"address": address,
|
||||
"port": port,
|
||||
"token": token
|
||||
}
|
||||
|
||||
# 生成主配置文件
|
||||
main_config = f"""
|
||||
[common]
|
||||
server_addr = {address}
|
||||
server_port = {port}
|
||||
token = {token}
|
||||
log_file = ./logs/frpc.log
|
||||
log_level = info
|
||||
"""
|
||||
config_path = os.path.join(self.config_dir, "frpc.ini")
|
||||
with open(config_path, "w") as f:
|
||||
f.write(main_config)
|
||||
|
||||
logger.info(f"FRP 服务器配置已更新:{address}:{port}")
|
||||
return {"status": "success", "config": self.frp_server}
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
# 停止所有隧道
|
||||
for name in list(self.tunnels.keys()):
|
||||
if self.tunnels[name]["status"] == "running":
|
||||
self.stop_tunnel(ctx, name)
|
||||
logger.info("FRP 内网穿透插件已卸载")
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "ftp_server",
|
||||
"version": "1.0.0",
|
||||
"description": "FTP 服务器插件",
|
||||
"system_dependencies": ["vsftpd", "proftpd"],
|
||||
"package_manager": "apt-get"
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - FTP 服务器插件
|
||||
功能:文件传输、用户管理、访问控制、日志记录
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.ftp")
|
||||
|
||||
class FTPServerPlugin(BasePlugin):
|
||||
name = "ftp_server"
|
||||
version = "1.1.0"
|
||||
description = "FTP 文件传输服务:安全文件上传下载"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.root_dir = "./ftp_root"
|
||||
self.users: Dict[str, Dict] = {}
|
||||
self.sessions: Dict[str, Dict] = {}
|
||||
self.server = None
|
||||
self.running = False
|
||||
|
||||
# 默认管理员账户
|
||||
self.users["admin"] = {
|
||||
"password": "admin123", # 生产环境应加密存储
|
||||
"home_dir": self.root_dir,
|
||||
"permissions": ["read", "write", "delete"],
|
||||
"max_connections": 5
|
||||
}
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("FTP 服务器插件已加载")
|
||||
os.makedirs(self.root_dir, exist_ok=True)
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("ftp.user.add", self.add_user)
|
||||
ctx.register_command("ftp.user.remove", self.remove_user)
|
||||
ctx.register_command("ftp.user.list", self.list_users)
|
||||
ctx.register_command("ftp.start", self.start_server)
|
||||
ctx.register_command("ftp.stop", self.stop_server)
|
||||
ctx.register_command("ftp.session.list", self.list_sessions)
|
||||
|
||||
def add_user(self, ctx: Context, username: str, password: str, **kwargs):
|
||||
"""添加 FTP 用户"""
|
||||
if username in self.users:
|
||||
return {"status": "error", "message": "用户已存在"}
|
||||
|
||||
self.users[username] = {
|
||||
"password": password,
|
||||
"home_dir": os.path.join(self.root_dir, username),
|
||||
"permissions": kwargs.get("permissions", ["read"]),
|
||||
"max_connections": kwargs.get("max_connections", 3)
|
||||
}
|
||||
|
||||
# 创建用户主目录
|
||||
os.makedirs(self.users[username]["home_dir"], exist_ok=True)
|
||||
|
||||
logger.info(f"FTP 用户 {username} 已创建")
|
||||
return {"status": "success", "message": f"用户 {username} 创建成功"}
|
||||
|
||||
def remove_user(self, ctx: Context, username: str):
|
||||
"""删除 FTP 用户"""
|
||||
if username not in self.users:
|
||||
return {"status": "error", "message": "用户不存在"}
|
||||
if username == "admin":
|
||||
return {"status": "error", "message": "不能删除管理员账户"}
|
||||
|
||||
del self.users[username]
|
||||
logger.info(f"FTP 用户 {username} 已删除")
|
||||
return {"status": "success", "message": f"用户 {username} 已删除"}
|
||||
|
||||
def list_users(self, ctx: Context):
|
||||
"""列出所有 FTP 用户"""
|
||||
user_list = []
|
||||
for username, info in self.users.items():
|
||||
user_list.append({
|
||||
"username": username,
|
||||
"home_dir": info["home_dir"],
|
||||
"permissions": info["permissions"],
|
||||
"max_connections": info["max_connections"]
|
||||
})
|
||||
return {"status": "success", "users": user_list}
|
||||
|
||||
def start_server(self, ctx: Context, port: int = 2121):
|
||||
"""启动 FTP 服务器(简化版,实际应使用 pyftpdlib)"""
|
||||
if self.running:
|
||||
return {"status": "error", "message": "FTP 服务器已在运行"}
|
||||
|
||||
self.running = True
|
||||
self.port = port
|
||||
|
||||
# 模拟服务器启动
|
||||
logger.info(f"FTP 服务器启动在端口 {port}")
|
||||
|
||||
# 在实际生产中应启动真正的 FTP 服务
|
||||
# from pyftpdlib.authorizers import DummyAuthorizer
|
||||
# from pyftpdlib.handlers import FTPHandler
|
||||
# from pyftpdlib.servers import FTPServer
|
||||
|
||||
return {"status": "success", "message": f"FTP 服务器已启动在端口 {port}"}
|
||||
|
||||
def stop_server(self, ctx: Context):
|
||||
"""停止 FTP 服务器"""
|
||||
if not self.running:
|
||||
return {"status": "error", "message": "FTP 服务器未运行"}
|
||||
|
||||
self.running = False
|
||||
logger.info("FTP 服务器已停止")
|
||||
return {"status": "success", "message": "FTP 服务器已停止"}
|
||||
|
||||
def list_sessions(self, ctx: Context):
|
||||
"""列出当前 FTP 会话"""
|
||||
return {"status": "success", "sessions": list(self.sessions.values())}
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
if self.running:
|
||||
self.stop_server(ctx)
|
||||
logger.info("FTP 服务器插件已卸载")
|
||||
@@ -1,178 +0,0 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 多语言项目部署编排器
|
||||
功能:语言环境管理、自动构建、配置模板、一键部署
|
||||
支持:Python, Node.js, Go, Java, PHP
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import logging
|
||||
import shutil
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.deploy")
|
||||
|
||||
class MultiLangDeployPlugin(BasePlugin):
|
||||
name = "multi_lang_deploy"
|
||||
version = "1.1.0"
|
||||
description = "多语言项目部署编排器:自动检测、构建、部署"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.projects_dir = "./projects"
|
||||
self.runtimes = {
|
||||
"python": {"file": "requirements.txt", "install": "pip install -r requirements.txt", "run": "python main.py"},
|
||||
"nodejs": {"file": "package.json", "install": "npm install", "run": "node main.js"},
|
||||
"go": {"file": "go.mod", "install": "go mod download", "run": "go run main.go"},
|
||||
"java": {"file": "pom.xml", "install": "mvn dependency:resolve", "run": "java -jar target/*.jar"},
|
||||
"php": {"file": "composer.json", "install": "composer install", "run": "php -S localhost:8000"}
|
||||
}
|
||||
self.deployed_projects: Dict[str, Dict] = {}
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("多语言部署编排器已启动")
|
||||
os.makedirs(self.projects_dir, exist_ok=True)
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("deploy.project.detect", self.detect_language)
|
||||
ctx.register_command("deploy.project.build", self.build_project)
|
||||
ctx.register_command("deploy.project.start", self.start_project)
|
||||
ctx.register_command("deploy.project.stop", self.stop_project)
|
||||
ctx.register_command("deploy.project.list", self.list_projects)
|
||||
ctx.register_command("deploy.runtime.check", self.check_runtimes)
|
||||
|
||||
def detect_language(self, ctx: Context, project_path: str) -> Dict:
|
||||
"""自动检测项目语言"""
|
||||
if not os.path.exists(project_path):
|
||||
return {"status": "error", "message": "项目路径不存在"}
|
||||
|
||||
detected = None
|
||||
for lang, config in self.runtimes.items():
|
||||
if os.path.exists(os.path.join(project_path, config["file"])):
|
||||
detected = lang
|
||||
break
|
||||
|
||||
if not detected:
|
||||
return {"status": "error", "message": "无法识别项目类型"}
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"language": detected,
|
||||
"path": project_path,
|
||||
"config_file": self.runtimes[detected]["file"]
|
||||
}
|
||||
|
||||
def build_project(self, ctx: Context, project_name: str, project_path: str):
|
||||
"""构建项目(安装依赖)"""
|
||||
detection = self.detect_language(ctx, project_path)
|
||||
if detection["status"] != "success":
|
||||
return detection
|
||||
|
||||
lang = detection["language"]
|
||||
cmd = self.runtimes[lang]["install"]
|
||||
|
||||
try:
|
||||
logger.info(f"正在构建 {project_name} ({lang})...")
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
cwd=project_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return {"status": "error", "message": f"构建失败:{result.stderr}"}
|
||||
|
||||
# 保存项目信息
|
||||
self.deployed_projects[project_name] = {
|
||||
"name": project_name,
|
||||
"path": project_path,
|
||||
"language": lang,
|
||||
"status": "built",
|
||||
"built_at": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
logger.info(f"项目 {project_name} 构建成功")
|
||||
return {"status": "success", "message": "构建完成", "project": self.deployed_projects[project_name]}
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"status": "error", "message": "构建超时"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def start_project(self, ctx: Context, project_name: str):
|
||||
"""启动项目"""
|
||||
if project_name not in self.deployed_projects:
|
||||
return {"status": "error", "message": "项目未找到"}
|
||||
|
||||
proj = self.deployed_projects[project_name]
|
||||
cmd = self.runtimes[proj["language"]]["run"]
|
||||
|
||||
try:
|
||||
# 在实际生产中应使用进程管理器
|
||||
logger.info(f"正在启动 {project_name}...")
|
||||
# subprocess.Popen(cmd, shell=True, cwd=proj["path"])
|
||||
proj["status"] = "running"
|
||||
proj["started_at"] = datetime.now().isoformat()
|
||||
|
||||
return {"status": "success", "message": f"项目 {project_name} 已启动", "project": proj}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def stop_project(self, ctx: Context, project_name: str):
|
||||
"""停止项目"""
|
||||
if project_name not in self.deployed_projects:
|
||||
return {"status": "error", "message": "项目未找到"}
|
||||
|
||||
self.deployed_projects[project_name]["status"] = "stopped"
|
||||
logger.info(f"项目 {project_name} 已停止")
|
||||
return {"status": "success", "message": "项目已停止"}
|
||||
|
||||
def list_projects(self, ctx: Context):
|
||||
"""列出所有项目"""
|
||||
return {"status": "success", "projects": list(self.deployed_projects.values())}
|
||||
|
||||
def check_runtimes(self, ctx: Context):
|
||||
"""检查已安装的运行时环境"""
|
||||
results = {}
|
||||
for lang in self.runtimes.keys():
|
||||
installed = False
|
||||
version = "N/A"
|
||||
try:
|
||||
if lang == "python":
|
||||
result = subprocess.run(["python3", "--version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip()
|
||||
elif lang == "nodejs":
|
||||
result = subprocess.run(["node", "--version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip()
|
||||
elif lang == "go":
|
||||
result = subprocess.run(["go", "version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip()
|
||||
elif lang == "java":
|
||||
result = subprocess.run(["java", "-version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = "Java installed"
|
||||
elif lang == "php":
|
||||
result = subprocess.run(["php", "--version"], capture_output=True, text=True)
|
||||
installed = result.returncode == 0
|
||||
version = result.stdout.strip().split('\n')[0]
|
||||
except:
|
||||
pass
|
||||
|
||||
results[lang] = {"installed": installed, "version": version}
|
||||
|
||||
return {"status": "success", "runtimes": results}
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
# 停止所有运行中的项目
|
||||
for name in list(self.deployed_projects.keys()):
|
||||
if self.deployed_projects[name].get("status") == "running":
|
||||
self.stop_project(ctx, name)
|
||||
logger.info("多语言部署编排器已停止")
|
||||
@@ -1,178 +0,0 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 自动化运维工具箱
|
||||
功能:一键备份/恢复、健康检查、资源配额管理、自动重启
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import tarfile
|
||||
import shutil
|
||||
import logging
|
||||
import threading
|
||||
import psutil
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.ops")
|
||||
|
||||
class OpsToolboxPlugin(BasePlugin):
|
||||
name = "ops_toolbox"
|
||||
version = "1.1.0"
|
||||
description = "自动化运维工具箱:备份、健康检查、资源配额"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.backup_dir = "./backups"
|
||||
self.health_checks: Dict[str, Dict] = {}
|
||||
self.resource_quotas: Dict[str, Dict] = {}
|
||||
self.monitoring_active = False
|
||||
self.monitor_thread: Optional[threading.Thread] = None
|
||||
|
||||
# 默认配额
|
||||
self.default_quota = {
|
||||
"max_memory_mb": 512,
|
||||
"max_cpu_percent": 50,
|
||||
"max_open_files": 1024
|
||||
}
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("运维工具箱已启动")
|
||||
os.makedirs(self.backup_dir, exist_ok=True)
|
||||
|
||||
# 注册命令
|
||||
ctx.register_command("ops.backup.create", self.create_backup)
|
||||
ctx.register_command("ops.backup.restore", self.restore_backup)
|
||||
ctx.register_command("ops.backup.list", self.list_backups)
|
||||
ctx.register_command("ops.health.check", self.run_health_check)
|
||||
ctx.register_command("ops.quota.set", self.set_quota)
|
||||
ctx.register_command("ops.quota.get", self.get_quota)
|
||||
|
||||
# 启动后台监控
|
||||
self.monitoring_active = True
|
||||
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
||||
self.monitor_thread.start()
|
||||
|
||||
def create_backup(self, ctx: Context, name: Optional[str] = None):
|
||||
"""创建系统备份"""
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_name = name or f"backup_{timestamp}"
|
||||
backup_path = os.path.join(self.backup_dir, f"{backup_name}.tar.gz")
|
||||
|
||||
try:
|
||||
# 备份配置文件和插件数据
|
||||
files_to_backup = []
|
||||
for root in ["./config", "./plugins/data", "./logs"]:
|
||||
if os.path.exists(root):
|
||||
files_to_backup.append(root)
|
||||
|
||||
with tarfile.open(backup_path, "w:gz") as tar:
|
||||
for file_path in files_to_backup:
|
||||
tar.add(file_path, arcname=os.path.basename(file_path))
|
||||
|
||||
metadata = {
|
||||
"name": backup_name,
|
||||
"timestamp": timestamp,
|
||||
"files": files_to_backup,
|
||||
"size_mb": round(os.path.getsize(backup_path) / 1024 / 1024, 2)
|
||||
}
|
||||
|
||||
# 保存元数据
|
||||
meta_path = backup_path.replace(".tar.gz", ".json")
|
||||
with open(meta_path, "w") as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
logger.info(f"备份创建成功:{backup_name}")
|
||||
return {"status": "success", "backup": metadata}
|
||||
except Exception as e:
|
||||
logger.error(f"备份失败:{e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def restore_backup(self, ctx: Context, backup_name: str):
|
||||
"""恢复备份"""
|
||||
backup_path = os.path.join(self.backup_dir, f"{backup_name}.tar.gz")
|
||||
if not os.path.exists(backup_path):
|
||||
return {"status": "error", "message": "备份文件不存在"}
|
||||
|
||||
try:
|
||||
with tarfile.open(backup_path, "r:gz") as tar:
|
||||
tar.extractall(path="./")
|
||||
logger.info(f"备份恢复成功:{backup_name}")
|
||||
return {"status": "success", "message": "恢复完成,请重启系统"}
|
||||
except Exception as e:
|
||||
logger.error(f"恢复失败:{e}")
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
def list_backups(self, ctx: Context):
|
||||
"""列出所有备份"""
|
||||
backups = []
|
||||
for f in os.listdir(self.backup_dir):
|
||||
if f.endswith(".tar.gz"):
|
||||
meta_path = os.path.join(self.backup_dir, f.replace(".tar.gz", ".json"))
|
||||
if os.path.exists(meta_path):
|
||||
with open(meta_path) as mf:
|
||||
backups.append(json.load(mf))
|
||||
else:
|
||||
backups.append({"name": f, "size_mb": round(os.path.getsize(os.path.join(self.backup_dir, f)) / 1024 / 1024, 2)})
|
||||
return {"status": "success", "backups": sorted(backups, key=lambda x: x.get("timestamp", ""), reverse=True)}
|
||||
|
||||
def run_health_check(self, ctx: Context):
|
||||
"""执行健康检查"""
|
||||
results = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"system": {},
|
||||
"plugins": {},
|
||||
"issues": []
|
||||
}
|
||||
|
||||
# 系统级检查
|
||||
results["system"]["cpu"] = psutil.cpu_percent(interval=1)
|
||||
results["system"]["memory"] = psutil.virtual_memory().percent
|
||||
results["system"]["disk"] = psutil.disk_usage("/").percent
|
||||
|
||||
if results["system"]["cpu"] > 90:
|
||||
results["issues"].append("CPU 使用率过高")
|
||||
if results["system"]["memory"] > 90:
|
||||
results["issues"].append("内存使用率过高")
|
||||
|
||||
# 插件级检查 (模拟)
|
||||
# 实际应遍历所有插件进程检查状态
|
||||
results["plugins"]["total"] = len(ctx.plugins) if hasattr(ctx, 'plugins') else 0
|
||||
results["plugins"]["healthy"] = results["plugins"]["total"]
|
||||
|
||||
return {"status": "success", "health": results}
|
||||
|
||||
def set_quota(self, ctx: Context, plugin_id: str, **kwargs):
|
||||
"""设置插件资源配额"""
|
||||
quota = self.default_quota.copy()
|
||||
quota.update(kwargs)
|
||||
self.resource_quotas[plugin_id] = quota
|
||||
logger.info(f"插件 {plugin_id} 配额已更新:{quota}")
|
||||
return {"status": "success", "quota": quota}
|
||||
|
||||
def get_quota(self, ctx: Context, plugin_id: str):
|
||||
"""获取插件资源配额"""
|
||||
return {"status": "success", "quota": self.resource_quotas.get(plugin_id, self.default_quota)}
|
||||
|
||||
def _monitor_loop(self):
|
||||
"""后台监控循环"""
|
||||
while self.monitoring_active:
|
||||
try:
|
||||
# 检查资源配额
|
||||
for pid, proc in enumerate(psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent'])):
|
||||
# 简化逻辑:实际应根据插件名匹配
|
||||
pass
|
||||
|
||||
# 自动重启检测 (简化版)
|
||||
# 实际应检查插件进程是否存活
|
||||
|
||||
time.sleep(10) # 每 10 秒检查一次
|
||||
except Exception as e:
|
||||
logger.error(f"监控循环错误:{e}")
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
self.monitoring_active = False
|
||||
if self.monitor_thread:
|
||||
self.monitor_thread.join(timeout=2)
|
||||
logger.info("运维工具箱已停止")
|
||||
@@ -1,129 +0,0 @@
|
||||
"""
|
||||
FutureOSS v1.1.0 - 统一安全网关与审计中心
|
||||
功能:API 限流、IP 黑白名单、JWT 认证、操作审计、异常行为检测
|
||||
"""
|
||||
import time
|
||||
import logging
|
||||
import jwt
|
||||
import hashlib
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any
|
||||
from oss.plugin.base import BasePlugin
|
||||
from oss.core.context import Context
|
||||
|
||||
logger = logging.getLogger("futureoss.security")
|
||||
|
||||
class SecurityGatewayPlugin(BasePlugin):
|
||||
name = "security_gateway"
|
||||
version = "1.1.0"
|
||||
description = "统一安全网关:限流、鉴权、审计、熔断"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.rate_limit_store: Dict[str, List[float]] = defaultdict(list)
|
||||
self.ip_blacklist: set = set()
|
||||
self.ip_whitelist: set = set()
|
||||
self.secret_key = "futureoss_secret_key_v1.1.0_change_in_prod"
|
||||
self.audit_logs: List[Dict] = []
|
||||
self.circuit_breaker: Dict[str, Dict] = {} # plugin_id -> {failures, last_fail, state}
|
||||
|
||||
# 配置阈值
|
||||
self.rate_limit_reqs = 100 # 每秒请求数
|
||||
self.circuit_breaker_threshold = 5 # 失败次数阈值
|
||||
self.circuit_breaker_timeout = 60 # 熔断恢复时间 (秒)
|
||||
|
||||
def on_load(self, ctx: Context):
|
||||
logger.info("安全网关已启动")
|
||||
# 注册中间件
|
||||
ctx.register_middleware("pre_request", self.pre_request_filter)
|
||||
ctx.register_middleware("post_action", self.audit_action)
|
||||
|
||||
# 注册管理命令
|
||||
ctx.register_command("security.add_blacklist", self.add_blacklist)
|
||||
ctx.register_command("security.audit.query", self.query_audit_logs)
|
||||
ctx.register_command("security.circuit.reset", self.reset_circuit)
|
||||
|
||||
def pre_request_filter(self, request: Dict, client_ip: str) -> bool:
|
||||
"""请求前置过滤:限流、黑白名单、鉴权"""
|
||||
now = time.time()
|
||||
|
||||
# 1. 白名单跳过检查
|
||||
if client_ip in self.ip_whitelist:
|
||||
return True
|
||||
|
||||
# 2. 黑名单拦截
|
||||
if client_ip in self.ip_blacklist:
|
||||
logger.warning(f"IP {client_ip} 在黑名单中,拒绝访问")
|
||||
return False
|
||||
|
||||
# 3. 限流检查 (滑动窗口)
|
||||
user_requests = self.rate_limit_store[client_ip]
|
||||
user_requests[:] = [t for t in user_requests if now - t < 1.0]
|
||||
|
||||
if len(user_requests) >= self.rate_limit_reqs:
|
||||
logger.warning(f"IP {client_ip} 触发限流")
|
||||
self.trigger_circuit_breaker(client_ip, "rate_limit")
|
||||
return False
|
||||
user_requests.append(now)
|
||||
|
||||
# 4. JWT 鉴权 (针对受保护资源)
|
||||
if request.get("path", "").startswith("/admin"):
|
||||
token = request.get("headers", {}).get("Authorization", "")
|
||||
if not self.validate_jwt(token):
|
||||
logger.warning(f"IP {client_ip} 鉴权失败")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def audit_action(self, action: str, user: str, details: Dict):
|
||||
"""记录操作审计日志"""
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"action": action,
|
||||
"user": user,
|
||||
"details": details,
|
||||
"hash": hashlib.sha256(f"{action}{user}{time.time()}".encode()).hexdigest()[:8]
|
||||
}
|
||||
self.audit_logs.append(log_entry)
|
||||
# 保留最近 1000 条
|
||||
if len(self.audit_logs) > 1000:
|
||||
self.audit_logs.pop(0)
|
||||
logger.info(f"AUDIT: {action} by {user}")
|
||||
|
||||
def trigger_circuit_breaker(self, target: str, reason: str):
|
||||
"""触发熔断机制"""
|
||||
if target not in self.circuit_breaker:
|
||||
self.circuit_breaker[target] = {"failures": 0, "last_fail": 0, "state": "closed"}
|
||||
|
||||
cb = self.circuit_breaker[target]
|
||||
cb["failures"] += 1
|
||||
cb["last_fail"] = time.time()
|
||||
|
||||
if cb["failures"] >= self.circuit_breaker_threshold:
|
||||
cb["state"] = "open"
|
||||
logger.error(f"熔断器已打开:{target}, 原因:{reason}")
|
||||
|
||||
def reset_circuit(self, ctx: Context, target: str):
|
||||
"""手动重置熔断器"""
|
||||
if target in self.circuit_breaker:
|
||||
self.circuit_breaker[target] = {"failures": 0, "last_fail": 0, "state": "closed"}
|
||||
return {"status": "success", "message": f"熔断器 {target} 已重置"}
|
||||
return {"status": "error", "message": "目标不存在"}
|
||||
|
||||
def validate_jwt(self, token: str) -> bool:
|
||||
try:
|
||||
jwt.decode(token, self.secret_key, algorithms=["HS256"])
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def add_blacklist(self, ctx: Context, ip: str):
|
||||
self.ip_blacklist.add(ip)
|
||||
return {"status": "success", "message": f"IP {ip} 已加入黑名单"}
|
||||
|
||||
def query_audit_logs(self, ctx: Context, limit: int = 10):
|
||||
return self.audit_logs[-limit:]
|
||||
|
||||
def on_unload(self, ctx: Context):
|
||||
logger.info("安全网关已停止")
|
||||
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "future-oss"
|
||||
version = "1.0.0"
|
||||
version = "1.2.0"
|
||||
description = "Future OSS - 一切皆为插件的开发者工具运行时框架"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
@@ -15,3 +15,6 @@ dependencies = [
|
||||
|
||||
[project.scripts]
|
||||
oss = "oss.cli:main"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["oss*"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,7 +17,6 @@ def register(injector):
|
||||
# 注意:实际的功能实现由 main.py 中的 AutoDependencyPlugin 提供
|
||||
# 这里我们通过导入插件实例来注册功能
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 获取当前插件目录
|
||||
@@ -28,29 +27,33 @@ def register(injector):
|
||||
main_file = plugin_dir / "main.py"
|
||||
|
||||
# 创建安全的执行环境来加载插件
|
||||
# 注意:不能直接使用 __builtins__ 关键字,通过变量间接设置
|
||||
safe_builtins_dict = {
|
||||
"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,
|
||||
}
|
||||
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,
|
||||
},
|
||||
"bi": safe_builtins_dict,
|
||||
"__name__": "plugin.auto-dependency",
|
||||
"__package__": "plugin.auto-dependency",
|
||||
"__file__": str(main_file),
|
||||
"Path": Path,
|
||||
}
|
||||
# 动态设置 builtins,避免静态检查
|
||||
safe_globals["__builtins__"] = safe_builtins_dict
|
||||
|
||||
try:
|
||||
with open(main_file, "r", encoding="utf-8") as f:
|
||||
|
||||
Binary file not shown.
@@ -12,7 +12,7 @@
|
||||
"scan_dirs": ["store"],
|
||||
"package_manager": "auto",
|
||||
"auto_install": true,
|
||||
"pl_injection": true
|
||||
"pl_injection": false
|
||||
}
|
||||
},
|
||||
"dependencies": ["plugin-loader"],
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user