移除 data/pkg 目录相关逻辑
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,3 +28,4 @@ data/signature-verifier/keys/private/
|
|||||||
|
|
||||||
# 签名文件(可选,本地开发可能不需要)
|
# 签名文件(可选,本地开发可能不需要)
|
||||||
# store/**/SIGNATURE
|
# store/**/SIGNATURE
|
||||||
|
.codebuddy/
|
||||||
@@ -33,7 +33,7 @@ COPY pyproject.toml ./pyproject.toml
|
|||||||
COPY README.md ./README.md
|
COPY README.md ./README.md
|
||||||
|
|
||||||
# 创建必要目录
|
# 创建必要目录
|
||||||
RUN mkdir -p /app/data/html-render /app/data/web-toolkit /app/data/plugin-storage /app/data/DCIM /app/data/pkg /app/logs
|
RUN mkdir -p /app/data/html-render /app/data/web-toolkit /app/data/plugin-storage /app/data/DCIM /app/logs
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 8080 8081 8082
|
EXPOSE 8080 8081 8082
|
||||||
|
|||||||
2
start.sh
2
start.sh
@@ -303,7 +303,7 @@ ok "Python 依赖安装完成"
|
|||||||
# ═══════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════
|
||||||
step "初始化数据目录"
|
step "初始化数据目录"
|
||||||
|
|
||||||
DATA_DIRS=("data" "data/html-render" "data/web-toolkit" "data/plugin-storage" "data/DCIM" "data/pkg" "data/signature-verifier/keys/private" "data/signature-verifier/keys/public" "logs")
|
DATA_DIRS=("data" "data/html-render" "data/web-toolkit" "data/plugin-storage" "data/DCIM" "data/signature-verifier/keys/private" "data/signature-verifier/keys/public" "logs")
|
||||||
DIR_COUNT=${#DATA_DIRS[@]}
|
DIR_COUNT=${#DATA_DIRS[@]}
|
||||||
DIR_CURRENT=0
|
DIR_CURRENT=0
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"args": {
|
"args": {
|
||||||
"watch_dirs": ["store", "./data/pkg"],
|
"watch_dirs": ["store"],
|
||||||
"watch_extensions": [".py", ".json"]
|
"watch_extensions": [".py", ".json"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
# pkg 包管理
|
|
||||||
|
|
||||||
插件的搜索、安装、卸载和更新功能。
|
|
||||||
|
|
||||||
## 功能
|
|
||||||
|
|
||||||
- 从远程仓库搜索插件
|
|
||||||
- 下载并安装到 `./data/pkg/` 目录
|
|
||||||
- 卸载已安装的插件
|
|
||||||
- 更新单个或所有插件
|
|
||||||
- 维护已安装插件列表
|
|
||||||
|
|
||||||
## 使用
|
|
||||||
|
|
||||||
```python
|
|
||||||
pm = pkg_plugin.manager
|
|
||||||
|
|
||||||
# 搜索
|
|
||||||
results = pm.search("keyword")
|
|
||||||
|
|
||||||
# 安装
|
|
||||||
pm.install("plugin-name")
|
|
||||||
pm.install("plugin-name", version="1.0.0")
|
|
||||||
|
|
||||||
# 卸载
|
|
||||||
pm.uninstall("plugin-name")
|
|
||||||
|
|
||||||
# 更新
|
|
||||||
pm.update() # 更新所有
|
|
||||||
pm.update("plugin-name") # 更新单个
|
|
||||||
|
|
||||||
# 列出已安装
|
|
||||||
installed = pm.list_installed()
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安装位置
|
|
||||||
|
|
||||||
```
|
|
||||||
./data/pkg/
|
|
||||||
└── <插件名>/
|
|
||||||
├── main.py
|
|
||||||
└── manifest.json
|
|
||||||
```
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"signature": "dXU/zN0Zge7OC8UZgWXZVhPn7LQyiKQw4iUVZAI0P4PA7zGed3cnXa7GFzVnUKxyZMKaOITcGeg7yIM9SWM7WRTWj3N1e6F/0ac6zQ57WgREUA2zc4w0/Vc742i0+KSrE1TkICZl1CTa1x3TG3VJQo0qw4FGPijKjJQIaA9yw+yLhm0dkMefZGVAuYRnupFvKxX1xar0vx6JPpoDmHxvU92PdzbR1ggsB5hOzIrvd3aVJ1U8GbogVhtaabToK9IXbX6qrTY32ffZpEOI5n0IqAvxZ81IUV3bwhf72nP6sedEEKJzgOGfqHhMalOpjsEHNiHnX3UgBfiXzeDn2zN0NevTGCGzvgQHc3/5o/Ct9wG8ujqlNLi37jXt1DrTnIF1IBsW73ltdaMvl4IgQ0Sln2Y7QMNt3CDtwNBSBiLUhTnMjPN7QVaCl7lMM0PJH5tWg3rlSdf7+LGUN535uMwrtEJEmhafo2lcApInEZryEmRcUb22Wl3xCqGgK5yk30QqGHCwY/h4fNhx2VE7LWIoD/jMJNH+TPXTzPPUGHOGB5zaR8v+qtohOwRYPewUkbEArg7qjsOgHerHziLYBY2yH1/4oi9/N7DYsgmRyHrl4siuo+5HtPar+q29yDORs5UgxK3VNHncElVWXQ9DGzIrm3Ffj610nw7kOiU58HrBjj8=",
|
|
||||||
"signer": "FutureOSS",
|
|
||||||
"algorithm": "RSA-SHA256",
|
|
||||||
"timestamp": 1775964952.754572,
|
|
||||||
"plugin_hash": "36a948e470e6cd7ac1b51a385f21fc615c36049e9cb3fac25cdaef8161063ef1",
|
|
||||||
"author": "FutureOSS"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,216 +0,0 @@
|
|||||||
"""包管理插件 - 搜索、安装、卸载、更新插件"""
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import shutil
|
|
||||||
import urllib.request
|
|
||||||
import urllib.parse
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Optional
|
|
||||||
|
|
||||||
from oss.logger.logger import Log
|
|
||||||
from oss.plugin.types import Plugin, register_plugin_type
|
|
||||||
|
|
||||||
|
|
||||||
# 远程仓库地址(可配置)
|
|
||||||
# 插件存储在 future-oss 仓库的 store/ 目录下
|
|
||||||
DEFAULT_REGISTRY = "https://gitee.com/starlight-apk/future-oss/raw/main"
|
|
||||||
|
|
||||||
# 插件安装目录
|
|
||||||
PKG_DIR = Path("./data/pkg")
|
|
||||||
|
|
||||||
|
|
||||||
class PackageInfo:
|
|
||||||
"""包信息"""
|
|
||||||
def __init__(self):
|
|
||||||
self.name: str = ""
|
|
||||||
self.version: str = ""
|
|
||||||
self.author: str = ""
|
|
||||||
self.description: str = ""
|
|
||||||
self.download_url: str = ""
|
|
||||||
self.dependencies: list[str] = []
|
|
||||||
|
|
||||||
|
|
||||||
class PackageManager:
|
|
||||||
"""包管理器"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.registry = DEFAULT_REGISTRY
|
|
||||||
self.index_cache: dict[str, PackageInfo] = {}
|
|
||||||
self.installed: dict[str, dict[str, Any]] = {}
|
|
||||||
self._load_installed()
|
|
||||||
|
|
||||||
def _load_installed(self):
|
|
||||||
"""加载已安装的包"""
|
|
||||||
if not PKG_DIR.exists():
|
|
||||||
return
|
|
||||||
# 扫描 @{author}/plugin_name 结构
|
|
||||||
for author_dir in PKG_DIR.iterdir():
|
|
||||||
if author_dir.is_dir() and author_dir.name.startswith("@{"):
|
|
||||||
for plugin_dir in author_dir.iterdir():
|
|
||||||
if plugin_dir.is_dir():
|
|
||||||
manifest = plugin_dir / "manifest.json"
|
|
||||||
if manifest.exists():
|
|
||||||
with open(manifest, "r", encoding="utf-8") as f:
|
|
||||||
full_name = author_dir.name + "/" + plugin_dir.name
|
|
||||||
self.installed[full_name] = json.load(f)
|
|
||||||
|
|
||||||
def search(self, query: str = "") -> list[PackageInfo]:
|
|
||||||
"""搜索可用的包"""
|
|
||||||
# 简化版本:直接返回本地缓存
|
|
||||||
# 实际使用时可以通过 API 或配置文件维护一个插件索引
|
|
||||||
return self._search_from_cache(query)
|
|
||||||
|
|
||||||
def _search_from_cache(self, query: str = "") -> list[PackageInfo]:
|
|
||||||
"""从本地缓存搜索包"""
|
|
||||||
results = []
|
|
||||||
for pkg_name, pkg_info in self.index_cache.items():
|
|
||||||
if not query or query.lower() in pkg_name.lower() or query.lower() in pkg_info.get("description", "").lower():
|
|
||||||
results.append(pkg_info)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def install(self, name: str, version: str = "") -> bool:
|
|
||||||
"""安装包,支持 @{作者名称}/插件名称 格式"""
|
|
||||||
# 解析输入格式 @{author}/plugin 或直接插件名
|
|
||||||
author = "FutureOSS" # 默认作者
|
|
||||||
plugin_name = name
|
|
||||||
|
|
||||||
if name.startswith("@{") and "}/" in name:
|
|
||||||
# 解析 @{author}/plugin 格式
|
|
||||||
end_bracket = name.index("}/")
|
|
||||||
author = name[2:end_bracket]
|
|
||||||
plugin_name = name[end_bracket + 2:]
|
|
||||||
elif name.startswith("@{") and name.endswith("}") and "/" in name:
|
|
||||||
# 兼容旧格式 @{author/plugin}
|
|
||||||
inner = name[2:-1]
|
|
||||||
parts = inner.split("/", 1)
|
|
||||||
if len(parts) == 2:
|
|
||||||
author, plugin_name = parts
|
|
||||||
|
|
||||||
# 搜索获取下载链接
|
|
||||||
packages = self.search(plugin_name)
|
|
||||||
pkg_info = None
|
|
||||||
for p in packages:
|
|
||||||
if p.name == plugin_name and p.author == author:
|
|
||||||
if not version or p.version == version:
|
|
||||||
pkg_info = p
|
|
||||||
break
|
|
||||||
|
|
||||||
if not pkg_info or not pkg_info.download_url:
|
|
||||||
# 尝试从远程仓库直接构建 URL
|
|
||||||
# 插件存储在 store/@{author}/plugin_name 目录下
|
|
||||||
pkg_info = PackageInfo()
|
|
||||||
pkg_info.name = plugin_name
|
|
||||||
pkg_info.author = author
|
|
||||||
pkg_info.version = version or "1.0.0"
|
|
||||||
pkg_info.download_url = f"{self.registry}/store/@{{{author}}}/{plugin_name}"
|
|
||||||
|
|
||||||
# 创建安装目录 @{author}/plugin_name
|
|
||||||
install_dir = PKG_DIR / ("@{" + author + "}") / plugin_name
|
|
||||||
install_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 下载 manifest.json
|
|
||||||
manifest_url = f"{pkg_info.download_url}/manifest.json"
|
|
||||||
with urllib.request.urlopen(manifest_url, timeout=10) as resp:
|
|
||||||
manifest_data = json.loads(resp.read().decode("utf-8"))
|
|
||||||
with open(install_dir / "manifest.json", "w", encoding="utf-8") as f:
|
|
||||||
json.dump(manifest_data, f, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
# 下载 main.py
|
|
||||||
main_url = f"{pkg_info.download_url}/main.py"
|
|
||||||
with urllib.request.urlopen(main_url, timeout=10) as resp:
|
|
||||||
main_data = resp.read().decode("utf-8")
|
|
||||||
with open(install_dir / "main.py", "w", encoding="utf-8") as f:
|
|
||||||
f.write(main_data)
|
|
||||||
|
|
||||||
# 更新已安装列表
|
|
||||||
full_name = "@{" + author + "}/" + plugin_name
|
|
||||||
self.installed[full_name] = manifest_data
|
|
||||||
Log.info("pkg", f"已安装: {full_name} {manifest_data.get('metadata', {}).get('version', '')}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
Log.error("pkg", f"安装失败 {name}: {e}")
|
|
||||||
# 清理失败的安装
|
|
||||||
if install_dir.exists():
|
|
||||||
shutil.rmtree(install_dir)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def uninstall(self, name: str) -> bool:
|
|
||||||
"""卸载包,支持 @{作者名称}/插件名称 格式"""
|
|
||||||
# 解析格式获取目录路径
|
|
||||||
if name.startswith("@{") and "}/" in name:
|
|
||||||
end_bracket = name.index("}/")
|
|
||||||
author = name[2:end_bracket]
|
|
||||||
plugin_name = name[end_bracket + 2:]
|
|
||||||
install_dir = PKG_DIR / ("@{" + author + "}") / plugin_name
|
|
||||||
elif name.startswith("@{") and name.endswith("}") and "/" in name:
|
|
||||||
# 兼容旧格式
|
|
||||||
install_dir = PKG_DIR / name
|
|
||||||
else:
|
|
||||||
install_dir = PKG_DIR / name
|
|
||||||
|
|
||||||
if not install_dir.exists():
|
|
||||||
Log.info("pkg", f"包未安装: {name}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
shutil.rmtree(install_dir)
|
|
||||||
# 从已安装列表中移除
|
|
||||||
for key in list(self.installed.keys()):
|
|
||||||
if key == name or key.endswith("/" + install_dir.name):
|
|
||||||
del self.installed[key]
|
|
||||||
break
|
|
||||||
Log.info("pkg", f"已卸载: {name}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
Log.error("pkg", f"卸载失败 {name}: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def update(self, name: str = "") -> bool:
|
|
||||||
"""更新包"""
|
|
||||||
if name:
|
|
||||||
# 更新单个包
|
|
||||||
if name not in self.installed:
|
|
||||||
Log.info("pkg", f"包未安装: {name}")
|
|
||||||
return False
|
|
||||||
return self.install(name)
|
|
||||||
else:
|
|
||||||
# 更新所有已安装的包
|
|
||||||
success = True
|
|
||||||
for pkg_name in list(self.installed.keys()):
|
|
||||||
if not self.install(pkg_name):
|
|
||||||
success = False
|
|
||||||
return success
|
|
||||||
|
|
||||||
def list_installed(self) -> dict[str, Any]:
|
|
||||||
"""列出已安装的包"""
|
|
||||||
return self.installed
|
|
||||||
|
|
||||||
|
|
||||||
class PkgPlugin(Plugin):
|
|
||||||
"""包管理插件"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.manager = PackageManager()
|
|
||||||
|
|
||||||
def init(self, deps: dict = None):
|
|
||||||
"""初始化"""
|
|
||||||
PKG_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
Log.info("pkg", "包管理器已初始化")
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""启动"""
|
|
||||||
Log.info("pkg", f"包管理器已启动,已安装 {len(self.manager.installed)} 个包")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""停止"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# 注册类型
|
|
||||||
register_plugin_type("PackageManager", PackageManager)
|
|
||||||
register_plugin_type("PackageInfo", PackageInfo)
|
|
||||||
|
|
||||||
|
|
||||||
def New():
|
|
||||||
return PkgPlugin()
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"metadata": {
|
|
||||||
"name": "pkg",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "FutureOSS",
|
|
||||||
"description": "包管理 - 插件的搜索、安装、卸载和更新",
|
|
||||||
"type": "utility"
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"enabled": true,
|
|
||||||
"args": {
|
|
||||||
"registry": "https://gitee.com/starlight-apk/future-oss-pkg/raw/main",
|
|
||||||
"install_dir": "./data/pkg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": [],
|
|
||||||
"permissions": []
|
|
||||||
}
|
|
||||||
@@ -43,7 +43,6 @@ class ProPluginManager:
|
|||||||
ProLogger.info("loader", "开始扫描插件...")
|
ProLogger.info("loader", "开始扫描插件...")
|
||||||
|
|
||||||
self._load_from_dir(Path(store_dir))
|
self._load_from_dir(Path(store_dir))
|
||||||
self._load_from_dir(Path("./data/pkg"))
|
|
||||||
|
|
||||||
ProLogger.info("loader", f"共加载 {len(self.plugins)} 个插件")
|
ProLogger.info("loader", f"共加载 {len(self.plugins)} 个插件")
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
- 自动扫描 `store/` 和 `./data/pkg/` 目录
|
- 自动扫描 `store/` 目录
|
||||||
- 动态加载 `main.py` 并调用 `New()` 获取实例
|
- 动态加载 `main.py` 并调用 `New()` 获取实例
|
||||||
- 解析 `manifest.json` 获取插件元数据
|
- 解析 `manifest.json` 获取插件元数据
|
||||||
- 自动扫描插件能力(AST 分析)
|
- 自动扫描插件能力(AST 分析)
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ class PluginManager:
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
def load_all(self, store_dir: str = "store"):
|
def load_all(self, store_dir: str = "store"):
|
||||||
"""加载 store 和 data/pkg 下所有插件(跳过自己)"""
|
"""加载 store 下所有插件(跳过自己)"""
|
||||||
# 确保 plugin 命名空间包存在(必须在最开头)
|
# 确保 plugin 命名空间包存在(必须在最开头)
|
||||||
import types
|
import types
|
||||||
if 'plugin' not in sys.modules:
|
if 'plugin' not in sys.modules:
|
||||||
@@ -411,7 +411,6 @@ class PluginManager:
|
|||||||
|
|
||||||
# 加载其他插件
|
# 加载其他插件
|
||||||
self._load_plugins_from_dir(Path(store_dir))
|
self._load_plugins_from_dir(Path(store_dir))
|
||||||
self._load_plugins_from_dir(Path("./data/pkg"))
|
|
||||||
|
|
||||||
# 按依赖排序
|
# 按依赖排序
|
||||||
if dependency_plugin:
|
if dependency_plugin:
|
||||||
@@ -448,44 +447,11 @@ class PluginManager:
|
|||||||
if plugin_dir.is_dir() and (plugin_dir / "main.py").exists():
|
if plugin_dir.is_dir() and (plugin_dir / "main.py").exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
pkg_dir = Path("./data/pkg")
|
|
||||||
if pkg_dir.exists():
|
|
||||||
for d in pkg_dir.iterdir():
|
|
||||||
if d.is_dir() and (d / "main.py").exists():
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _bootstrap_installation(self):
|
def _bootstrap_installation(self):
|
||||||
"""引导安装 FutureOSS 官方插件"""
|
"""引导安装 FutureOSS 官方插件"""
|
||||||
# 加载 pkg 插件
|
Log.info("plugin-loader", "跳过引导安装(pkg 插件已移除)")
|
||||||
pkg_dir = Path("store/@{FutureOSS}/pkg")
|
|
||||||
if pkg_dir.exists() and (pkg_dir / "main.py").exists():
|
|
||||||
try:
|
|
||||||
pkg_instance = self.load(pkg_dir, use_sandbox=False)
|
|
||||||
if pkg_instance:
|
|
||||||
pkg_mgr = pkg_instance.manager
|
|
||||||
|
|
||||||
Log.info("plugin-loader", "正在搜索可用插件...")
|
|
||||||
results = pkg_mgr.search()
|
|
||||||
if not results:
|
|
||||||
Log.warn("plugin-loader", "未找到远程插件")
|
|
||||||
return
|
|
||||||
|
|
||||||
Log.info("plugin-loader", f"发现 {len(results)} 个插件,开始安装...")
|
|
||||||
installed_count = 0
|
|
||||||
for pkg_info in results:
|
|
||||||
Log.info("plugin-loader", f"安装: {pkg_info.name}")
|
|
||||||
if pkg_mgr.install(pkg_info.name):
|
|
||||||
installed_count += 1
|
|
||||||
|
|
||||||
if installed_count > 0:
|
|
||||||
Log.info("plugin-loader", f"已安装 {installed_count} 个插件,重新扫描加载...")
|
|
||||||
# pkg 保留,重新加载其他插件
|
|
||||||
except Exception as e:
|
|
||||||
Log.error("plugin-loader", f"引导安装失败: {e}")
|
|
||||||
else:
|
|
||||||
Log.info("plugin-loader", "pkg 插件不存在,跳过引导安装")
|
|
||||||
|
|
||||||
def _sort_by_dependencies(self, dep_plugin):
|
def _sort_by_dependencies(self, dep_plugin):
|
||||||
"""按依赖关系排序"""
|
"""按依赖关系排序"""
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"args": {
|
"args": {
|
||||||
"scan_dirs": ["store", "./data/pkg"],
|
"scan_dirs": ["store"],
|
||||||
"sandbox_enabled": true,
|
"sandbox_enabled": true,
|
||||||
"permission_check": true
|
"permission_check": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ class WebUIPlugin(Plugin):
|
|||||||
dashboard_exists = False
|
dashboard_exists = False
|
||||||
store_dirs = [
|
store_dirs = [
|
||||||
Path("store/@{FutureOSS}/dashboard"),
|
Path("store/@{FutureOSS}/dashboard"),
|
||||||
Path("./data/pkg/dashboard"),
|
|
||||||
]
|
]
|
||||||
for d in store_dirs:
|
for d in store_dirs:
|
||||||
if d.exists() and (d / "main.py").exists():
|
if d.exists() and (d / "main.py").exists():
|
||||||
|
|||||||
Reference in New Issue
Block a user