feat.文档更新

This commit is contained in:
lincube
2026-06-08 03:54:33 +08:00
parent 7db72fbcd0
commit 49af6601aa
247 changed files with 2939 additions and 0 deletions

464
docs/archive/LAUNCHER.md Normal file
View File

@@ -0,0 +1,464 @@
# Launcher 架构文档
> LanMountainDesktop.Launcher - 应用启动器与版本目录选择
## 目录
- [概述](#概述)
- [职责范围](#职责范围)
- [架构设计](#架构设计)
- [核心服务](#核心服务)
- [版本管理](#版本管理)
- [启动流程](#启动流程)
- [命令行接口](#命令行接口)
- [开发指南](#开发指南)
## 概述
Launcher 是 LanMountainDesktop 的唯一入口点,负责:
- 首次体验引导 (OOBE)
- 启动动画 (Splash Screen)
- 多版本管理和选择
- 不承担更新职责;更新检查、下载、应用与回滚均由主程序负责
- 插件安装和升级维护命令
Air APP 窗口生命周期不再由 Launcher 进程内 broker 承担。Launcher 在正常启动时预启动包根下的 `LanMountainDesktop.AirAppRuntime`,该进程以框架依赖 JIT 方式运行并负责 Air APP IPC、实例表和 AirAppHost 进程管理。
**设计理念**: 参考 ClassIsland 项目,实现原子化的多版本管理和随时版本回退能力。
## 职责范围
### 1. OOBE (Out-of-Box Experience)
- 首次启动引导
- 欢迎页面
- 初始设置向导
### 2. Splash Screen
- 启动动画
- 加载进度显示
- 品牌展示
### 3. 版本管理
- 多版本并存 (`app-{version}/` 目录)
- 版本选择算法
- 版本标记系统 (`.current`, `.partial`, `.destroy`)
- 旧版本自动清理
### 4. 更新边界
- Host 负责更新检查、频道策略、下载、`deployment.lock` 写入、PLONDS 应用、部署切换和回滚
- Launcher 不提供 `update check` / `update download` / `apply-update` / `rollback` 命令
- Launcher 只按版本目录和 `.current` / `.partial` / `.destroy` 标记选择要启动的 Host
### 5. 插件维护
- `plugin install` / `plugin update` 保留为兼容维护命令
- 应用内插件市场下载、校验和 pending 队列由 Host 负责
## 架构设计
### 目录结构
**安装后的目录结构:**
```
C:\Program Files\LanMountainDesktop\
├── LanMountainDesktop.Launcher.exe ← 唯一入口
├── LanMountainDesktop.AirAppRuntime.exe ← Air APP 生命周期容器JIT
├── app-1.0.0/ ← 版本目录
│ ├── .current ← 当前版本标记
│ ├── LanMountainDesktop.exe
│ ├── LanMountainDesktop.AirAppHost.exe
│ ├── LanMountainDesktop.dll
│ └── ... (所有依赖)
├── app-1.0.1/ ← 新版本
│ ├── .partial ← 下载中标记
│ └── ...
├── app-0.9.9/ ← 旧版本
│ ├── .destroy ← 待删除标记
│ └── ...
└── .Launcher/ ← Launcher 数据目录
├── state/
│ └── first_run_completed ← OOBE 完成标记
├── update/
│ ├── incoming/ ← 更新缓存
│ │ ├── files.json
│ │ ├── files.json.sig
│ │ └── update.zip
│ └── public-key.pem ← RSA 公钥
└── snapshots/ ← 更新快照
└── {snapshot-id}.json
```
### 版本标记文件
| 文件名 | 作用 | 创建时机 | 删除时机 |
|--------|------|----------|----------|
| `.current` | 标记当前使用的版本 | 更新完成后 | 新版本激活时 |
| `.partial` | 标记下载未完成的版本 | 开始下载时 | 下载完成验证通过后 |
| `.destroy` | 标记待删除的旧版本 | 新版本激活时 | 目录删除后 |
## 核心服务
### DeploymentLocator
**职责**: 扫描和定位版本目录,选择最佳版本
**关键方法**:
```csharp
// 查找当前部署目录
string? FindCurrentDeploymentDirectory()
// 解析主程序可执行文件路径
string? ResolveHostExecutablePath()
// 获取当前版本号
string GetCurrentVersion()
// 构建下一个部署目录路径
string BuildNextDeploymentDirectory(string targetVersion)
// 清理标记为 .destroy 的目录
void CleanupDestroyedDeployments()
```
**版本选择算法**:
1. 扫描所有 `app-*` 目录
2. 过滤掉带 `.destroy``.partial` 标记的目录
3. 优先选择带 `.current` 标记的版本
4. 如果没有 `.current`,选择版本号最高的
### Host UpdateOrchestrator
**职责**: 更新检查、频道策略、manifest 解析、下载与安装触发位于 Host 的 `LanMountainDesktop/Services/Update/UpdateOrchestrator.cs`。Launcher 不再提供 `update check` / `update download` CLI。
### Host UpdateInstallGateway
**职责**: 更新应用与回滚入口位于 Host。`UpdateOrchestrator` 下载后调用 `UpdateInstallGateway` 在 Host 进程内应用 PLONDS payload回滚通过 Host 的 `UpdateRollbackGateway` 执行。
### LauncherOrchestrator / LaunchPipeline
**职责**: 协调完整的启动流程(`Shell/LauncherOrchestrator.cs` + `Startup/LaunchPipeline.cs`
**启动阶段 (ILaunchPhase)**:
1. `CleanupDeploymentsPhase` — 清理旧部署
2. `ExistingHostProbePhase` — 多实例 / 现有 Host 探测
3. `OobeGatePhase` — OOBE 步骤
4. `LaunchHostPhase` — 启动 Host
5. `MonitorStartupPhase` — IPC 启动监控
**GUI 入口**: `Shell/LauncherCompositionRoot` + `Shell/LauncherServiceRegistration`MS DI 轻量装配)
### ~~LauncherFlowCoordinator~~ (已移除)
已由 `LauncherOrchestrator` + `LaunchPipeline` 替代。
### OobeStateService
**职责**: 管理首次运行状态
**关键方法**:
```csharp
// 检查是否首次运行
bool IsFirstRun()
// 标记 OOBE 已完成
void MarkCompleted()
```
### PluginInstallerService
**职责**: CLI 维护命令下的插件包安装(`plugin install`)。应用内插件市场安装由 Host 在启动时应用 pending 队列,不经过 Launcher 正常启动流程。
**关键方法**:
```csharp
// 安装插件包CLI 维护)
LauncherResult InstallPackage(string sourcePath, string pluginsDirectory)
```
### PluginUpgradeQueueService
**职责**: CLI 维护命令下的待处理插件升级(`plugin update`。Launcher 正常 GUI 启动流程不再应用 pending 队列Host 在 `PluginRuntimeService.ApplyPendingPluginOperations()` 中统一处理。
**关键方法**:
```csharp
// 应用待处理的插件升级CLI 维护)
LauncherResult ApplyPendingUpgrades(string pluginsDirectory)
```
## 版本管理
### 版本选择算法详解
```csharp
public string? FindCurrentDeploymentDirectory()
{
var candidates = Directory.GetDirectories(rootDir, "app-*");
// 1. 过滤无效版本
var validCandidates = candidates
.Where(path =>
!File.Exists(Path.Combine(path, ".destroy")) &&
!File.Exists(Path.Combine(path, ".partial")))
.ToList();
// 2. 优先选择带 .current 标记的
var withMarkers = validCandidates
.Where(path => File.Exists(Path.Combine(path, ".current")))
.OrderByDescending(path => ParseVersion(path))
.FirstOrDefault();
if (withMarkers != null)
return withMarkers;
// 3. 选择版本号最高的
return validCandidates
.OrderByDescending(path => ParseVersion(path))
.FirstOrDefault();
}
```
### 版本激活流程
```csharp
private void ActivateDeployment(string fromDeployment, string toDeployment)
{
// 1. 在新版本添加 .current 标记
File.WriteAllText(Path.Combine(toDeployment, ".current"), string.Empty);
// 2. 移除旧版本的 .current 标记
var fromCurrent = Path.Combine(fromDeployment, ".current");
if (File.Exists(fromCurrent))
File.Delete(fromCurrent);
// 3. 标记旧版本为待删除
File.WriteAllText(Path.Combine(fromDeployment, ".destroy"), string.Empty);
// 4. 移除新版本的 .partial 标记 (如果有)
var toPartial = Path.Combine(toDeployment, ".partial");
if (File.Exists(toPartial))
File.Delete(toPartial);
}
```
### 版本清理流程
```csharp
public void CleanupDestroyedDeployments()
{
var destroyedDirs = Directory.GetDirectories(rootDir)
.Where(x => File.Exists(Path.Combine(x, ".destroy")));
foreach (var dir in destroyedDirs)
{
try
{
Directory.Delete(dir, recursive: true);
}
catch
{
// 忽略删除失败 (可能文件被占用)
// 下次启动时再试
}
}
}
```
## 启动流程
### 完整启动流程图
```
用户启动 Launcher.exe
清理旧版本 (.destroy 目录)
首次运行? ──Yes→ 显示 OOBE 窗口
↓ No
显示 Splash 窗口
检查待处理的更新
有更新? ──Yes→ 应用更新 (原子化)
↓ No
处理插件升级队列
选择最佳版本 (DeploymentLocator)
启动主程序 (Process.Start)
关闭 Splash 窗口
Launcher 退出
```
### 代码流程
**Program.cs**:
```csharp
static async Task<int> Main(string[] args)
{
var commandContext = CommandContext.FromArgs(args);
// 处理 CLI 命令
if (commandContext.Command != "launch")
return await Commands.RunCliCommandAsync(commandContext);
// 启动 Avalonia 应用
LauncherRuntimeContext.Current = commandContext;
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
return Environment.ExitCode;
}
```
**App.axaml.cs / Shell 入口**:
```csharp
public override void OnFrameworkInitializationCompleted()
{
var splashWindow = LaunchEntryHandler.CreateSplashWindow();
splashWindow.Show();
_ = LauncherCompositionRoot.RunOrchestratorWithSplashAsync(desktop, context, splashWindow);
}
```
**LaunchPipeline**:
```csharp
internal sealed class LaunchPipeline
{
public async Task<LauncherResult> ExecuteAsync(LaunchContext context, CancellationToken cancellationToken = default)
{
foreach (var phase in _phases)
{
var result = await phase.ExecuteAsync(context, cancellationToken);
if (result.Status == LaunchPhaseStatus.Completed)
{
return result.Result!;
}
}
return LaunchResultBuilder.BuildFailure("launch", "pipeline_incomplete", "Launch pipeline finished without producing a result.");
}
}
```
`LauncherFlowCoordinator` 已删除。GUI 顶层生命周期由 `LauncherGuiCoordinator` 处理,启动阶段由 `LaunchPipeline` 和各 `ILaunchPhase` 承载;更新检查、下载、应用与回滚均由 Host 处理。
## 命令行接口
### launch - 启动应用
```bash
LanMountainDesktop.Launcher.exe launch
```
启动完整流程: OOBE → Splash → 更新 → 插件 → 主程序
### plugin install - 安装插件
```bash
LanMountainDesktop.Launcher.exe plugin install <path-to-plugin.laapp>
```
维护兼容入口:直接把 `.laapp` 插件包写入指定插件目录。应用内插件市场不再使用 Launcher 做普通插件安装;市场安装会先把包下载到当前用户的 pending 队列,并在下一次 Host 启动、插件发现前应用。
## 开发指南
### 本地调试
**直接运行 Launcher:**
```bash
dotnet run --project LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj -- launch
```
**调试特定命令:**
```bash
# Launcher 不提供更新/回滚 CLI调试更新请运行主程序并使用设置页或 Host 更新服务。
```
### 模拟多版本环境
```bash
# 1. 发布主程序
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj -c Debug -o ./test-deploy/app-1.0.0
# 2. 创建 .current 标记
New-Item -ItemType File -Path ./test-deploy/app-1.0.0/.current
# 3. 复制 Launcher 到根目录
Copy-Item LanMountainDesktop.Launcher/bin/Debug/net10.0/* ./test-deploy/
# 4. 运行 Launcher
./test-deploy/LanMountainDesktop.Launcher.exe launch
```
### 测试更新流程
```bash
# 1. 创建两个版本
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj -o ./test-deploy/app-1.0.0
dotnet publish LanMountainDesktop/LanMountainDesktop.csproj -o ./test-deploy/app-1.0.1
# 2. 生成增量包
pwsh ./scripts/Generate-DeltaPackage.ps1 `
-PreviousVersion "1.0.0" `
-CurrentVersion "1.0.1" `
-PreviousDir "./test-deploy/app-1.0.0" `
-CurrentDir "./test-deploy/app-1.0.1" `
-OutputDir "./test-deploy/.Launcher/update/incoming"
# 3. 测试应用更新
# 运行主程序并通过 Host 更新服务触发下载、应用和回滚。
```
### 添加新的 OOBE 步骤
1. 实现 `IOobeStep` 接口:
```csharp
public class MyOobeStep : IOobeStep
{
public async Task RunAsync(CancellationToken cancellationToken)
{
// 显示 OOBE 窗口
// 等待用户完成
}
}
```
2.`Shell/LauncherOrchestrator.cs` 的 OOBE step 组装处注册,或通过后续 `ILaunchPhase`/OOBE 装配点接入:
```csharp
_oobeSteps = [
new WelcomeOobeStep(_oobeStateService),
new MyOobeStep() // 添加新步骤
];
```
当前内置 OOBE 向导窗口(`OobeWindow`)内步骤顺序包含:开场 → 主题 → **数据保存位置****启动与展示** → 隐私与遥测 → 完成。「启动与展示」写入 Host 的 `settings.json`PascalCase并在 Windows 下同步 Run 项,实现代码在 `HostAppSettingsOobeMerger.cs``LauncherWindowsStartupService.cs`,界面与逻辑挂在 `Views/OobeWindow.axaml(.cs)`
### 自定义更新源
更新源配置与 manifest provider 位于 Host 更新服务中,优先查看 `LanMountainDesktop/Services/Update/UpdateOrchestrator.cs``SettingsUpdateManifestProvider.cs` 与具体 provider。
## 相关文档
- [更新系统详细文档](UPDATE_SYSTEM.md)
- [构建和部署指南](BUILD_AND_DEPLOY.md)
- [架构文档](ARCHITECTURE.md)
- [开发文档](DEVELOPMENT.md)
## Current OOBE and Elevation Contract
- OOBE state is a per-user truth source stored at `%LOCALAPPDATA%\LanMountainDesktop\.launcher\state\oobe-state.json`.
- Same-user reinstall or upgrade must not re-enter OOBE.
- `first_run_completed` is legacy compatibility data only and should not remain the long-term primary format.
- Launch source values are `normal`, `postinstall`, `plugin-install`, and `debug-preview`.
- Auto-OOBE is allowed only for normal user-mode startup.
- `postinstall` may open OOBE only when the launcher is not elevated and the user state path is available.
- `plugin-install` and `debug-preview` must not auto-enter OOBE.
- Allowed elevation paths are limited to the installer itself, full installer update application, and user-confirmed legacy uninstall.
- Default plugin installation targets the Host data root and must not request elevation when that directory is writable.
- The Launcher `plugin install` maintenance command accepts `--app-root` so it can verify the configured data root before writing. It rejects targets outside that root.
- In-app market installs are deferred Host-side operations when the data root is writable: download and verify now, apply from the pending queue on the next Host startup.
- If portable data is configured under an administrator-protected install path, Host stages the package in a user-writable download directory and invokes the restricted Launcher maintenance command with UAC to copy the package into `Extensions/Plugins`.
## Public IPC Baseline
Launcher now consumes Host startup telemetry from the unified public IPC stack:
- Host publishes `StartupProgressMessage` via `lanmountain.launcher.startup-progress`
- Host publishes `LoadingStateMessage` via `lanmountain.launcher.loading-state`
- Launcher connects through `LanMountainDesktopIpcClient`
The previous custom length-prefixed named-pipe transport is no longer the primary startup communication path.
## Coordinator Guard
Launcher also owns a small per-user local coordinator used only between Launcher processes. It reserves `startup-attempt.json` before host launch, publishes a heartbeat, and exposes a local coordinator pipe for secondary Launchers. A secondary Launcher must attach to that coordinator or activate the existing Host through Public IPC instead of starting another Host process. See [Launcher Coordinator](LAUNCHER_COORDINATOR.md).