From 1295aaed91de5f83d86fb676ea92393c8d4ef1be Mon Sep 17 00:00:00 2001 From: Falck Date: Sun, 26 Apr 2026 16:55:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=BA=86=E4=B8=8D=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pid | 1 - .pylintrc | 287 -------- ai.md | 661 ------------------ tests/test_cli.py | 39 -- tests/test_config.py | 105 --- tests/test_plugin_loader.py | 48 -- tests/test_plugin_manager.py | 53 -- .../sign_core_plugins.cpython-312.pyc | Bin 4301 -> 0 bytes .../sign_plugin_loader.cpython-312.pyc | Bin 4591 -> 0 bytes .../__pycache__/sign_plugins.cpython-312.pyc | Bin 8961 -> 0 bytes .../sign_single_plugin.cpython-312.pyc | Bin 4581 -> 0 bytes .../test_signature.cpython-312.pyc | Bin 8531 -> 0 bytes tools/sign_core_plugins.py | 99 --- tools/sign_plugin_loader.py | 102 --- tools/sign_plugins.py | 193 ----- tools/sign_single_plugin.py | 102 --- tools/test_signature.py | 199 ------ video/architecture.html | 274 -------- video/index.html | 113 --- video/intro.html | 553 --------------- video/plugin-demo.html | 324 --------- 21 files changed, 3153 deletions(-) delete mode 100644 .pid delete mode 100644 .pylintrc delete mode 100644 ai.md delete mode 100644 tests/test_cli.py delete mode 100644 tests/test_config.py delete mode 100644 tests/test_plugin_loader.py delete mode 100644 tests/test_plugin_manager.py delete mode 100644 tools/__pycache__/sign_core_plugins.cpython-312.pyc delete mode 100644 tools/__pycache__/sign_plugin_loader.cpython-312.pyc delete mode 100644 tools/__pycache__/sign_plugins.cpython-312.pyc delete mode 100644 tools/__pycache__/sign_single_plugin.cpython-312.pyc delete mode 100644 tools/__pycache__/test_signature.cpython-312.pyc delete mode 100644 tools/sign_core_plugins.py delete mode 100644 tools/sign_plugin_loader.py delete mode 100644 tools/sign_plugins.py delete mode 100644 tools/sign_single_plugin.py delete mode 100644 tools/test_signature.py delete mode 100644 video/architecture.html delete mode 100644 video/index.html delete mode 100644 video/intro.html delete mode 100644 video/plugin-demo.html diff --git a/.pid b/.pid deleted file mode 100644 index 1447442..0000000 --- a/.pid +++ /dev/null @@ -1 +0,0 @@ -18490 diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index de82293..0000000 --- a/.pylintrc +++ /dev/null @@ -1,287 +0,0 @@ -[MASTER] -jobs=4 -persistent=yes -rcfile= -load-plugins= -extension-pkg-whitelist= -ignore-patterns=^test_.*\.py$ -ignore=CVS -output-format=colorized -reports=yes -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) -score=yes -fail-under=7.0 - -[MESSAGES CONTROL] -disable=all -enable= - missing-docstring, - empty-docstring, - invalid-name, - too-few-public-methods, - too-many-arguments, - too-many-instance-attributes, - too-many-locals, - too-many-public-methods, - too-many-statements, - redefined-builtin, - redefined-outer-name, - unused-argument, - unused-import, - unused-variable, - unused-wildcard-import, - wrong-import-order, - wrong-import-position, - import-error, - no-name-in-module, - no-member, - no-self-use, - not-callable, - undefined-variable, - used-before-assignment, - broad-except, - bare-except, - try-except-raise, - duplicate-code, - fixme, - trailing-whitespace, - bad-whitespace, - line-too-long, - missing-final-newline, - mixed-line-endings, - bad-continuation, - trailing-newlines, - multiple-statements, - anomalous-backslash-in-string, - deprecated-module, - deprecated-method, - super-with-arguments, - raise-missing-from, - consider-using-f-string, - consider-using-with, - use-implicit-booleaness-not-comparison, - use-list-literal, - use-dict-literal, - consider-using-enumerate, - consider-iterating-dictionary, - consider-using-set-comprehension, - consider-using-generator, - consider-using-any-or-all, - consider-using-in, - consider-using-max-builtin, - consider-using-min-builtin, - consider-using-sum, - consider-merging-isinstance, - chained-comparison, - simplifiable-if-expression, - unnecessary-lambda, - unnecessary-comprehension, - unnecessary-dunder-call, - unnecessary-pass, - unnecessary-ellipsis, - useless-else-on-loop, - useless-return, - useless-object-inheritance, - useless-suppression, - wrong-spelling-in-comment, - wrong-spelling-in-docstring - -[REPORTS] -output-format=text -files-output=no -reports=yes -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) -score=yes - -[REFACTORING] -max-nested-blocks=5 -max-line-length=88 -max-module-lines=1000 -max-statements=50 -max-args=5 -max-locals=15 -max-returns=6 -max-branches=12 -max-statements-in-a-loop=20 -max-public-methods=20 -max-attributes=7 -max-parents=7 -max-bool-expr=5 - -[BASIC] -good-names=i, - j, - k, - ex, - Run, - _, - __, - fd, - msg, - v, - var, - x, - y, - z, - ax, - fig, - plt, - df, - idx, - cnt, - doc, - env, - app, - req, - res, - cls, - self, - mcs, - obj, - mod, - pkgs, - pkg, - cfg, - conf, - config, - opts, - args, - kwargs, - logger, - log -bad-names=foo, - bar, - baz, - toto, - tutu, - tata -docstring-min-length=-1 - -[FORMAT] -max-line-length=88 -ignore-long-lines=^\\s*(# )??$ -single-line-if-stmt=no -single-line-class-stmt=no -max-module-lines=1000 -indent-string=' ' - -[SIMILARITIES] -min-similarity-lines=4 -ignore-comments=yes -ignore-docstrings=yes -ignore-imports=no - -[TYPECHECK] -ignored-modules= -ignored-classes=optparse.Values,thread._local,_thread._local -generated-members=REQUEST,acl_users,aq_parent -contextmanager-decorators=contextlib.contextmanager -missing-member-hint=yes -missing-member-hint-distance=1 -missing-member-max-choices=1 -missing-member-local-gt=2 -ignore-on-opaque-inference=yes -ignored-checks-for-mixins= -signature-mutators= -ignore-mixin-members=yes -ignore-none=yes -ignored-parents= -ignore-erase=no -ignore-import-error=yes -ignore-missing-imports=yes -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ -additional-builtins= - -[VARIABLES] -additional-builtins= -init-import=no -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ -callbacks=cb_,_cb -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io -allow-global-unused-variables=no - -[LOGGING] -logging-modules=logging - -[MISCELLANEOUS] -notes=FIXME, - XXX, - TODO, - HACK, - BUG, - NOTE, - OPTIMIZE, - REVIEW, - WARNING, - DEPRECATED -max-string-length=100 - -[DESIGN] -max-args=5 -max-locals=15 -max-returns=6 -max-branches=12 -max-statements=50 -max-parents=7 -max-attributes=7 -min-public-methods=2 -max-public-methods=20 - -[IMPORTS] -deprecated-modules= -import-graph= -ext-import-graph= -int-import-graph= -known-standard-library= -known-third-party= -known-local-folder= -preferred-modules= -allow-wildcard-with-all=no -allow-any-import-level=no -allow-relative-imports=yes -allow-from-import-under-package=yes -allow-import-from-same-module=no -allow-import-from-package=yes -allow-unused-imports=no -allow-cyclic-import=no -cyclic-import-limit=10 -ignore-imports=no -ignore-import-error=yes -ignore-missing-imports=yes -preferred-modules= -single-line-exceptions=no - -[EXCEPTIONS] -overgeneral-exceptions=Exception,BaseException,StandardError,ArithmeticError,LookupError,EnvironmentError,EOFError,ImportError -ignore-on-exception=no - -[CLASSES] -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ -valid-classmethod-first-arg=cls -valid-metaclass-classmethod-first-arg=mcs -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make -bad-dunder-names=__authors__, - __version__, - __date__, - __credits__, - __status__, - __maintainer__, - __email__, - __contact__, - __copyright__, - __license__, - __uri__, - __url__, - __program__, - __description__, - __build__ - -[STRING] -check-str-concat-over-line-jumps=no diff --git a/ai.md b/ai.md deleted file mode 100644 index 482b933..0000000 --- a/ai.md +++ /dev/null @@ -1,661 +0,0 @@ -# FutureOSS - AI 专用项目介绍 - -## 项目概述 - -**FutureOSS** 是一个面向开发者的插件化运行时框架,采用「一切皆为插件」的设计理念。它是一个轻量级、安全、灵活的底层支撑系统,适用于构建微服务、开发工具链和可扩展的业务系统。 - -**核心设计哲学**:最小化核心框架,最大化插件扩展能力。核心框架仅提供最基本的插件加载和管理功能,所有其他功能(HTTP服务、Web界面、日志系统等)都通过插件实现。 - -## 技术栈 - -- **语言**: Python 3.10+ -- **主要依赖**: - - `click`: CLI 框架 - - `pyyaml`: 配置解析 - - `websockets`: WebSocket 支持 - - `psutil`: 系统监控 - - `cryptography`: 加密和签名验证 -- **架构**: 插件化微内核架构 -- **协议支持**: HTTP RESTful API, WebSocket, TCP HTTP -- **部署**: Docker 容器化支持 - -## 项目结构 - -``` -/root/future-oss/ -├── 📁 oss/ # 核心框架代码 -│ ├── cli.py # CLI 命令入口 -│ ├── config/ # 配置系统 -│ ├── logger/ # 日志系统 -│ ├── plugin/ # 插件框架核心 -│ │ ├── capabilities.py # 能力接口定义 -│ │ ├── loader.py # 插件加载器 -│ │ ├── manager.py # 插件生命周期管理 -│ │ └── types.py # 类型定义 -│ └── shared/ # 共享组件 -│ └── router.py # 统一路由系统 -├── 📁 store/ # 插件仓库(核心功能) -│ ├── @{FutureOSS}/ # 官方核心插件 -│ │ ├── plugin-loader/ # 插件加载器(核心) -│ │ ├── http-api/ # HTTP API 服务 -│ │ ├── http-tcp/ # TCP HTTP 服务 -│ │ ├── ws-api/ # WebSocket API -│ │ ├── dashboard/ # Web 控制台 -│ │ ├── dependency/ # 依赖解析 -│ │ ├── signature-verifier/ # 签名验证 -│ │ ├── plugin-bridge/ # 插件间通信 -│ │ ├── plugin-storage/ # 数据持久化 -│ │ ├── pkg-manager/ # 包管理 -│ │ ├── log-terminal/ # 日志终端 -│ │ ├── json-codec/ # JSON 编解码器 -│ │ └── webui/ # Web 用户界面 -│ └── @{Falck}/ # 社区插件 -│ ├── html-render/ # HTML 渲染引擎 -│ └── web-toolkit/ # Web 开发工具集 -├── 📁 data/ # 运行时数据目录 -├── 📁 static/ # 静态资源 -├── 📁 templates/ # 模板文件 -├── 📁 tools/ # 开发工具脚本 -├── 📁 video/ # 演示视频和文档 -├── 📄 pyproject.toml # Python 项目配置 -├── 📄 requirements.txt # Python 依赖 -├── 📄 docker-compose.yml # Docker 编排配置 -├── 📄 Dockerfile # Docker 构建文件 -├── 📄 README.md # 项目说明文档 -└── 📄 LICENSE # Apache 2.0 许可证 -``` - -## 核心架构 - -### 1. 插件系统架构 - -FutureOSS 采用三层插件架构: - -1. **核心框架层** (`oss/`): 提供最基本的插件加载和管理能力 -2. **核心插件层** (`store/@{FutureOSS}/`): 官方提供的核心功能插件 -3. **社区插件层** (`store/@{Falck}/`): 第三方社区插件 - -### 2. 插件生命周期 - -``` -加载 (load) → 初始化 (init) → 启动 (start) → 运行 (run) → 停止 (stop) -``` - -### 3. 插件元数据格式 - -每个插件必须包含 `manifest.json` 文件,格式如下: - -```json -{ - "metadata": { - "name": "插件名称", - "version": "版本号", - "author": "作者", - "description": "功能描述", - "type": "插件类型 (core/protocol/utility)" - }, - "config": { - "enabled": true/false, - "args": { - "参数名": "参数值" - } - }, - "dependencies": ["依赖插件列表"], - "permissions": ["所需权限列表"] -} -``` - -### 4. 安全机制 - -- **数字签名验证**: 每个插件包含 SIGNATURE 文件 -- **权限分级控制**: 插件声明所需权限 -- **沙箱环境**: 可选的安全隔离 -- **来源验证**: 插件作者命名空间 (@{作者名}) - -## 核心插件功能 - -### 系统核心插件 - -1. **plugin-loader**: 插件扫描、加载与生命周期管理(必需) -2. **http-api**: HTTP RESTful API 服务(端口 8080) -3. **http-tcp**: TCP 高性能 HTTP 服务(端口 8082) -4. **ws-api**: WebSocket API 服务(端口 8081) -5. **dashboard**: Web 可视化监控仪表盘 -6. **dependency**: 插件依赖解析与自动安装 -7. **signature-verifier**: 插件数字签名验证 -8. **plugin-bridge**: 插件间通信桥接 -9. **plugin-storage**: 插件数据持久化存储 -10. **pkg-manager**: 插件包管理(安装/卸载/搜索) -11. **log-terminal**: 日志终端实时输出 -12. **json-codec**: 统一 JSON 编解码器 -13. **webui**: Web 用户界面框架 - -### 社区插件 - -1. **html-render**: HTML 模板渲染引擎 -2. **web-toolkit**: Web 开发工具集(静态文件/模板/路由) - -### 禁用插件(默认不加载) - -1. **hot-reload**: 开发模式热重载 -2. **i18n**: 国际化支持 -3. **lifecycle**: 插件生命周期钩子 -4. **code-reviewer**: 代码审查工具 -5. **plugin-loader-pro**: 高级插件加载器 - -## PL 注入机制 - -PL 注入是 plugin-loader 插件提供的一种扩展机制,允许插件通过 `PL/` 文件夹向插件加载器注册自定义功能。 - -### 工作原理 - -插件加载器在启动时自动扫描所有插件,检查其 `manifest.json` 中是否声明了 `pl_injection` 配置项: - -``` -插件加载器启动 - ↓ -扫描所有插件 manifest.json - ↓ -检查 config.args.pl_injection - ├── true → 检查 PL/ 文件夹 - │ ├── 存在 PL/main.py → 沙箱执行 → 调用 register(injector) → ✅ 正常加载 - │ └── 缺少 PL/ 或 PL/main.py → ⚠️ 警告并 ❌ 拒绝加载 - └── false/未声明 → ✅ 正常加载(跳过 PL 检查) -``` - -### 使用方式 - -#### 1. 在 manifest.json 中声明 - -```json -{ - "config": { - "args": { - "pl_injection": true - } - } -} -``` - -#### 2. 创建 PL/main.py - -``` -store/@{作者名}/插件名/ -├── manifest.json # 声明 pl_injection: true -├── main.py # 插件主逻辑 -├── PL/ # PL 注入文件夹 -│ └── main.py # 注入逻辑(必须包含 register() 函数) -└── README.md -``` - -#### 3. 实现 register() 函数 - -```python -# PL/main.py -def register(injector): - """向插件加载器注册功能 - - Args: - injector: PLInjector 实例 - """ - # 注册普通功能 - injector.register_function("my_helper", my_func, "功能描述") - - # 注册 HTTP 路由 - injector.register_route("GET", "/pl/hello", handler) - - # 注册事件处理器 - injector.register_event_handler("plugin.started", on_started) -``` - -### 注入器 API - -| 方法 | 说明 | -|------|------| -| `register_function(name, func, description="")` | 注册注入功能 | -| `register_route(method, path, handler)` | 注册 HTTP 路由 | -| `register_event_handler(event_name, handler)` | 注册事件处理器 | -| `get_injected_functions(name=None)` | 获取已注册的注入功能 | -| `get_injection_info(plugin_name=None)` | 获取注入信息 | -| `has_injection(plugin_name)` | 检查插件是否有 PL 注入 | -| `get_registry_info()` | 获取注册表完整信息 | - -### 安全限制 - -PL 注入机制实施了多层安全限制: - -| 限制类型 | 具体措施 | -|---------|---------| -| **文件类型限制** | 禁止 PL 文件夹中包含 `.sh`、`.bat`、`.exe`、`.dll`、`.so` 等可执行文件 | -| **静态源码检查** | 编译前扫描源码,禁止导入 `os/sys/subprocess/socket/ctypes` 等系统模块,禁止 `exec/eval/compile/open/__import__` | -| **沙箱执行** | 在受限沙箱中执行 PL/main.py,仅提供安全的 builtins | -| **参数校验** | 功能名称、路由路径、HTTP 方法、事件名称均通过正则校验 | -| **数量限制** | 每个插件最多注册 50 个功能,每个名称最多被注册 10 次 | -| **异常安全** | 所有注册函数自动包装 try-catch,异常不影响主流程 | -| **调用者溯源** | 通过栈帧回溯自动识别调用者插件名,防止冒充注册 | - -### 行为说明 - -| 场景 | 结果 | -|------|------| -| `pl_injection: true` + 存在 `PL/main.py` | ✅ 正常加载,执行注入 | -| `pl_injection: true` + 缺少 `PL/` 文件夹 | ❌ 警告并拒绝加载该插件 | -| `pl_injection: true` + 存在 `PL/` 但缺少 `main.py` | ❌ 警告并拒绝加载该插件 | -| 未声明 `pl_injection` | ✅ 正常加载,跳过 PL 检查 | -| `pl_injection: false` | ✅ 正常加载,跳过 PL 检查 | - -## 开发与部署 - -### 开发环境设置 - -```bash -# 安装依赖 -pip install -e . - -# 启动开发服务器 -oss serve - -# 访问 Web 控制台 -# http://localhost:8080 -``` - -### Docker 部署 - -```bash -# 使用 Docker Compose -docker-compose up -d - -# 暴露端口 -# 8080: HTTP API + 网站 -# 8081: WebSocket -# 8082: HTTP TCP -``` - -### 插件开发 - -1. **创建插件目录**: `store/@{作者名}/{插件名}/` -2. **编写 manifest.json**: 定义插件元数据 -3. **实现 main.py**: 插件主逻辑 -4. **添加 SIGNATURE**: 数字签名(可选) -5. **测试插件**: 通过 pkg-manager 安装测试 - -## API 接口 - -### HTTP API (端口 8080) - -- `GET /health`: 健康检查 -- `GET /api/plugins`: 获取插件列表 -- `GET /api/plugins/{name}`: 获取插件详情 -- `POST /api/plugins/{name}/enable`: 启用插件 -- `POST /api/plugins/{name}/disable`: 禁用插件 - -### WebSocket API (端口 8081) - -- 实时日志推送 -- 系统状态监控 -- 插件事件通知 - -### TCP HTTP (端口 8082) - -- 高性能 HTTP 服务 -- 兼容 HTTP/1.1 协议 - -## 配置系统 - -### 配置文件位置 - -1. **全局配置**: `config.yaml` (可选) -2. **插件配置**: `store/@{作者名}/{插件名}/config.json` -3. **环境变量**: 支持 Docker 环境变量覆盖 - -### 配置优先级 - -``` -环境变量 > 全局配置文件 > 插件默认配置 -``` - -## 数据存储 - -### 数据目录结构 - -``` -data/ -├── html-render/ # 网站渲染文件 -├── web-toolkit/ # Web 工具配置 -├── plugin-storage/ # 插件持久化数据 -└── DCIM/ # 共享资源存储 -``` - -### 存储类型 - -1. **临时存储**: 内存缓存 -2. **持久化存储**: 文件系统 (data/ 目录) -3. **插件私有存储**: 每个插件独立存储空间 - -## 监控与日志 - -### 日志系统 - -- **日志级别**: DEBUG, INFO, WARNING, ERROR, CRITICAL -- **输出目标**: 控制台、文件、WebSocket -- **日志格式**: 结构化 JSON 日志 - -### 监控指标 - -- 系统资源使用率 (CPU/内存/磁盘) -- 插件运行状态 -- API 请求统计 -- 错误率和异常监控 - -## 扩展能力 - -### 自定义插件开发 - -FutureOSS 支持多种插件类型: - -1. **协议插件**: 实现网络协议 (HTTP/WebSocket/TCP) -2. **工具插件**: 提供开发工具功能 -3. **界面插件**: 扩展 Web 控制台 -4. **存储插件**: 实现数据存储后端 -5. **中间件插件**: 请求处理管道 - -### 插件间通信 - -- **事件系统**: 发布/订阅模式 -- **直接调用**: 通过插件管理器 -- **共享存储**: 通过 plugin-storage -- **消息队列**: 通过 plugin-bridge - -## 最佳实践 - -### 性能优化 - -1. **懒加载插件**: 按需加载非核心插件 -2. **连接池管理**: 数据库和网络连接复用 -3. **缓存策略**: 合理使用内存缓存 -4. **异步处理**: I/O 密集型操作异步化 - -### 安全建议 - -1. **签名验证**: 生产环境启用所有插件签名验证 -2. **权限最小化**: 插件只申请必要权限 -3. **沙箱隔离**: 不可信插件启用沙箱模式 -4. **定期更新**: 及时更新插件和安全补丁 - -### 高可用性 - -1. **健康检查**: 配置完整的健康检查端点 -2. **故障转移**: 关键插件多实例部署 -3. **监控告警**: 设置系统监控和告警 -4. **备份恢复**: 定期备份插件配置和数据 - -## 故障排除 - -### 常见问题 - -1. **插件加载失败**: 检查 manifest.json 格式和依赖 -2. **服务启动失败**: 检查端口冲突和权限 -3. **性能问题**: 监控系统资源使用情况 -4. **内存泄漏**: 检查插件资源释放 - -### 调试工具 - -1. **日志终端**: 实时查看系统日志 -2. **Web 控制台**: 可视化监控插件状态 -3. **API 接口**: 通过 REST API 获取系统信息 -4. **开发工具**: tools/ 目录下的辅助脚本 - -## 官方网站项目 - -### 项目位置 -`/root/future-oss/website/` - FutureOSS 官方宣传网站 - -### 技术栈 -- **后端**: Node.js + Express.js -- **前端**: EJS 模板引擎 + 原生 JavaScript + CSS3 -- **构建工具**: npm -- **开发工具**: VS Code 运行与调试配置 - -### 项目结构 -``` -website/ -├── 📁 public/ # 静态资源 -│ ├── css/ # 样式文件 -│ │ ├── main.css # 基础样式和变量 -│ │ ├── components.css # 组件样式 -│ │ ├── animations.css # 动画效果 -│ │ └── pages/ # 页面特定样式 -│ ├── js/ # JavaScript 文件 -│ │ ├── main.js # 主逻辑 -│ │ ├── router.js # 前端路由 -│ │ ├── animations.js # 动画控制 -│ │ └── pages/ # 页面特定脚本 -│ └── images/ # 图片资源 -├── 📁 src/ # 源代码 -│ ├── server.js # Express 服务器 -│ ├── router.js # 路由配置 -│ ├── controllers/ # 控制器 -│ │ ├── pagesController.js # 页面控制器 -│ │ └── apiController.js # API 控制器 -│ ├── middleware/ # 中间件 -│ │ └── performance.js # 性能优化中间件 -│ ├── components/ # 组件(预留) -│ ├── pages/ # 页面逻辑(预留) -│ └── styles/ # 样式源码(预留) -├── 📁 views/ # EJS 视图模板 -│ ├── layouts/ # 布局文件 -│ │ └── main.ejs # 主布局 -│ ├── pages/ # 页面模板 -│ │ ├── home.ejs # 首页 -│ │ ├── features.ejs # 特性页 -│ │ ├── architecture.ejs # 架构页 -│ │ └── plugins.ejs # 插件页 -│ └── partials/ # 局部组件 -│ ├── navbar.ejs # 导航栏 -│ └── footer.ejs # 页脚 -├── 📄 package.json # npm 配置 -├── 📄 package-lock.json # 依赖锁定 -└── 📄 server.log # 服务器日志 -``` - -### 核心功能 - -#### 1. 多页面网站 -- **首页** (`/`): 项目介绍和快速开始 -- **特性页** (`/features`): 核心功能展示 -- **架构页** (`/architecture`): 技术架构说明 -- **插件页** (`/plugins`): 插件生态系统 - -#### 2. 性能优化特性 -- **响应时间监控**: X-Response-Time 头部 -- **缓存控制**: 静态资源长期缓存 -- **压缩支持**: Gzip 和预压缩 -- **安全头**: CSP、XSS 保护等 -- **内存监控**: 实时内存使用监控 - -#### 3. 用户体验增强 -- **加载动画**: 页面加载旋转指示器 -- **页面切换动画**: 平滑的页面过渡效果 -- **图片懒加载**: 延迟加载非视口图片 -- **骨架屏**: 内容加载占位符 -- **响应式设计**: 完整的移动端适配 - -#### 4. API 接口 -- `GET /api/health`: 健康检查 -- `GET /api/metrics`: 性能指标 -- `GET /api/info`: 服务器信息 -- `GET /api/stress-test`: 压力测试(仅开发环境) - -### 技术特点 - -#### 服务器特性 -- **端口自动切换**: 8080被占用时自动使用8081、8082等 -- **优雅关闭**: 支持 SIGTERM/SIGINT 信号优雅关闭 -- **错误处理**: 完善的错误处理和404页面 -- **中间件栈**: 完整的性能优化中间件 - -#### 前端特性 -- **组件化设计**: 导航栏、页脚等独立组件 -- **前端路由**: 支持无刷新页面切换 -- **CSS 变量**: 统一的主题变量系统 -- **动画系统**: 丰富的CSS动画和过渡效果 - -#### 开发工具 -- **VS Code 调试配置**: 支持直接运行与调试 -- **热重载支持**: nodemon 开发模式 -- **性能监控**: 实时性能指标输出 -- **日志系统**: 结构化服务器日志 - -### 部署与运行 - -#### 开发模式 -```bash -cd /root/future-oss/website -npm install -npm start -# 访问 http://localhost:8080 (或自动切换的端口) -``` - -#### 生产部署 -```bash -# 使用 PM2 进程管理 -pm2 start src/server.js --name "futureoss-website" - -# 使用 Docker -docker build -t futureoss-website . -docker run -p 8080:8080 futureoss-website -``` - -#### VS Code 调试 -1. 打开运行与调试面板 (Ctrl+Shift+D) -2. 选择 "FutureOSS 网站: 启动Node.js服务器" -3. 按 F5 启动调试 - -### 性能优化措施 - -#### 已实施的优化 -1. **响应头优化**: 缓存控制、安全头、压缩头 -2. **静态资源优化**: 长期缓存、预压缩支持 -3. **代码分割**: 按页面加载CSS和JS -4. **图片优化**: 懒加载、占位符、响应式图片 -5. **动画优化**: 减少重绘、will-change提示 - -#### 监控指标 -- **响应时间**: X-Response-Time 头部 -- **内存使用**: X-Memory-Usage 头部(开发环境) -- **慢响应日志**: 超过1秒的请求记录 -- **错误率**: 500错误监控和记录 - -### 已知问题和解决方案 - -#### 1. ERR_HTTP_HEADERS_SENT 错误 -- **问题**: 在响应已发送后设置响应头 -- **原因**: `responseTime` 中间件在 `finish` 事件中设置头部 -- **解决方案**: 使用 `headers` 事件或重写 `end` 方法 -- **修复代码**: - ```javascript - // 错误写法(在 finish 事件中): - res.on('finish', () => { - res.setHeader('X-Response-Time', `${duration}ms`); // 错误! - }); - - // 正确写法(重写 end 方法): - const originalEnd = res.end; - res.end = function(...args) { - if (!res.headersSent) { - res.setHeader('X-Response-Time', `${duration}ms`); - } - return originalEnd.apply(this, args); - }; - ``` - -#### 2. CSS 加载问题 -- **问题**: 页面只显示纯文本,CSS未加载 -- **原因**: EJS 布局系统未正确配置 -- **解决方案**: 安装并配置 `express-ejs-layouts` -- **修复步骤**: - 1. `npm install express-ejs-layouts` - 2. 在 server.js 中添加中间件 - 3. 设置默认布局 `app.set('layout', 'layouts/main')` - -#### 3. 加载性能问题 -- **问题**: 页面加载慢,无加载反馈 -- **解决方案**: 添加加载动画和性能优化 -- **实施措施**: - 1. 添加页面加载旋转动画 - 2. 实现图片懒加载 - 3. 添加骨架屏占位符 - 4. 优化静态资源缓存 - -### 扩展和定制 - -#### 添加新页面 -1. 在 `views/pages/` 创建新的 `.ejs` 文件 -2. 在 `controllers/pagesController.js` 添加渲染函数 -3. 在 `src/router.js` 添加路由 -4. 在 `public/css/pages/` 添加页面特定CSS -5. 在 `public/js/pages/` 添加页面特定JS - -#### 添加新组件 -1. 在 `views/partials/` 创建组件 `.ejs` 文件 -2. 在 `public/css/components.css` 添加组件样式 -3. 在布局或页面中使用 `<%- include('partials/组件名') %>` - -#### 主题定制 -通过修改 CSS 变量实现主题切换: -```css -:root { - --primary-color: #1e6fbb; /* 主色调 */ - --text-primary: #333333; /* 主要文字 */ - --bg-primary: #ffffff; /* 背景色 */ - /* 更多变量... */ -} -``` - -### 维护指南 - -#### 日常维护 -1. **日志监控**: 定期检查 `server.log` 文件 -2. **性能监控**: 关注慢响应日志 -3. **错误处理**: 及时修复报告的错误 -4. **依赖更新**: 定期更新 npm 包 - -#### 故障排除 -1. **服务器无法启动**: 检查端口占用,查看日志 -2. **页面样式异常**: 检查CSS文件路径和网络请求 -3. **API 无响应**: 检查路由配置和控制器 -4. **性能下降**: 使用 `/api/metrics` 接口诊断 - -#### 备份策略 -1. **代码备份**: Git 版本控制 -2. **配置备份**: 备份 `package.json` 和服务器配置 -3. **数据备份**: 备份用户上传内容(如有) -4. **日志归档**: 定期归档服务器日志 - -## 未来发展方向 - -### 短期规划 - -1. 插件市场功能完善 -2. 更多官方插件开发 -3. 性能优化和稳定性提升 -4. 官方网站功能增强 - -### 长期愿景 - -1. 跨语言插件支持 -2. 云原生集成 -3. 企业级功能扩展 -4. 生态系统建设 - -## 注意事项 - -1. **生产部署**: 建议使用 Docker 容器化部署 -2. **数据备份**: 定期备份 data/ 目录重要数据 -3. **安全更新**: 关注安全公告并及时更新 -4. **社区支持**: 通过 Gitee Issues 获取帮助 -5. **网站维护**: 定期更新官方网站内容和功能 - ---- - -*本文件专为 AI 助手设计,提供 FutureOSS 项目的全面技术概述,帮助 AI 理解项目架构、功能和使用方式。更新日期:2026年4月19日* \ No newline at end of file diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index b89fba3..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,39 +0,0 @@ -"""CLI 命令单元测试""" -import pytest -from click.testing import CliRunner -from oss.cli import cli, version, serve - - -class TestCLI: - """测试 CLI 命令""" - - def test_version_command(self): - """测试版本命令""" - runner = CliRunner() - result = runner.invoke(version) - - assert result.exit_code == 0 - assert "Future OSS" in result.output - assert "1.2.0" in result.output - - def test_cli_help(self): - """测试 CLI 帮助信息""" - runner = CliRunner() - result = runner.invoke(cli, ['--help']) - - assert result.exit_code == 0 - assert "Future OSS" in result.output - assert "serve" in result.output - assert "version" in result.output - - def test_serve_command_exists(self): - """测试 serve 命令存在""" - runner = CliRunner() - result = runner.invoke(serve, ['--help']) - - assert result.exit_code == 0 - assert "启动 Future OSS" in result.output - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) diff --git a/tests/test_config.py b/tests/test_config.py deleted file mode 100644 index 6e131c1..0000000 --- a/tests/test_config.py +++ /dev/null @@ -1,105 +0,0 @@ -"""配置管理测试""" -import os -import pytest -from pathlib import Path -import tempfile -import json - -from oss.config.config import Config - - -class TestConfig: - """配置管理测试类""" - - def test_default_values(self): - """测试默认配置值""" - config = Config() - assert config.http_api_port == 8080 - assert config.http_tcp_port == 8082 - assert config.host == "0.0.0.0" - assert config.log_level == "INFO" - assert config.permission_check is True - - def test_env_override(self, monkeypatch): - """测试环境变量覆盖""" - monkeypatch.setenv("HTTP_API_PORT", "9999") - monkeypatch.setenv("LOG_LEVEL", "DEBUG") - - config = Config() - assert config.http_api_port == 9999 - assert config.log_level == "DEBUG" - - def test_file_config(self): - """测试配置文件加载""" - with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: - json.dump({"HTTP_API_PORT": 7777, "LOG_LEVEL": "WARNING"}, f) - temp_path = f.name - - try: - config = Config(temp_path) - assert config.http_api_port == 7777 - assert config.log_level == "WARNING" - finally: - os.unlink(temp_path) - - def test_env_priority_over_file(self, monkeypatch): - """测试环境变量优先级高于配置文件""" - with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: - json.dump({"HTTP_API_PORT": 7777}, f) - temp_path = f.name - - try: - monkeypatch.setenv("HTTP_API_PORT", "8888") - config = Config(temp_path) - assert config.http_api_port == 8888 # 环境变量优先 - finally: - os.unlink(temp_path) - monkeypatch.delenv("HTTP_API_PORT", raising=False) - - def test_get_set(self): - """测试 get/set 方法""" - config = Config() - assert config.get("HTTP_API_PORT") == 8080 - config.set("HTTP_API_PORT", 6666) - assert config.get("HTTP_API_PORT") == 6666 - - def test_properties(self): - """测试属性访问""" - config = Config() - assert isinstance(config.data_dir, Path) - assert isinstance(config.store_dir, Path) - assert config.data_dir.name == "data" - assert config.store_dir.name == "store" - - def test_all_method(self): - """测试 all() 方法返回所有配置""" - config = Config() - all_config = config.all() - assert "HTTP_API_PORT" in all_config - assert "HOST" in all_config - assert len(all_config) > 5 - - def test_bool_conversion(self, monkeypatch): - """测试布尔值转换""" - monkeypatch.setenv("PERMISSION_CHECK", "false") - config = Config() - assert config.permission_check is False - - monkeypatch.setenv("PERMISSION_CHECK", "true") - config = Config() - assert config.permission_check is True - - def test_int_conversion(self, monkeypatch): - """测试整数转换""" - monkeypatch.setenv("MAX_WORKERS", "8") - config = Config() - assert config.get("MAX_WORKERS") == 8 - - # 无效值应该保持默认 - monkeypatch.setenv("MAX_WORKERS", "invalid") - config = Config() - assert config.get("MAX_WORKERS") == 4 # 默认值 - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) diff --git a/tests/test_plugin_loader.py b/tests/test_plugin_loader.py deleted file mode 100644 index 49f05d5..0000000 --- a/tests/test_plugin_loader.py +++ /dev/null @@ -1,48 +0,0 @@ -"""插件加载器单元测试""" -import pytest -from pathlib import Path -from oss.plugin.loader import PluginLoader - - -class TestPluginLoader: - """测试插件加载器核心功能""" - - def test_loader_initialization(self): - """测试加载器初始化""" - loader = PluginLoader() - assert loader.loaded == {} - - def test_load_nonexistent_plugin(self): - """测试加载不存在的插件""" - loader = PluginLoader() - result = loader.load_core_plugin("nonexistent-plugin") - assert result is None - - def test_load_plugin_loader(self): - """测试加载 plugin-loader 核心插件""" - loader = PluginLoader() - result = loader.load_core_plugin("plugin-loader") - - assert result is not None - assert "instance" in result - assert "module" in result - assert "path" in result - assert "name" in result - assert result["name"] == "plugin-loader" - assert hasattr(result["instance"], "init") - assert hasattr(result["instance"], "start") - assert hasattr(result["instance"], "stop") - - def test_loaded_plugins_tracking(self): - """测试已加载插件跟踪""" - loader = PluginLoader() - initial_count = len(loader.loaded) - - loader.load_core_plugin("plugin-loader") - - assert len(loader.loaded) == initial_count + 1 - assert "plugin-loader" in loader.loaded - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py deleted file mode 100644 index 8656d9a..0000000 --- a/tests/test_plugin_manager.py +++ /dev/null @@ -1,53 +0,0 @@ -"""插件管理器单元测试""" -import pytest -from oss.plugin.manager import PluginManager - - -class TestPluginManager: - """测试插件管理器核心功能""" - - def test_manager_initialization(self): - """测试管理器初始化""" - manager = PluginManager() - assert manager.plugin_loader is None - assert manager.loader is not None - - def test_manager_load(self): - """测试管理器加载 plugin-loader""" - manager = PluginManager() - manager.load() - - assert manager.plugin_loader is not None - assert hasattr(manager.plugin_loader, "init") - assert hasattr(manager.plugin_loader, "start") - assert hasattr(manager.plugin_loader, "stop") - - def test_manager_start_without_load(self): - """测试未加载时启动(应安全处理)""" - manager = PluginManager() - # 不应抛出异常 - manager.start() - - def test_manager_stop_without_load(self): - """测试未加载时停止(应安全处理)""" - manager = PluginManager() - # 不应抛出异常 - manager.stop() - - def test_manager_lifecycle(self): - """测试完整生命周期""" - manager = PluginManager() - - # 加载 - manager.load() - assert manager.plugin_loader is not None - - # 启动(会初始化所有插件) - # 注意:实际启动需要完整环境,这里只测试方法存在 - assert callable(manager.plugin_loader.init) - assert callable(manager.plugin_loader.start) - assert callable(manager.plugin_loader.stop) - - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) diff --git a/tools/__pycache__/sign_core_plugins.cpython-312.pyc b/tools/__pycache__/sign_core_plugins.cpython-312.pyc deleted file mode 100644 index 4cf7f170f55059a0c56240d131ddac3dffc71e74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4301 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 diff --git a/tools/__pycache__/sign_plugins.cpython-312.pyc b/tools/__pycache__/sign_plugins.cpython-312.pyc deleted file mode 100644 index 57607e6c0e5a6f31f0d761f8427d5e6bb0a3408b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8961 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 ITt~qEzZ=zGy#N3J diff --git a/tools/__pycache__/test_signature.cpython-312.pyc b/tools/__pycache__/test_signature.cpython-312.pyc deleted file mode 100644 index 1a84f4b4e6268990ead6e314cd26b57199061382..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8531 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 str: - """计算插件目录的内容哈希""" - hasher = hashlib.sha256() - files_to_hash = [] - for file_path in sorted(plugin_dir.rglob("*")): - if file_path.is_file() and file_path.name != "SIGNATURE": - rel_path = file_path.relative_to(plugin_dir) - files_to_hash.append((str(rel_path), file_path)) - - for rel_path, file_path in files_to_hash: - hasher.update(rel_path.encode("utf-8")) - hasher.update(file_path.read_bytes()) - - return hasher.hexdigest() - - -def sign_plugin(plugin_dir: Path): - """为插件签名""" - # 加载私钥 - print(f"加载私钥: {PRIVATE_KEY_FILE}") - private_key = serialization.load_pem_private_key( - PRIVATE_KEY_FILE.read_bytes(), - password=None, - backend=default_backend() - ) - - # 计算插件哈希 - print(f"计算插件目录哈希: {plugin_dir.name}...") - plugin_hash = compute_plugin_hash(plugin_dir) - print(f"哈希: {plugin_hash}") - - # 签名 - signed_data = f"FutureOSS:{plugin_hash}".encode("utf-8") - signature = private_key.sign( - signed_data, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA256() - ) - - # 写入签名文件 - sig_data = { - "signature": base64.b64encode(signature).decode(), - "signer": "FutureOSS", - "algorithm": "RSA-SHA256", - "timestamp": time.time(), - "plugin_hash": plugin_hash, - "author": "FutureOSS" - } - - signature_file = plugin_dir / "SIGNATURE" - signature_file.write_text(json.dumps(sig_data, indent=2)) - print(f"✓ 已签名: {plugin_dir.name}") - - -def main(): - if not PRIVATE_KEY_FILE.exists(): - print(f"错误: 私钥文件不存在: {PRIVATE_KEY_FILE}") - sys.exit(1) - - store_dir = PROJECT_ROOT / "store" / "@{FutureOSS}" - - for plugin_name in PLUGINS: - plugin_dir = store_dir / plugin_name - if plugin_dir.exists(): - sign_plugin(plugin_dir) - else: - print(f"警告: 插件目录不存在: {plugin_dir}") - - print("\n完成!") - - -if __name__ == "__main__": - main() diff --git a/tools/sign_plugin_loader.py b/tools/sign_plugin_loader.py deleted file mode 100644 index f9914b6..0000000 --- a/tools/sign_plugin_loader.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -""" -为 plugin-loader 插件签名 -""" - -import sys -import json -import base64 -import hashlib -import time -from pathlib import Path - -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.backends import default_backend - -# ========== 配置 ========== -PROJECT_ROOT = Path(__file__).parent.parent -PRIVATE_KEY_FILE = PROJECT_ROOT / "data" / "signature-verifier" / "keys" / "private" / "futureoss_private.pem" -PLUGIN_DIR = PROJECT_ROOT / "store" / "@{FutureOSS}" / "plugin-loader" - - -def compute_plugin_hash(plugin_dir: Path) -> str: - """计算插件目录的内容哈希""" - hasher = hashlib.sha256() - files_to_hash = [] - for file_path in sorted(plugin_dir.rglob("*")): - if file_path.is_file() and file_path.name != "SIGNATURE": - rel_path = file_path.relative_to(plugin_dir) - files_to_hash.append((str(rel_path), file_path)) - - for rel_path, file_path in files_to_hash: - hasher.update(rel_path.encode("utf-8")) - hasher.update(file_path.read_bytes()) - - return hasher.hexdigest() - - -def sign_plugin(): - """为插件签名""" - # 加载私钥 - print(f"加载私钥: {PRIVATE_KEY_FILE}") - private_key = serialization.load_pem_private_key( - PRIVATE_KEY_FILE.read_bytes(), - password=None, - backend=default_backend() - ) - - # 计算插件哈希 - print(f"计算插件目录哈希...") - plugin_hash = compute_plugin_hash(PLUGIN_DIR) - print(f"哈希: {plugin_hash}") - - # 签名 - signed_data = f"FutureOSS:{plugin_hash}".encode("utf-8") - signature = private_key.sign( - signed_data, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA256() - ) - - # 写入签名文件 - sig_data = { - "signature": base64.b64encode(signature).decode(), - "signer": "FutureOSS", - "algorithm": "RSA-SHA256", - "timestamp": time.time(), - "plugin_hash": plugin_hash, - "author": "FutureOSS" - } - - signature_file = PLUGIN_DIR / "SIGNATURE" - signature_file.write_text(json.dumps(sig_data, indent=2)) - print(f"\n✓ 签名成功!") - print(f" 插件: {PLUGIN_DIR.name}") - print(f" 签名文件: {signature_file}") - print(f" 算法: RSA-SHA256") - print(f" 时间戳: {time.strftime('%Y-%m-%d %H:%M:%S')}") - - -if __name__ == "__main__": - try: - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import padding - from cryptography.hazmat.backends import default_backend - except ImportError: - print("错误: 未安装 cryptography 库") - print("运行: pip install cryptography") - sys.exit(1) - - if not PLUGIN_DIR.exists(): - print(f"错误: 插件目录不存在: {PLUGIN_DIR}") - sys.exit(1) - - if not PRIVATE_KEY_FILE.exists(): - print(f"错误: 私钥文件不存在: {PRIVATE_KEY_FILE}") - sys.exit(1) - - sign_plugin() diff --git a/tools/sign_plugins.py b/tools/sign_plugins.py deleted file mode 100644 index 80ff3a3..0000000 --- a/tools/sign_plugins.py +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env python3 -""" -密钥生成与插件签名工具 -- 生成 Falck 官方密钥对 -- 为所有官方插件签名 -""" - -import sys -import json -import base64 -import hashlib -import time -from pathlib import Path - -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding, rsa -from cryptography.hazmat.backends import default_backend - -# ========== 配置 ========== -PROJECT_ROOT = Path(__file__).parent.parent # 修复:tools 的上级目录 -KEY_DIR = PROJECT_ROOT / "data" / "signature-verifier" / "keys" -STORE_DIR = PROJECT_ROOT / "store" - -# 官方作者目录 -OFFICIAL_AUTHORS = ["FutureOSS", "Falck"] - - -def generate_keypair(author: str): - """生成 4096 位 RSA 密钥对""" - print(f"\n{'='*60}") - print(f"生成 {author} 的密钥对...") - print(f"{'='*60}") - - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=4096, - backend=default_backend() - ) - public_key = private_key.public_key() - - # 保存私钥 - private_pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.PKCS8, - encryption_algorithm=serialization.NoEncryption() - ) - priv_dir = KEY_DIR / "private" - priv_dir.mkdir(parents=True, exist_ok=True) - priv_file = priv_dir / f"{author.lower()}_private.pem" - priv_file.write_bytes(private_pem) - print(f"私钥已保存: {priv_file}") - - # 保存公钥 - public_pem = public_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo - ) - pub_dir = KEY_DIR / "public" - pub_dir.mkdir(parents=True, exist_ok=True) - pub_file = pub_dir / f"{author}.pem" - pub_file.write_bytes(public_pem) - print(f"公钥已保存: {pub_file}") - - # 显示公钥(用于嵌入代码) - print(f"\n--- 公钥 PEM (用于嵌入 main.py) ---") - print(public_pem.decode()) - print(f"--- END ---\n") - - return private_key, public_key - - -def compute_plugin_hash(plugin_dir: Path) -> str: - """计算插件目录的内容哈希""" - hasher = hashlib.sha256() - files_to_hash = [] - for file_path in sorted(plugin_dir.rglob("*")): - if file_path.is_file() and file_path.name != "SIGNATURE": - rel_path = file_path.relative_to(plugin_dir) - files_to_hash.append((str(rel_path), file_path)) - - for rel_path, file_path in files_to_hash: - hasher.update(rel_path.encode("utf-8")) - hasher.update(file_path.read_bytes()) - - return hasher.hexdigest() - - -def sign_plugin(plugin_dir: Path, private_key, signer_name: str, author: str): - """为插件生成签名""" - plugin_hash = compute_plugin_hash(plugin_dir) - - # 签名 - signed_data = f"{author}:{plugin_hash}".encode("utf-8") - signature = private_key.sign( - signed_data, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA256() - ) - - # 写入签名文件 - sig_data = { - "signature": base64.b64encode(signature).decode(), - "signer": signer_name, - "algorithm": "RSA-SHA256", - "timestamp": time.time(), - "plugin_hash": plugin_hash, - "author": author - } - - signature_file = plugin_dir / "SIGNATURE" - signature_file.write_text(json.dumps(sig_data, indent=2)) - print(f" ✓ 已签名: {plugin_dir.name} (哈希: {plugin_hash[:16]}...)") - - -def sign_all_plugins(private_key): - """为所有官方插件签名""" - for author in OFFICIAL_AUTHORS: - author_dir = STORE_DIR / f"@{{{author}}}" - if not author_dir.exists(): - print(f"\n警告: 作者目录不存在: {author_dir}") - continue - - print(f"\n{'='*60}") - print(f"为 @{{{author}}} 的插件签名...") - print(f"{'='*60}") - - count = 0 - for plugin_dir in sorted(author_dir.iterdir()): - if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists(): - sign_plugin(plugin_dir, private_key, author, author) - count += 1 - - print(f"\n完成: 已签名 {count} 个 @{author} 插件") - - -def main(): - print("="*60) - print("FutureOSS 插件签名工具") - print("="*60) - - # 检查 cryptography - try: - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import padding, rsa - from cryptography.hazmat.backends import default_backend - except ImportError: - print("错误: 未安装 cryptography 库") - print("运行: pip install cryptography") - sys.exit(1) - - # 步骤 1: 生成密钥对 - print("\n步骤 1: 生成 Falck 官方密钥对...") - falck_priv, falck_pub = generate_keypair("Falck") - - print("\n步骤 1b: 生成 FutureOSS 官方密钥对...") - foss_priv, foss_pub = generate_keypair("FutureOSS") - - # 步骤 2: 为所有官方插件签名(使用对应的密钥) - print("\n步骤 2: 为所有官方插件签名...") - - # Falck 的插件用 Falck 密钥签名 - falck_dir = STORE_DIR / "@{Falck}" - if falck_dir.exists(): - print(f"\n{'='*60}") - print("为 @{Falck} 的插件使用 Falck 密钥签名...") - print(f"{'='*60}") - for plugin_dir in sorted(falck_dir.iterdir()): - if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists(): - sign_plugin(plugin_dir, falck_priv, "Falck", "Falck") - - # FutureOSS 的插件用 FutureOSS 密钥签名 - foss_dir = STORE_DIR / "@{FutureOSS}" - if foss_dir.exists(): - print(f"\n{'='*60}") - print("为 @{FutureOSS} 的插件使用 FutureOSS 密钥签名...") - print(f"{'='*60}") - for plugin_dir in sorted(foss_dir.iterdir()): - if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists(): - sign_plugin(plugin_dir, foss_priv, "FutureOSS", "FutureOSS") - - print("\n" + "="*60) - print("全部完成!") - print("="*60) - print(f"\n密钥位置: {KEY_DIR}") - print("请将公钥嵌入 signature-verifier/main.py 的 FALCK_PUBLIC_KEY_PEM 变量") - print("并妥善保管私钥,不要提交到版本控制系统!") - - -if __name__ == "__main__": - main() diff --git a/tools/sign_single_plugin.py b/tools/sign_single_plugin.py deleted file mode 100644 index cfe32b2..0000000 --- a/tools/sign_single_plugin.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -""" -为单个插件签名 -""" - -import sys -import json -import base64 -import hashlib -import time -from pathlib import Path - -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.backends import default_backend - -# ========== 配置 ========== -PROJECT_ROOT = Path(__file__).parent.parent -PRIVATE_KEY_FILE = PROJECT_ROOT / "data" / "signature-verifier" / "keys" / "private" / "futureoss_private.pem" -PLUGIN_DIR = PROJECT_ROOT / "store" / "@{FutureOSS}" / "log-terminal" - - -def compute_plugin_hash(plugin_dir: Path) -> str: - """计算插件目录的内容哈希""" - hasher = hashlib.sha256() - files_to_hash = [] - for file_path in sorted(plugin_dir.rglob("*")): - if file_path.is_file() and file_path.name != "SIGNATURE": - rel_path = file_path.relative_to(plugin_dir) - files_to_hash.append((str(rel_path), file_path)) - - for rel_path, file_path in files_to_hash: - hasher.update(rel_path.encode("utf-8")) - hasher.update(file_path.read_bytes()) - - return hasher.hexdigest() - - -def sign_plugin(): - """为插件签名""" - # 加载私钥 - print(f"加载私钥: {PRIVATE_KEY_FILE}") - private_key = serialization.load_pem_private_key( - PRIVATE_KEY_FILE.read_bytes(), - password=None, - backend=default_backend() - ) - - # 计算插件哈希 - print(f"计算插件目录哈希...") - plugin_hash = compute_plugin_hash(PLUGIN_DIR) - print(f"哈希: {plugin_hash}") - - # 签名 - signed_data = f"FutureOSS:{plugin_hash}".encode("utf-8") - signature = private_key.sign( - signed_data, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - hashes.SHA256() - ) - - # 写入签名文件 - sig_data = { - "signature": base64.b64encode(signature).decode(), - "signer": "FutureOSS", - "algorithm": "RSA-SHA256", - "timestamp": time.time(), - "plugin_hash": plugin_hash, - "author": "FutureOSS" - } - - signature_file = PLUGIN_DIR / "SIGNATURE" - signature_file.write_text(json.dumps(sig_data, indent=2)) - print(f"\n✓ 签名成功!") - print(f" 插件: {PLUGIN_DIR.name}") - print(f" 签名文件: {signature_file}") - print(f" 算法: RSA-SHA256") - print(f" 时间戳: {time.strftime('%Y-%m-%d %H:%M:%S')}") - - -if __name__ == "__main__": - try: - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import padding - from cryptography.hazmat.backends import default_backend - except ImportError: - print("错误: 未安装 cryptography 库") - print("运行: pip install cryptography") - sys.exit(1) - - if not PLUGIN_DIR.exists(): - print(f"错误: 插件目录不存在: {PLUGIN_DIR}") - sys.exit(1) - - if not PRIVATE_KEY_FILE.exists(): - print(f"错误: 私钥文件不存在: {PRIVATE_KEY_FILE}") - sys.exit(1) - - sign_plugin() diff --git a/tools/test_signature.py b/tools/test_signature.py deleted file mode 100644 index beea771..0000000 --- a/tools/test_signature.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python3 -""" -签名验证测试脚本 -测试签名验证功能是否正常工作 -""" - -import sys -import json -import base64 -import hashlib -from pathlib import Path - -# 添加项目路径 -sys.path.insert(0, str(Path(__file__).parent.parent)) -sys.path.insert(0, str(Path(__file__).parent.parent / "store/@{FutureOSS}/signature-verifier")) - -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.backends import default_backend -from cryptography.exceptions import InvalidSignature - -# 导入签名验证插件 -from main import SignatureVerifier, SignatureSigner - - -def test_verify_official_plugins(): - """测试验证所有已签名的官方插件""" - print("="*60) - print("测试 1: 验证所有官方插件签名") - print("="*60) - - store_dir = Path(__file__).parent.parent / "store" - verifier = SignatureVerifier(key_dir="./data/signature-verifier/keys") - - authors = ["FutureOSS", "Falck"] - total = 0 - passed = 0 - failed = 0 - - for author in authors: - author_dir = store_dir / f"@{{{author}}}" - if not author_dir.exists(): - continue - - print(f"\n--- @{author} ---") - for plugin_dir in sorted(author_dir.iterdir()): - if plugin_dir.is_dir() and (plugin_dir / "manifest.json").exists(): - total += 1 - valid, msg = verifier.verify_plugin(plugin_dir, author) - status = "✅ 通过" if valid else "❌ 失败" - print(f" {status}: {plugin_dir.name} - {msg}") - if valid: - passed += 1 - else: - failed += 1 - - print(f"\n{'='*60}") - print(f"结果: {passed}/{total} 通过, {failed} 失败") - print(f"{'='*60}") - return failed == 0 - - -def test_tamper_detection(): - """测试篡改检测""" - print("\n" + "="*60) - print("测试 2: 篡改检测") - print("="*60) - - store_dir = Path(__file__).parent.parent / "store" - verifier = SignatureVerifier(key_dir="./data/signature-verifier/keys") - - # 选择一个测试插件 - test_plugin = store_dir / "@{FutureOSS}" / "dashboard" - if not test_plugin.exists(): - print("跳过: dashboard 插件不存在") - return True - - # 验证原始签名 - valid_before, msg_before = verifier.verify_plugin(test_plugin, "FutureOSS") - print(f"\n篡改前: {'✅ 有效' if valid_before else '❌ 无效'} - {msg_before}") - - if not valid_before: - print("警告: 原始签名已无效,跳过篡改测试") - return False - - # 创建一个临时篡改文件 - tamper_file = test_plugin / "__tamper_test__.tmp" - tamper_file.write_text("tampered content") - - # 验证篡改后的签名 - valid_after, msg_after = verifier.verify_plugin(test_plugin, "FutureOSS") - print(f"篡改后: {'✅ 有效' if valid_after else '❌ 无效'} - {msg_after}") - - # 清理 - tamper_file.unlink() - - # 再次验证应该恢复有效 - valid_clean, msg_clean = verifier.verify_plugin(test_plugin, "FutureOSS") - print(f"清理后: {'✅ 有效' if valid_clean else '❌ 无效'} - {msg_clean}") - - # 预期:篡改后无效,清理后有效 - success = not valid_after and valid_clean - print(f"\n{'='*60}") - print(f"篡改检测: {'✅ 成功' if success else '❌ 失败'}") - print(f"{'='*60}") - return success - - -def test_missing_signature(): - """测试缺失签名文件""" - print("\n" + "="*60) - print("测试 3: 缺失签名检测") - print("="*60) - - store_dir = Path(__file__).parent.parent / "store" - verifier = SignatureVerifier(key_dir="./data/signature-verifier/keys") - - # 选择一个插件并临时移除签名 - test_plugin = store_dir / "@{FutureOSS}" / "json-codec" - if not test_plugin.exists(): - print("跳过: json-codec 插件不存在") - return True - - sig_file = test_plugin / "SIGNATURE" - if not sig_file.exists(): - print("跳过: json-codec 没有签名文件") - return True - - # 备份签名 - backup = sig_file.read_text() - sig_file.unlink() - - # 验证 - valid, msg = verifier.verify_plugin(test_plugin, "FutureOSS") - print(f"无签名: {'✅ 有效' if valid else '❌ 无效'} - {msg}") - - # 恢复 - sig_file.write_text(backup) - - valid_restored, msg_restored = verifier.verify_plugin(test_plugin, "FutureOSS") - print(f"恢复后: {'✅ 有效' if valid_restored else '❌ 无效'} - {msg_restored}") - - success = not valid and valid_restored - print(f"\n{'='*60}") - print(f"缺失签名检测: {'✅ 成功' if success else '❌ 失败'}") - print(f"{'='*60}") - return success - - -def test_official_check(): - """测试 is_official_plugin 方法""" - print("\n" + "="*60) - print("测试 4: 官方插件识别") - print("="*60) - - store_dir = Path(__file__).parent.parent / "store" - verifier = SignatureVerifier(key_dir="./data/signature-verifier/keys") - - # 测试官方插件 - official_plugin = store_dir / "@{FutureOSS}" / "dashboard" - is_official = verifier.is_official_plugin(official_plugin) - print(f"dashboard 是官方插件: {'✅ 是' if is_official else '❌ 否'}") - - success = is_official - print(f"\n{'='*60}") - print(f"官方插件识别: {'✅ 成功' if success else '❌ 失败'}") - print(f"{'='*60}") - return success - - -def main(): - print("FutureOSS 签名验证系统测试") - print("="*60) - - results = [] - - results.append(("官方插件验证", test_verify_official_plugins())) - results.append(("篡改检测", test_tamper_detection())) - results.append(("缺失签名检测", test_missing_signature())) - results.append(("官方插件识别", test_official_check())) - - print("\n" + "="*60) - print("测试总结") - print("="*60) - - for name, passed in results: - status = "✅ 通过" if passed else "❌ 失败" - print(f" {status}: {name}") - - all_passed = all(r[1] for r in results) - print(f"\n{'='*60}") - print(f"总体结果: {'✅ 全部通过' if all_passed else '❌ 有失败'}") - print(f"{'='*60}") - - return 0 if all_passed else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/video/architecture.html b/video/architecture.html deleted file mode 100644 index 8166329..0000000 --- a/video/architecture.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - FutureOSS - 架构解析 - - - -
-
-
-
- -
🏗️ 架构解析
- -
-
- -
-
前端层
-
🌐WebUI 容器
-
📊Dashboard
-
💻Log Terminal
-
- -
- - -
-
服务层
-
🔌HTTP API
:8080
-
🔌TCP Server
:8082
-
🔌WebSocket
-
- -
- - -
-
核心层
-
📦Plugin Loader
-
🌉Plugin Bridge
-
🔐签名验证
-
- -
- - -
-
基础设施
-
💾Plugin Storage
-
🎨Logger
-
📦Pkg Manager
-
🔗Dependency
-
-
- -
一切皆为插件,从核心到界面
-
- -
-
- - - -
- - - - diff --git a/video/index.html b/video/index.html deleted file mode 100644 index f7a58a7..0000000 --- a/video/index.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - FutureOSS - 视频展示 - - - -
-

🎬 FutureOSS 视频展示

-

用动画和声音了解我们的项目

-
-
-
-
📦
-
-

项目特性展示

-

立方体动画演示核心特性,插件化、安全性、实时监控...

-
▶ 点击播放
-
-
-
-
🔌
-
-

插件开发演示

-

从零开发一个插件,看这里就对了

-
▶ 点击播放
-
-
-
-
🏗️
-
-

架构解析

-

深入了解 FutureOSS 的技术架构和设计思想

-
▶ 点击播放
-
-
-
-
-

FutureOSS © 2026 — 一切皆为插件

-
- - diff --git a/video/intro.html b/video/intro.html deleted file mode 100644 index acf632d..0000000 --- a/video/intro.html +++ /dev/null @@ -1,553 +0,0 @@ - - - - - -FutureOSS - - - - -
-
-
- -
-
FUTUREOSS
-
-
-
-
-
-
- - -
- -
- - -
- - -
-
不知道你有没有这种感觉
-
- - -
-
每次搭一个新项目,都很累
-
- - -
-
-
因为每次都在
重复同样的事
-
-
    -
  • 装环境装到怀疑人生
  • -
  • 找个配置文件翻遍整个项目
  • -
  • 加个小功能要大改架构
  • -
  • 出了问题连日志都看不明白
  • -
-
-
-
- - -
-
于是我们想
-
- - -
-
-
能不能做一个
不用操心的框架?
-
-
    -
  • 不用再重复写基础代码
  • -
  • 不用再手动管理依赖
  • -
  • 不用在几十个文件里找配置
  • -
  • 不用担心插件被篡改
  • -
-
-
-
- - -
-
FutureOSS
-
一切皆为插件
-
- - -
-
不是部分功能可插拔
-
而是所有东西都是插件
-
仪表盘、日志、前端全部是
-
- - -
-
-
-# 新建一个目录 丢进去 -store/@{你}/hello/ - main.py - manifest.json - README.md - -# 启动自动加载 删掉自动消失 -# 不用改一行核心代码 -
-
-
插件化
-
想加功能?新建一个目录丢进去。想删?直接 rm -rf。不用改一行核心代码。
-
-
-
- - -
-
-
-验证中... - SHA-256 校验通过 - RSA-4096 签名合法 - 来源: @Falck - -# 每个官方插件启动前 -# 自动验证,失败直接拒绝加载 -
-
-
签名验证
-
不是事后检查,是加载前就验证。改了一个字节,整个插件拒绝加载。
-
-
-
- - -
-
-
-Log.info("app", "启动") -Log.warn("db", "将满") -Log.error("api", "超时") -Log.tip("app", "已加载 20 插件") - -# 终端自动着色 -# 不用 grep 找关键字 -
-
-
彩色日志
-
info 白 warn 黄 error 红。不用在一堆黑白文字里翻来覆去。
-
-
-
- - -
-
-
-$ pkg install @author/plugin - 下载完成 - 签名验证通过 - 已安装,重启生效 - -# 不用 git clone -# 不用手动复制目录 -# 一行命令搞定 -
-
-
插件商店
-
一条命令安装插件。不用去仓库翻目录、不用手动复制文件。
-
-
-
- - -
-
-
-仪表盘实时展示: - CPU 78% - MEM 56% - NET 12.3M/s 4.1M/s - DISK RD 45MB WR 23MB - LOAD 1.2 0.8 0.6 - -# 打开浏览器就有 -# 不用装 Prometheus -# 不用配 Grafana -
-
-
实时监控
-
启动就有仪表盘。不用搭 Prometheus、不用配 Grafana,打开浏览器就行。
-
-
-
- - -
-
-
-

以前

-

git clone xxx
pip install -r req.txt
python -m venv .venv
source .venv/bin/activate
pip install flask mysql...
改配置文件半小时
python main.py
报错 -> 查日志 -> 改配置
python main.py
又报错...

-
-
-

现在

-

bash start.sh

-
-
-
- - -
-
-
$ bash start.sh
-
检测环境...
-
自动安装依赖...
-
虚拟环境创建完成
-
20 个插件加载完成
-
http://localhost:8080
-
$
-
-
- - -
-
-
0
+ 官方插件
-
-
0
行命令
-
-
0
% 插件化
-
-
- - -
-
-
FutureOSS
-
一切皆为插件
- -
-
- -
- - - - diff --git a/video/plugin-demo.html b/video/plugin-demo.html deleted file mode 100644 index 62179d7..0000000 --- a/video/plugin-demo.html +++ /dev/null @@ -1,324 +0,0 @@ - - - - - - FutureOSS - 插件开发演示 - - - -
-
-
-
- -
-
-
- -
🔌 从零开发一个插件
- -
-
-
1
-

📁 创建插件目录

-

按照规范在 store/@{作者名}/插件名/ 下创建目录

- - store/@{myname}/hello-world/
- ├── main.py
- ├── manifest.json
- └── README.md -
-
-
-
2
-

📝 编写 manifest.json

-

声明插件名称、版本、依赖和描述信息

- - {
-   "name": "hello-world",
-   "version": "1.0.0",
-   "author": "@{myname}",
-   "description": "我的第一个插件"
- } -
-
-
-
3
-

🐍 编写 main.py

-

实现插件的初始化逻辑,注册路由或事件

- - class Plugin:
-   def init(self, app):
-     @app.route("/hello")
-     def hello():
-       return {"msg": "Hello, FutureOSS!"} -
-
-
-
4
-

🚀 启动 & 测试

-

运行项目,访问 http://localhost:8080/hello 验证插件是否正常工作

- - bash start.sh
- curl http://localhost:8080/hello
- # → {"msg": "Hello, FutureOSS!"} -
-
-
-
5
-

📦 发布到商店

-

打包插件并上传到 Gitee 商店,其他人一键安装!

- - python tools/sign_single_plugin.py @{myname}/hello-world
- # 上传到 Gitee 商店仓库 -
-
-
- -
-
- - - -
- - - -