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:
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("安全网关已停止")
|
||||
Reference in New Issue
Block a user