installation pipeline - mclucy/lucy GitHub Wiki
本文系统性描述 Lucy install/ 模块的完整实现细节,面向希望深入理解安装机制的贡献者。
在阅读本页前,建议先了解:
install 模块位于 Lucy 数据流末端:接收 routing 层解析出的 PackageRemote,并将目标文件落地到服务器目录。模块内部采用 "注册表 + 函数分发" 模式:每个平台对应独立 platformInstaller,并由全局 map 在进程启动时通过 init() 完成注册。
用户执行 lucy add <package> 后,调用链如下:
用户输入: lucy add fabric/fabric-api@latest
|
v
cmd/cmd_add.go -> actionAdd() cmd/cmd_add.go:49
| syntax.Parse() 解析字符串为 PackageId
| 版本 VersionLatest/VersionAny -> VersionCompatible cmd/cmd_add.go:54
|
v
install/install.go -> Install(id, source) install/install.go:25
|
+- [identity package 分支]
| installPlatform(id) install/install.go:110
| 处理 minecraft/fabric/forge/neoforge/mcdr 平台自身安装
|
'- [普通 mod/plugin 分支]
|
v
ensureServerPlatformMatch(id) install/install.go:35
评估 topology 兼容性,验证目标平台与当前服务器匹配
|
v
probe.ServerInfo() install/install.go:39
获取服务器环境快照(platform、topology、mod 路径等)
|
v
routing.ResolveProvidersByTopology(...) install/install.go:43
根据 topology + source 选择 Provider 列表
检测 MCDR 环境时追加 MCDR Provider install/install.go:52
|
v
routing.FetchMany(providers, id) install/install.go:68
并行 fetch,返回全部成功的 PackageRemote 候选列表
|
+- 0 候选 -> 返回错误
+- 1 候选 -> 直接选用
'- 多候选 -> selectFromCandidates() 触发 TUI 选择 install/install.go:91
|
v
installers[id.Platform](p) install/install.go:98
按 Platform 从注册表取出 platformInstaller 并调用
|
v
util.CachedDownload(...)
content-addressed 缓存层,cache hit 时跳过网络
ensureServerPlatformMatch() 会调用 probe.EvaluateCompatibility(topology, requiredCapability) 评估拓扑兼容性,结论与行为如下:
| Verdict | 含义 | 行为 |
|---|---|---|
CompatCompatible |
直接能力匹配 | 放行 |
CompatDegraded |
桥接兼容(risk < High) | 警告后放行 |
CompatDegraded |
桥接兼容(risk >= High) | 返回错误,拒绝安装 |
CompatIncompatible |
无法匹配 | 返回错误 |
CompatUnresolved |
topology 未解析 | 返回错误 |
参见 install/install.go:200 与 probe/probe_topology_evaluation.go:13。
// ../lucy/install/install.go:25-108
func Install(id types.PackageId, source types.Source) error {
p := id.NewPackage()
// route to platform installer if it's an identity package
if id.IsIdentityPackage() {
return installPlatform(id)
}
// this is order-sensitive, ensureServerPlatformMatch() does not check for
// identity packages
if err := ensureServerPlatformMatch(id); err != nil {
return err
}
serverInfo := probe.ServerInfo()
serverPlatform := serverInfo.Executable.DerivedModLoader()
hasMcdr := serverInfo.Environments.Mcdr != nil
providers, err := routing.ResolveProvidersByTopology(
serverInfo.Executable.Topology,
serverPlatform,
source,
)
if err != nil {
return err
}
if hasMcdr {
mcdrProviders, err := routing.ResolveProviders(
types.PlatformMCDR,
types.SourceAuto,
)
if err != nil {
logger.ShowInfo(
fmt.Errorf(
"failed to resolve MCDR provider: %w",
err,
),
)
}
providers = append(providers, mcdrProviders...)
}
remotes, errs := routing.FetchMany(providers, id)
for _, err := range errs {
if source == types.SourceAuto && len(providers) > 1 {
logger.ReportWarn(
fmt.Errorf(
"search on %s failed: %w",
err.Source.Title(),
err.Err,
),
)
continue
}
}
switch len(remotes) {
case 0:
return fmt.Errorf("no candidates found for %s", id.String())
case 1:
// good,follow through
p.Remote = &remotes[0]
default:
// prompt user to select one
var err error
p.Remote, err = selectFromCandidates(remotes)
if err != nil {
return err
}
}
source = p.Remote.Source
installer := installers[id.Platform]
if installer == nil {
return fmt.Errorf("no installer found for platform %s", id.Platform)
}
err = installer(p)
if err != nil {
return err
}
return nil
}安装器注册表由 map[types.Platform]platformInstaller 维护;每个平台文件通过 init() 注册对应函数。
// install/install.go
type platformInstaller func(p types.Package) error
var installers = map[types.Platform]platformInstaller{}
func registerInstaller(platform types.Platform, installer platformInstaller) {
if installer == nil {
panic("install: nil installer")
}
installers[platform] = installer
}参见 install/install.go:14。
// install/install_generic.go
func init() {
registerInstaller(types.PlatformAny, installGenericPackage)
}| 平台 | 注册函数 | 注册文件 |
|---|---|---|
PlatformAny |
installGenericPackage |
install_generic.go:12 |
PlatformFabric |
installFabricMod |
install_fabric.go:36 |
PlatformForge |
installForgeMod |
install_forge.go:68 |
PlatformNeoforge |
installNeoForgeMod |
install_neoforge.go:6 |
PlatformMCDR |
installMcdrPlugin |
install_mcdr.go:16 |
installGenericPackage 是最短路径,适用于不依赖 mod loader 的通用包。它直接调用 CachedDownload,将文件落地到工作目录根("."):
result, err := util.CachedDownload(p.Remote.FileUrl, ".", util.DownloadOptions{
Kind: cache.KindArtifact,
Filename: p.Remote.Filename,
ExpectedHash: p.Remote.Hash,
HashAlgorithm: cache.ParseHashAlgorithm(p.Remote.HashAlgorithm),
})参见 install/install_generic.go:21。
installFabricMod 委托 installModLoaderPackage(p, types.PlatformFabric),最终将文件写入 serverInfo.ModPath[0](服务器 mod 目录):
result, err := util.CachedDownload(
p.Remote.FileUrl,
serverInfo.ModPath[0],
util.DownloadOptions{...},
)参见 install/install_modloader.go:27。若 serverInfo.ModPath 为空则返回错误。
installForgeMod 同样委托 installModLoaderPackage(p, types.PlatformForge),落地路径为 mod 目录。参见 install/install_forge.go:71。
installNeoForgeMod 委托 installModLoaderPackage(p, types.PlatformNeoforge),落地路径为 mod 目录。参见 install/install_neoforge.go:9。
installMcdrPlugin 从 serverInfo.Environments.Mcdr.Config.PluginDirectories[0] 获取插件目录并落地文件。若 MCDR 环境或插件目录不存在,则返回错误。参见 install/install_mcdr.go:19。
当 PackageId.IsIdentityPackage() 为 true 时,Install() 将进入 installPlatform(),跳过 routing 层,直接安装平台本身。
installMinecraftServer() 通过 Mojang 版本清单 API 解析目标版本;在用户同意 EULA 后下载并落地 server JAR,同时设置可执行权限(chmod +x)。参见 install/install_vanilla.go:34。
installFabric() / installFabricWithOverride() 调用 Fabric Meta API 解析 loader 与 game 版本,构造 server JAR 下载 URL,并通过 CachedDownload 落地到 serverInfo.WorkPath。参见 install/install_fabric.go:39。
installForge() 从 Forge promotions API 解析版本,下载 installer JAR,然后本地执行 java -jar forge-*-installer.jar --installServer。安装过程中会实时解析日志驱动进度条;完成后调用 verifyForgeInstallation() 校验产物。参见 install/install_forge.go:188。
Warning
此功能尚未完整/正确实现
NeoForge 平台自身安装(installNeoForge())尚未实现;当前调用将直接 panic。详见 install/install_neoforge.go:14。
initMcdr() 的初始化步骤为:
- 检查
mcdreforged命令是否在 PATH 中。 - 创建
server/子目录。 - 将当前目录下所有文件移入
server/。 - 执行
mcdreforged init。 - 调用
probe.Rebuild()重新探测服务器环境。
参见 install/install_mcdr.go:50。
当 routing.FetchMany() 返回多个 PackageRemote 时,selectFromCandidates() 基于 huh 库弹出交互式选择界面,由用户确认最终安装项。参见 install/install.go:253。
installPlatform() 对平台冲突设有明确保护:
- 检测到 Forge/NeoForge 时,不允许安装 Fabric(反向同理)。
- 检测到 Fabric 时,不允许安装 Forge。
- MCDR 已安装时,不允许重复初始化。
这些判定基于 probe.ServerInfo().Executable.DerivedModLoader() 返回的当前平台信息。
Warning
此功能尚未完整/正确实现
Minecraft Vanilla 服务端的覆盖安装(overwrite confirmation)尚未完整实现。当检测到已有服务器时,当前实现会直接返回错误 "a server is already installed",而非提示用户确认是否覆盖。源码中已以 TODO 标记该缺口。详见 install/install.go:131。
case types.PlatformMinecraft:
if serverPlatform != types.PlatformNone {
// TODO: ask if overwrite existing server
return errors.New("a server is already installed")
}
return installMinecraftServer(id)Fabric 平台安装对 Vanilla 提供特殊覆盖流程:若检测到 Vanilla,promptOverrideVanillaWithFabric() 会通过 TUI 询问是否覆盖,并可选择删除原 Vanilla JAR。这是当前唯一完整实现的覆盖确认路径。参见 install/install_platform_fabric.go:130。
多数安装器在文件落地后无额外动作。例外如下:
-
Forge 平台安装:installer 执行完成后调用
verifyForgeInstallation()校验产物,再调用probe.Rebuild()刷新环境信息。 -
Fabric 平台安装:调用
probe.Rebuild()刷新环境信息(参见install/install_fabric.go:103)。 -
MCDR 平台初始化:调用
probe.Rebuild()重新探测整个服务器目录结构。
Warning
以下内容基于非完整代码推断,可能与实际预期行为存在偏差。
请以 install/install.go:98 的实际分发逻辑为准。普通 mod/plugin 安装器(Generic、Fabric Mod、Forge Mod、NeoForge Mod、MCDR Plugin)在文件落地后通常不调用 probe.Rebuild(),因为单个 mod/plugin 安装不会改变服务器平台拓扑,无需重建 topology 缓存。
在 Install() 中:当 SourceAuto 且 Provider 数量大于 1 时,单个 Provider 的 fetch 失败会以警告(logger.ReportWarn)上报,而非立即终止,以保留其他 Provider 继续提供候选的机会。
仅当所有 Provider 均无法提供候选(候选列表为空)时,流程才返回最终错误。
参见 install/install.go:69。
本页内容综合以下源码(基线 commit 86d3480cffa821b9b9c0747263a637b2973a7366):
-
install/install.go:14-platformInstaller类型定义与registerInstaller()注册模式 -
install/install.go:25-Install()主流程入口 -
install/install.go:98- 按 Platform 分发至 platformInstaller -
install/install.go:110-installPlatform()identity package 分支 -
install/install.go:131- overwrite 未完成实现(L-3 已知限制) -
install/install.go:180-ensureServerPlatformMatch()兼容性校验 -
install/install.go:253-selectFromCandidates()多候选 TUI 选择 -
install/install_generic.go:12- Generic 安装器注册与实现 -
install/install_fabric.go:36- Fabric mod 安装器注册 -
install/install_fabric.go:39- Fabric 平台安装实现 -
install/install_forge.go:68- Forge mod 安装器注册 -
install/install_forge.go:188- Forge 平台安装(包含 java installer 执行) -
install/install_neoforge.go:6- NeoForge mod 安装器注册 -
install/install_neoforge.go:14- NeoForge 平台安装(panic,未实现) -
install/install_mcdr.go:16- MCDR plugin 安装器注册 -
install/install_mcdr.go:50- MCDR 平台初始化逻辑 -
install/install_modloader.go:15- 通用 mod loader 包安装函数 -
install/install_vanilla.go:34- Minecraft Vanilla 服务端安装 -
install/install_platform_fabric.go:130- Vanilla -> Fabric 覆盖确认 TUI -
probe/probe_topology_evaluation.go:13-EvaluateCompatibility()拓扑评估 -
probe/probe_topology_evaluation.go:67-CapabilityForPlatform()平台能力映射 -
cmd/cmd_add.go:49-actionAddCLI 入口 -
cmd/cmd_add.go:59-install.Install()调用点