重构:核心迁移至 oss/core + NBPF 多重签名加密 + NIR 编译器 + README 全面升级
- 核心功能从 store/ 迁移至 oss/core/ 框架层 - 实现 NBPF 包格式:多重签名(Ed25519+RSA-PSS+HMAC)+ 多重加密(AES-256-GCM) - 实现 NIR 编译器:基于 compile()+marshal 的跨平台中间表示 - 新增 nebula nbpf CLI 命令组(pack/unpack/verify/sign/keygen) - 新增 19 个 NBPF 测试用例,覆盖全链路 - 彻底重写 README,大型项目标准框架风格,所有图表使用 SVG - 更新 LICENSE 版权声明 - 清理旧版 store 插件目录(已迁移至 oss/core)
This commit is contained in:
348
oss/cli.py
348
oss/cli.py
@@ -3,9 +3,11 @@ import click
|
||||
import signal
|
||||
import os
|
||||
import sys
|
||||
import random
|
||||
from pathlib import Path
|
||||
|
||||
from oss import __version__
|
||||
from oss.logger.logger import Logger
|
||||
from oss.logger.logger import Log
|
||||
from oss.plugin.manager import PluginManager
|
||||
from oss.config import init_config, get_config
|
||||
|
||||
@@ -18,21 +20,52 @@ except ImportError:
|
||||
_ACHIEVEMENTS_ENABLED = False
|
||||
|
||||
|
||||
def _handle_hidden_command():
|
||||
"""处理 !! 前缀的隐藏命令"""
|
||||
if len(sys.argv) <= 1 or not sys.argv[1].startswith("!!"):
|
||||
return False
|
||||
if not _ACHIEVEMENTS_ENABLED:
|
||||
print("成就系统未启用")
|
||||
return True
|
||||
|
||||
cmd = sys.argv[1][2:]
|
||||
args = sys.argv[2:]
|
||||
|
||||
cmd_map = {
|
||||
"echo": _cmd_echo,
|
||||
"help": _cmd_help_internal,
|
||||
"list": _cmd_list_all,
|
||||
"stats": _cmd_stats,
|
||||
"reset": _cmd_reset_progress,
|
||||
"export": _cmd_export,
|
||||
"import": _cmd_import,
|
||||
"verify": _cmd_verify,
|
||||
"debug": _cmd_debug,
|
||||
"info": _cmd_info,
|
||||
}
|
||||
|
||||
if cmd in cmd_map:
|
||||
validator = get_validator()
|
||||
validator.use_hidden_command(cmd)
|
||||
cmd_map[cmd](args)
|
||||
else:
|
||||
print(f"未知命令:!!{cmd}")
|
||||
return True
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option('--config', '-c', type=str, help='配置文件路径')
|
||||
@click.pass_context
|
||||
def cli(ctx, config):
|
||||
"""NebulaShell - 一切皆为插件"""
|
||||
# 初始化配置
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['config'] = init_config(config)
|
||||
|
||||
# 初始化成就系统(如果启用)
|
||||
|
||||
if _ACHIEVEMENTS_ENABLED:
|
||||
try:
|
||||
init_achievements()
|
||||
except Exception:
|
||||
pass # 静默失败,不影响主功能
|
||||
pass
|
||||
|
||||
|
||||
@cli.command()
|
||||
@@ -43,42 +76,44 @@ def cli(ctx, config):
|
||||
def serve(ctx, host, port, tcp_port):
|
||||
"""启动 NebulaShell 服务端"""
|
||||
config = ctx.obj.get('config', get_config())
|
||||
|
||||
# 命令行参数覆盖配置
|
||||
|
||||
if host:
|
||||
config.set('HOST', host)
|
||||
if port:
|
||||
config.set('HTTP_API_PORT', port)
|
||||
if tcp_port:
|
||||
config.set('HTTP_TCP_PORT', tcp_port)
|
||||
|
||||
log = Logger()
|
||||
log.info(f"NebulaShell {__version__} 启动")
|
||||
log.info(f"监听地址:{config.host}:{config.http_api_port}")
|
||||
log.info(f"数据目录:{config.data_dir.absolute()}")
|
||||
log.info(f"插件仓库:{config.store_dir.absolute()}")
|
||||
|
||||
Log.info("NebulaShell", f"NebulaShell {__version__} 启动")
|
||||
Log.info("NebulaShell", f"监听地址:{config.host}:{config.http_api_port}")
|
||||
Log.info("NebulaShell", f"数据目录:{config.data_dir.absolute()}")
|
||||
Log.info("NebulaShell", f"插件仓库:{config.store_dir.absolute()}")
|
||||
|
||||
plugin_mgr = PluginManager()
|
||||
plugin_mgr.load()
|
||||
plugin_mgr.start()
|
||||
|
||||
log.info("就绪")
|
||||
Log.info("NebulaShell", "就绪")
|
||||
|
||||
def shutdown(sig, frame):
|
||||
log.info("停止中...")
|
||||
Log.info("NebulaShell", "停止中...")
|
||||
plugin_mgr.stop()
|
||||
log.info("已停止")
|
||||
Log.info("NebulaShell", "已停止")
|
||||
raise SystemExit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, shutdown)
|
||||
signal.signal(signal.SIGTERM, shutdown)
|
||||
|
||||
# 启动 REPL 交互(由 Core 内部提供)
|
||||
try:
|
||||
if hasattr(plugin_mgr, 'core') and plugin_mgr.core:
|
||||
plugin_mgr.core.start_repl()
|
||||
else:
|
||||
Log.error("NebulaShell", "Core 未加载,无法启动 REPL")
|
||||
signal.pause()
|
||||
except Exception as e:
|
||||
Log.error("NebulaShell", f"REPL 启动失败: {e}")
|
||||
signal.pause()
|
||||
except AttributeError:
|
||||
import time
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@@ -91,8 +126,6 @@ def version():
|
||||
@click.pass_context
|
||||
def info(ctx):
|
||||
"""显示系统信息"""
|
||||
import random
|
||||
|
||||
config = ctx.obj.get('config', get_config())
|
||||
click.echo(f"NebulaShell {__version__}")
|
||||
click.echo(f"配置文件:{config._config_file or '无'}")
|
||||
@@ -103,13 +136,12 @@ def info(ctx):
|
||||
click.echo(f"插件仓库:{config.store_dir.absolute()}")
|
||||
click.echo(f"日志级别:{config.log_level}")
|
||||
click.echo(f"权限检查:{'启用' if config.permission_check else '禁用'}")
|
||||
|
||||
# 小提示:有些功能需要特殊方式访问 (4675747572654f53)
|
||||
# 随机显示隐藏提示(约 10% 概率)
|
||||
|
||||
# 彩蛋提示
|
||||
click.echo("")
|
||||
if random.random() < 0.1: # 10% 概率显示彩蛋提示
|
||||
if random.random() < 0.1:
|
||||
click.echo("✨ 奇怪的提示:试试在命令前加两个感叹号会怎样?比如 !!help")
|
||||
elif random.random() < 0.05: # 额外 5% 概率显示另一种提示
|
||||
elif random.random() < 0.05:
|
||||
click.echo("🤔 听说有人用 !! 开头的命令发现了不得了的东西...")
|
||||
|
||||
|
||||
@@ -122,50 +154,234 @@ def cli_command(connect_host, connect_port):
|
||||
click.echo(f"目标后端:{connect_host}:{connect_port}")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# NBPF 命令组
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@cli.group()
|
||||
def nbpf():
|
||||
"""管理 .nbpf 插件包(打包/解包/签名/验证/密钥生成)"""
|
||||
pass
|
||||
|
||||
|
||||
@nbpf.command()
|
||||
@click.argument('plugin_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True))
|
||||
@click.argument('output', type=click.Path(), default=None, required=False)
|
||||
@click.option('--ed25519-key', type=click.Path(exists=True), help='Ed25519 私钥路径')
|
||||
@click.option('--rsa-key', type=click.Path(exists=True), help='RSA 私钥路径')
|
||||
@click.option('--rsa-pub', type=click.Path(exists=True), help='RSA 公钥路径')
|
||||
@click.option('--signer', default='unknown', help='签名者名称')
|
||||
@click.pass_context
|
||||
def pack(ctx, plugin_dir, output, ed25519_key, rsa_key, rsa_pub, signer):
|
||||
"""打包插件目录为 .nbpf 文件"""
|
||||
from oss.core.nbpf import NBPFPacker
|
||||
|
||||
plugin_path = Path(plugin_dir)
|
||||
if not output:
|
||||
output = f"{plugin_path.name}.nbpf"
|
||||
|
||||
# 读取密钥
|
||||
ed25519_private = Path(ed25519_key).read_bytes() if ed25519_key else None
|
||||
rsa_private_pem = Path(rsa_key).read_bytes() if rsa_key else None
|
||||
rsa_public_pem = Path(rsa_pub).read_bytes() if rsa_pub else None
|
||||
|
||||
if not ed25519_private:
|
||||
click.echo("错误: 需要 Ed25519 私钥 (--ed25519-key)", err=True)
|
||||
raise click.Abort()
|
||||
if not rsa_private_pem:
|
||||
click.echo("错误: 需要 RSA 私钥 (--rsa-key)", err=True)
|
||||
raise click.Abort()
|
||||
if not rsa_public_pem:
|
||||
click.echo("错误: 需要 RSA 公钥 (--rsa-pub)", err=True)
|
||||
raise click.Abort()
|
||||
|
||||
click.echo(f"打包插件: {plugin_path}")
|
||||
click.echo(f"输出文件: {output}")
|
||||
click.echo(f"签名者: {signer}")
|
||||
|
||||
try:
|
||||
packer = NBPFPacker()
|
||||
result = packer.pack(
|
||||
plugin_dir=plugin_path,
|
||||
output_path=Path(output),
|
||||
ed25519_private_key=ed25519_private,
|
||||
rsa_private_key_pem=rsa_private_pem,
|
||||
rsa_public_key_pem=rsa_public_pem,
|
||||
signer_name=signer,
|
||||
)
|
||||
click.echo(f"打包成功: {result}")
|
||||
except Exception as e:
|
||||
click.echo(f"打包失败: {type(e).__name__}: {e}", err=True)
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@nbpf.command()
|
||||
@click.argument('nbpf_file', type=click.Path(exists=True, dir_okay=False))
|
||||
@click.argument('output_dir', type=click.Path(), default=None, required=False)
|
||||
def unpack(nbpf_file, output_dir):
|
||||
"""解包 .nbpf 文件到目录"""
|
||||
from oss.core.nbpf import NBPFUnpacker
|
||||
|
||||
nbpf_path = Path(nbpf_file)
|
||||
if not output_dir:
|
||||
output_dir = nbpf_path.stem
|
||||
|
||||
click.echo(f"解包: {nbpf_path}")
|
||||
click.echo(f"输出目录: {output_dir}")
|
||||
|
||||
try:
|
||||
unpacker = NBPFUnpacker()
|
||||
result = unpacker.unpack(nbpf_path, Path(output_dir))
|
||||
click.echo(f"解包成功: {result}")
|
||||
except Exception as e:
|
||||
click.echo(f"解包失败: {type(e).__name__}: {e}", err=True)
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@nbpf.command()
|
||||
@click.argument('nbpf_file', type=click.Path(exists=True, dir_okay=False))
|
||||
@click.option('--trusted-keys-dir', type=click.Path(exists=True), help='信任的 Ed25519 公钥目录')
|
||||
def verify(nbpf_file, trusted_keys_dir):
|
||||
"""验证 .nbpf 文件签名"""
|
||||
from oss.core.nbpf import NBPFUnpacker
|
||||
|
||||
nbpf_path = Path(nbpf_file)
|
||||
|
||||
# 加载信任密钥
|
||||
trusted_keys = {}
|
||||
if trusted_keys_dir:
|
||||
keys_path = Path(trusted_keys_dir)
|
||||
for kf in keys_path.glob("*.pem"):
|
||||
trusted_keys[kf.stem] = kf.read_bytes()
|
||||
else:
|
||||
# 尝试从默认目录加载
|
||||
default_dir = Path("./data/nbpf-keys/trusted")
|
||||
if default_dir.exists():
|
||||
for kf in default_dir.glob("*.pem"):
|
||||
trusted_keys[kf.stem] = kf.read_bytes()
|
||||
|
||||
if not trusted_keys:
|
||||
click.echo("警告: 未加载任何信任密钥,将尝试提取 manifest 信息", err=True)
|
||||
|
||||
click.echo(f"验证: {nbpf_path}")
|
||||
click.echo(f"信任密钥: {len(trusted_keys)} 个")
|
||||
|
||||
try:
|
||||
unpacker = NBPFUnpacker()
|
||||
manifest = unpacker.extract_manifest(nbpf_path)
|
||||
click.echo(f"插件名称: {manifest.get('metadata', {}).get('name', '未知')}")
|
||||
click.echo(f"版本: {manifest.get('metadata', {}).get('version', '未知')}")
|
||||
click.echo(f"作者: {manifest.get('metadata', {}).get('author', '未知')}")
|
||||
|
||||
if trusted_keys:
|
||||
valid, msg = unpacker.verify_signature(nbpf_path, trusted_keys)
|
||||
if valid:
|
||||
click.echo(f"签名验证: 通过 ({msg})")
|
||||
else:
|
||||
click.echo(f"签名验证: 失败 ({msg})", err=True)
|
||||
raise click.Abort()
|
||||
except Exception as e:
|
||||
click.echo(f"验证失败: {type(e).__name__}: {e}", err=True)
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@nbpf.command()
|
||||
@click.argument('nbpf_file', type=click.Path(exists=True, dir_okay=False))
|
||||
@click.option('--ed25519-key', type=click.Path(exists=True), help='Ed25519 私钥路径')
|
||||
@click.option('--signer', default=None, help='签名者名称')
|
||||
def sign(nbpf_file, ed25519_key, signer):
|
||||
"""为 .nbpf 文件重新签名"""
|
||||
from oss.core.nbpf import NBPFPacker, NBPFUnpacker
|
||||
|
||||
nbpf_path = Path(nbpf_file)
|
||||
|
||||
if not ed25519_key:
|
||||
click.echo("错误: 需要 Ed25519 私钥 (--ed25519-key)", err=True)
|
||||
raise click.Abort()
|
||||
|
||||
ed25519_private = Path(ed25519_key).read_bytes()
|
||||
|
||||
click.echo(f"重新签名: {nbpf_path}")
|
||||
|
||||
try:
|
||||
# 解包
|
||||
temp_dir = nbpf_path.parent / f".{nbpf_path.stem}_tmp"
|
||||
if temp_dir.exists():
|
||||
import shutil
|
||||
shutil.rmtree(temp_dir)
|
||||
NBPFUnpacker().unpack(nbpf_path, temp_dir)
|
||||
|
||||
# 重新打包
|
||||
packer = NBPFPacker()
|
||||
result = packer.pack(
|
||||
plugin_dir=temp_dir,
|
||||
output_path=nbpf_path,
|
||||
ed25519_private_key=ed25519_private,
|
||||
rsa_private_key_pem=None,
|
||||
rsa_public_key_pem=None,
|
||||
signer_name=signer or "resign",
|
||||
)
|
||||
click.echo(f"重新签名成功: {result}")
|
||||
|
||||
# 清理临时目录
|
||||
import shutil
|
||||
shutil.rmtree(temp_dir)
|
||||
except Exception as e:
|
||||
click.echo(f"重新签名失败: {type(e).__name__}: {e}", err=True)
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@nbpf.command(name="keygen")
|
||||
@click.option('--output-dir', type=click.Path(), default='./data/nbpf-keys', help='密钥输出目录')
|
||||
@click.option('--name', default='default', help='密钥名称')
|
||||
def keygen(output_dir, name):
|
||||
"""生成 Ed25519 + RSA 密钥对"""
|
||||
from oss.core.nbpf import NBPCrypto
|
||||
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 创建子目录
|
||||
trusted_dir = output_path / "trusted"
|
||||
rsa_dir = output_path / "rsa"
|
||||
private_dir = output_path / "private"
|
||||
trusted_dir.mkdir(exist_ok=True)
|
||||
rsa_dir.mkdir(exist_ok=True)
|
||||
private_dir.mkdir(exist_ok=True)
|
||||
|
||||
click.echo(f"生成密钥对到: {output_path}")
|
||||
|
||||
# 生成 Ed25519 密钥对
|
||||
click.echo("生成 Ed25519 密钥对...")
|
||||
ed25519_private, ed25519_public = NBPCrypto.generate_ed25519_keypair()
|
||||
(trusted_dir / f"{name}.pem").write_bytes(ed25519_public)
|
||||
(private_dir / f"{name}_ed25519.pem").write_bytes(ed25519_private)
|
||||
click.echo(f" Ed25519 公钥: {trusted_dir / f'{name}.pem'}")
|
||||
click.echo(f" Ed25519 私钥: {private_dir / f'{name}_ed25519.pem'}")
|
||||
|
||||
# 生成 RSA 密钥对
|
||||
click.echo("生成 RSA-4096 密钥对(可能需要几秒钟)...")
|
||||
rsa_private, rsa_public = NBPCrypto.generate_rsa_keypair(key_size=4096)
|
||||
(rsa_dir / f"{name}.pem").write_bytes(rsa_public)
|
||||
(private_dir / f"{name}_rsa.pem").write_bytes(rsa_private)
|
||||
click.echo(f" RSA 公钥: {rsa_dir / f'{name}.pem'}")
|
||||
click.echo(f" RSA 私钥: {private_dir / f'{name}_rsa.pem'}")
|
||||
|
||||
click.echo("密钥生成完成!")
|
||||
click.echo("")
|
||||
click.echo("使用示例:")
|
||||
click.echo(f" nebula nbpf pack ./my-plugin --ed25519-key {private_dir / f'{name}_ed25519.pem'} --rsa-key {private_dir / f'{name}_rsa.pem'} --rsa-pub {rsa_dir / f'{name}.pem'} --signer {name}")
|
||||
|
||||
|
||||
def main():
|
||||
# 检测是否通过已弃用的 oss 命令调用
|
||||
cmd_name = os.path.basename(sys.argv[0])
|
||||
if cmd_name in ("oss", "oss.exe"):
|
||||
print("╔══════════════════════════════════════════╗")
|
||||
print("║ ⚠ oss 命令已弃用,请使用 nebula 替代 ║")
|
||||
print("║ 例如: nebula serve ║")
|
||||
print("║ nebula info ║")
|
||||
print("║ nebula version ║")
|
||||
print("╚══════════════════════════════════════════╝")
|
||||
Log.warn("NebulaShell", "oss 命令已弃用,请使用 nebula 替代")
|
||||
sys.exit(1)
|
||||
|
||||
# 检查隐藏命令前缀
|
||||
if len(sys.argv) > 1 and sys.argv[1].startswith("!!"):
|
||||
if _ACHIEVEMENTS_ENABLED:
|
||||
cmd = sys.argv[1][2:] # 去掉 !! 前缀
|
||||
args = sys.argv[2:]
|
||||
|
||||
# 映射隐藏命令
|
||||
cmd_map = {
|
||||
"echo": _cmd_echo,
|
||||
"help": _cmd_help_internal,
|
||||
"list": _cmd_list_all,
|
||||
"stats": _cmd_stats,
|
||||
"reset": _cmd_reset_progress,
|
||||
"export": _cmd_export,
|
||||
"import": _cmd_import,
|
||||
"verify": _cmd_verify,
|
||||
"debug": _cmd_debug,
|
||||
"info": _cmd_info,
|
||||
}
|
||||
|
||||
if cmd in cmd_map:
|
||||
validator = get_validator()
|
||||
validator.use_hidden_command(cmd)
|
||||
cmd_map[cmd](args)
|
||||
return
|
||||
else:
|
||||
print(f"未知命令:!!{cmd}")
|
||||
return
|
||||
else:
|
||||
print("成就系统未启用")
|
||||
return
|
||||
|
||||
if _handle_hidden_command():
|
||||
return
|
||||
|
||||
cli()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user