mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
* 激进的更新 * 试试 * fix.可爱的我一直在修CI( * fix.启动器一定要能够启动 * feat.尝试弄了AOT的启动器。 * fix.修CI,好像是因为Linux那边有个问题,反正修就对了。 * fix.ci难修,为什么liunx跑不起来呢? * Update build.yml * Update LanMountainDesktop.csproj * changed.调整了启动逻辑,优化了更新页面。 * changed.优化了更新体验 * feat.依旧试增量更新这一块,看看velopack * fix.我们试验性地修复了启动器无法正常启动的问题,原因可能是这个画面没有启动,就GUI没显示。然后还把编译问题修了一下。 * fix.继续修ci,ci怎么天天炸 * changed.velopack,试试rust * fix.修ci,修融合桌面,修启动器 * fix.GitHub Action工作流怎么天天出问题 * feat.引入velopack,不好,是rust(至少内存很安全了。 * chore: migrate release pipeline to signed filemap and wire rainyun s3 * fix: make optional s3 upload step workflow-parse safe * fix: make delta pack generation robust for empty diffs and linux paths * chore: rotate launcher update public key for pdc signing * fix: restore stable launcher update public key * fix: sync launcher public key with update signing secret * fix: normalize PEM line endings in signing key validation * fix: rotate launcher public key to match ci signing secret * fix: compare signing keys by SPKI instead of PEM text * refactor update backend to host-managed PDC pipeline * fix release workflow env key collisions * relax publish-pdc precheck to require S3 only * set GH_TOKEN for PDCC installer step * ci: add local pdc mock fallback for release publish * ci: fix pdc mock process log redirection * ci: fallback pdcc signing key to update private key * ci: ensure pdcc signing passphrase env is always set * ci: create pdcc publish root before invoking client * ci: set pdcc version variable from release version * ci: decouple pdcc installer version from publish config version * ci: package pdcc subchannels with generated filemap and changelog * ci: make local pdc mock diff return empty for fast fallback * ci: fix pdcc variable mapping and pdc signing prechecks * Update App.axaml.cs * ci: wire aws cli credentials for rainyun s3 * ci: pin pdcc client version separately from app version * ci: harden local pdc mock transport handling * ci: publish pdcc subchannels in one pass * ci: add pdcc publish heartbeat and timeout * ci: fix pdcc publish workdir bootstrap * feat.Penguin Logistics Online Network Distribution System * ci: fix plonds s3 probe and signing fallback * ci: validate signing key and quiet missing baselines * ci: relax aws checksum mode for rainyun s3 * ci: avoid multipart uploads to rainyun s3 * ci: handle empty plonds baselines safely * ci.plonds * Rebuild release pipeline around PLONDS and DDSS * Fix Windows installer script path in release workflow
194 lines
7.1 KiB
C#
194 lines
7.1 KiB
C#
using System.Text;
|
||
using System.Text.Json;
|
||
using LanMountainDesktop.Launcher.Models;
|
||
|
||
namespace LanMountainDesktop.Launcher.Services;
|
||
|
||
internal static class Commands
|
||
{
|
||
public static async Task<int> RunLegacyPluginInstallAsync(CommandContext context, PluginInstallerService installer)
|
||
{
|
||
var resultPath = context.GetOption("result");
|
||
LauncherResult result;
|
||
try
|
||
{
|
||
var source = context.GetOption("source") ?? string.Empty;
|
||
var pluginsDir = context.GetOption("plugins-dir") ?? string.Empty;
|
||
result = installer.InstallPackage(source, pluginsDir);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result = new LauncherResult
|
||
{
|
||
Success = false,
|
||
Stage = "plugin.install",
|
||
Code = "failed",
|
||
Message = ex.Message,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}
|
||
|
||
await WriteResultIfNeededAsync(resultPath, result).ConfigureAwait(false);
|
||
return result.Success ? 0 : 1;
|
||
}
|
||
|
||
public static async Task<int> RunCliCommandAsync(CommandContext context)
|
||
{
|
||
var appRoot = ResolveAppRoot(context);
|
||
var deploymentLocator = new DeploymentLocator(appRoot);
|
||
var updateEngine = new UpdateEngineService(deploymentLocator);
|
||
var pluginInstaller = new PluginInstallerService();
|
||
var pluginUpgrades = new PluginUpgradeQueueService(pluginInstaller);
|
||
|
||
LauncherResult result;
|
||
try
|
||
{
|
||
result = await ExecuteCoreAsync(context, updateEngine, pluginInstaller, pluginUpgrades).ConfigureAwait(false);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
result = new LauncherResult
|
||
{
|
||
Success = false,
|
||
Stage = "command",
|
||
Code = "exception",
|
||
Message = ex.Message,
|
||
ErrorMessage = ex.Message
|
||
};
|
||
}
|
||
|
||
await WriteResultIfNeededAsync(context.GetOption("result"), result).ConfigureAwait(false);
|
||
return result.Success ? 0 : 1;
|
||
}
|
||
|
||
private static async Task<LauncherResult> ExecuteCoreAsync(
|
||
CommandContext context,
|
||
UpdateEngineService updateEngine,
|
||
PluginInstallerService pluginInstaller,
|
||
PluginUpgradeQueueService pluginUpgrades)
|
||
{
|
||
switch (context.Command.ToLowerInvariant())
|
||
{
|
||
case "update":
|
||
return await ExecuteUpdateAsync(context, updateEngine).ConfigureAwait(false);
|
||
case "plugin":
|
||
return ExecutePluginCommand(context, pluginInstaller, pluginUpgrades);
|
||
default:
|
||
return new LauncherResult
|
||
{
|
||
Success = false,
|
||
Stage = "command",
|
||
Code = "unsupported_command",
|
||
Message = $"Unsupported command '{context.Command}'."
|
||
};
|
||
}
|
||
}
|
||
|
||
private static async Task<LauncherResult> ExecuteUpdateAsync(CommandContext context, UpdateEngineService updateEngine)
|
||
{
|
||
return context.SubCommand.ToLowerInvariant() switch
|
||
{
|
||
"check" => updateEngine.CheckPendingUpdate(),
|
||
"apply" => await updateEngine.ApplyPendingUpdateAsync().ConfigureAwait(false),
|
||
"rollback" => updateEngine.RollbackLatest(),
|
||
"download" => await DownloadUpdatePayloadAsync(context, updateEngine).ConfigureAwait(false),
|
||
_ => new LauncherResult
|
||
{
|
||
Success = false,
|
||
Stage = "update",
|
||
Code = "unsupported_subcommand",
|
||
Message = $"Unsupported update sub-command '{context.SubCommand}'."
|
||
}
|
||
};
|
||
}
|
||
|
||
private static async Task<LauncherResult> DownloadUpdatePayloadAsync(CommandContext context, UpdateEngineService updateEngine)
|
||
{
|
||
return await updateEngine.DownloadAsync(
|
||
context.GetOption("manifest-url") ?? throw new InvalidOperationException("Missing --manifest-url."),
|
||
context.GetOption("signature-url") ?? throw new InvalidOperationException("Missing --signature-url."),
|
||
context.GetOption("archive-url") ?? throw new InvalidOperationException("Missing --archive-url."),
|
||
CancellationToken.None).ConfigureAwait(false);
|
||
}
|
||
|
||
private static LauncherResult ExecutePluginCommand(
|
||
CommandContext context,
|
||
PluginInstallerService pluginInstaller,
|
||
PluginUpgradeQueueService pluginUpgrades)
|
||
{
|
||
switch (context.SubCommand.ToLowerInvariant())
|
||
{
|
||
case "install":
|
||
{
|
||
var source = context.GetOption("source") ?? throw new InvalidOperationException("Missing --source.");
|
||
var pluginsDir = context.GetOption("plugins-dir") ?? throw new InvalidOperationException("Missing --plugins-dir.");
|
||
return pluginInstaller.InstallPackage(source, pluginsDir);
|
||
}
|
||
case "update":
|
||
{
|
||
var pluginsDir = context.GetOption("plugins-dir") ?? throw new InvalidOperationException("Missing --plugins-dir.");
|
||
return pluginUpgrades.ApplyPendingUpgrades(pluginsDir);
|
||
}
|
||
default:
|
||
return new LauncherResult
|
||
{
|
||
Success = false,
|
||
Stage = "plugin",
|
||
Code = "unsupported_subcommand",
|
||
Message = $"Unsupported plugin sub-command '{context.SubCommand}'."
|
||
};
|
||
}
|
||
}
|
||
|
||
public static async Task WriteResultIfNeededAsync(string? resultPath, LauncherResult result)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(resultPath))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var fullPath = Path.GetFullPath(resultPath);
|
||
var dir = Path.GetDirectoryName(fullPath);
|
||
if (!string.IsNullOrWhiteSpace(dir))
|
||
{
|
||
Directory.CreateDirectory(dir);
|
||
}
|
||
|
||
var json = JsonSerializer.Serialize(result, AppJsonContext.Default.LauncherResult);
|
||
await File.WriteAllTextAsync(fullPath, json, Encoding.UTF8).ConfigureAwait(false);
|
||
}
|
||
|
||
public static string ResolveAppRoot(CommandContext context)
|
||
{
|
||
var configured = context.GetOption("app-root");
|
||
if (!string.IsNullOrWhiteSpace(configured))
|
||
{
|
||
return Path.GetFullPath(configured);
|
||
}
|
||
|
||
var baseDir = AppContext.BaseDirectory;
|
||
|
||
// 发布版结构:Launcher 和 app-* 目录在同一目录
|
||
// 检查当前目录是否有 app-* 子目录(发布版)
|
||
var appDirs = Directory.GetDirectories(baseDir, "app-*", SearchOption.TopDirectoryOnly);
|
||
if (appDirs.Length > 0)
|
||
{
|
||
// 找到 app-* 目录,说明是发布版结构
|
||
return baseDir;
|
||
}
|
||
|
||
// 开发环境:检查父目录是否有主程序
|
||
var parent = Path.GetFullPath(Path.Combine(baseDir, ".."));
|
||
var parentHost = OperatingSystem.IsWindows()
|
||
? Path.Combine(parent, "LanMountainDesktop.exe")
|
||
: Path.Combine(parent, "LanMountainDesktop");
|
||
if (File.Exists(parentHost))
|
||
{
|
||
return parent;
|
||
}
|
||
|
||
// 默认返回 baseDir
|
||
return baseDir;
|
||
}
|
||
}
|