ci.plonds

This commit is contained in:
lincube
2026-04-21 16:12:47 +08:00
parent d31aa90b9c
commit 8568fdf16b
12 changed files with 1662 additions and 437 deletions

View File

@@ -1,10 +1,10 @@
# PLONDS Skeleton
# PLONDS 骨架
Penguin Logistics Online Network Distribution System, or PLONDS, is the standalone update-distribution skeleton for LanMountainDesktop.
Penguin Logistics Online Network Distribution System(企鹅物流在线网络分发系统),简称 PLONDS LanMountainDesktop 的独立更新分发骨架。
This directory is intentionally isolated from the main app and Launcher. It contains only the new distribution protocol, a thin read-only API, and sample S3-style metadata files.
本目录有意与主应用和启动器隔离,仅包含新的分发协议、一个轻量级的只读 API以及示例 S3 风格的元数据文件。
## Directory Layout
## 目录结构
```text
PenguinLogisticsOnlineNetworkDistributionSystem/
@@ -22,72 +22,72 @@ PenguinLogisticsOnlineNetworkDistributionSystem/
distributions/
```
## Projects
## 项目说明
- `Plonds.Shared` provides protocol constants and models.
- `Plonds.Core` owns hashing, diffing, object-repo generation, manifest generation, signing, and publish orchestration.
- `Plonds.Tool` is the CI-facing CLI entrypoint. PowerShell should stay as a thin wrapper around this tool.
- `Plonds.Api` is a thin read-only API that reads metadata from a local folder laid out like S3.
- `Plonds.Shared` 提供协议常量和数据模型。
- `Plonds.Core` 负责哈希计算、差异生成、对象仓库生成、清单生成、签名和发布编排。
- `Plonds.Tool` 是面向 CI 的命令行入口。PowerShell 脚本应保持为围绕此工具的薄包装层。
- `Plonds.Api` 是一个轻量级只读 API从类似 S3 布局的本地文件夹中读取元数据。
## Architecture
## 架构设计
PLONDS is intentionally built around a single C# implementation stack so the protocol and publish behavior do not drift across languages.
PLONDS 有意围绕单一的 C# 实现栈构建,以确保协议和发布行为不会在不同语言之间产生偏差。
```text
Host App
-> checks updates, downloads objects, stages incoming payload
Launcher
-> verifies signature, applies file map, switches deployment, rolls back
宿主应用
-> 检查更新、下载对象、暂存传入的负载
启动器
-> 验证签名、应用文件映射、切换部署、回滚
PLONDS.Api
-> read-only metadata projection for clients
-> 面向客户端的只读元数据投影
PLONDS.Tool
-> CI/release command surface
-> CI/发布命令界面
PLONDS.Core
-> hash/diff/object-repo/sign/publish implementation
-> 哈希/差异/对象仓库/签名/发布实现
PLONDS.Shared
-> protocol constants and DTOs
-> 协议常量和 DTO
```
Rules for v1:
## v1 规则
- Core protocol behavior should live in `Plonds.Core`, not in PowerShell scripts.
- `scripts/*.ps1` may remain only as thin wrappers for GitHub Actions and local convenience.
- Host keeps download responsibility.
- Launcher keeps apply, atomic switch, snapshot, and rollback responsibility.
- 核心协议行为应位于 `Plonds.Core` 中,而非 PowerShell 脚本。
- `scripts/*.ps1` 仅可作为 GitHub Actions 和本地便利的薄包装层保留。
- 宿主应用保留下载职责。
- 启动器保留应用、原子切换、快照和回滚职责。
## Storage Layout
## 存储布局
The first version keeps one fixed object root:
第一版本保持固定的对象根目录:
```text
lanmountain/update/
repo/sha256/<prefix>/<hash>
meta/channels/<channel>/<platform>/latest.json
meta/distributions/<distributionId>.json
installers/<platform>/<version>/...
repo/sha256/<前缀>/<哈希>
meta/channels/<频道>/<平台>/latest.json
meta/distributions/<分发ID>.json
installers/<平台>/<版本>/...
```
Planned but not enabled in v1:
已规划但 v1 中未启用:
```text
lanmountain/update/repo-compressed/<algo>/<prefix>/<hash>
lanmountain/update/patches/<algo>/<baseHash>/<targetHash>
lanmountain/update/repo-compressed/<算法>/<前缀>/<哈希>
lanmountain/update/patches/<算法>/<基础哈希>/<目标哈希>
```
## Public Endpoints
## 公共接口
The API base path is `/api/plonds/v1`.
API 基础路径为 `/api/plonds/v1`
- `GET /healthz`
- `GET /api/plonds/v1/metadata`
- `GET /api/plonds/v1/channels/{channel}/{platform}/latest?currentVersion=...`
- `GET /api/plonds/v1/distributions/{distributionId}`
- `GET /healthz` - 健康检查
- `GET /api/plonds/v1/metadata` - 获取元数据目录
- `GET /api/plonds/v1/channels/{channel}/{platform}/latest?currentVersion=...` - 获取指定频道和平台的最新版本
- `GET /api/plonds/v1/distributions/{distributionId}` - 获取指定分发版本的完整信息
## Local Run
## 本地运行
```powershell
dotnet run --project src/Plonds.Api
```
By default the API reads metadata from `sample-data`.
默认情况下API 从 `sample-data` 读取元数据。

View File

@@ -13,4 +13,11 @@ public sealed record PlondsGenerateOptions(
string? FileMapUrl = null,
string? FileMapSignatureUrl = null,
string? InstallerDirectory = null,
string? InstallerBaseUrl = null);
string? InstallerBaseUrl = null,
string IncrementalStrategy = "release-payload",
string? BaselineVersion = null,
string? BaselineRef = null,
string? SourceCommit = null,
bool IsFullPayloadRelease = false,
string? CommitRangeStart = null,
string? CommitRangeEnd = null);

View File

@@ -42,11 +42,16 @@ public sealed class PlondsGenerator
Directory.CreateDirectory(metaDistributionRoot);
Directory.CreateDirectory(metaChannelRoot);
var previousManifest = ScanDirectory(previousDirectory);
var previousManifest = options.IsFullPayloadRelease
? new Dictionary<string, FileFingerprint>(StringComparer.OrdinalIgnoreCase)
: ScanDirectory(previousDirectory);
var currentManifest = ScanDirectory(currentDirectory);
var fileEntries = BuildFileEntries(previousManifest, currentManifest, repoRoot, options.RepoBaseUrl);
var installerMirrors = BuildInstallerMirrors(options.Platform, installerMirrorRoot, options.InstallerDirectory, options.InstallerBaseUrl);
var publishedAt = DateTimeOffset.UtcNow;
var baselineVersion = string.IsNullOrWhiteSpace(options.BaselineVersion)
? options.PreviousVersion
: options.BaselineVersion;
var fileMap = new FileMapDocument(
FormatVersion: "1.0",
@@ -69,7 +74,14 @@ public sealed class PlondsGenerator
Metadata: new Dictionary<string, string>
{
["protocol"] = "PLONDS",
["mode"] = "file-object"
["mode"] = "file-object",
["baselineVersion"] = baselineVersion,
["incrementalStrategy"] = options.IncrementalStrategy,
["isFullPayloadRelease"] = options.IsFullPayloadRelease ? "true" : "false",
["sourceCommit"] = options.SourceCommit ?? string.Empty,
["baselineRef"] = options.BaselineRef ?? string.Empty,
["commitRangeStart"] = options.CommitRangeStart ?? string.Empty,
["commitRangeEnd"] = options.CommitRangeEnd ?? string.Empty
});
var distribution = new DistributionDocument(
@@ -83,7 +95,17 @@ public sealed class PlondsGenerator
Components: fileMap.Components,
InstallerMirrors: installerMirrors,
Capabilities: ["file-object"],
Metadata: new Dictionary<string, string> { ["protocol"] = "PLONDS" });
Metadata: new Dictionary<string, string>
{
["protocol"] = "PLONDS",
["baselineVersion"] = baselineVersion,
["incrementalStrategy"] = options.IncrementalStrategy,
["isFullPayloadRelease"] = options.IsFullPayloadRelease ? "true" : "false",
["sourceCommit"] = options.SourceCommit ?? string.Empty,
["baselineRef"] = options.BaselineRef ?? string.Empty,
["commitRangeStart"] = options.CommitRangeStart ?? string.Empty,
["commitRangeEnd"] = options.CommitRangeEnd ?? string.Empty
});
var latest = new LatestPointerDocument(
DistributionId: distributionId,
@@ -225,6 +247,7 @@ public sealed class PlondsGenerator
Platform: platform,
Arch: ResolveArch(platform),
Url: url,
Name: fileName,
FileName: fileName,
Sha256: ComputeSha256(destinationPath),
Size: new FileInfo(destinationPath).Length));
@@ -345,6 +368,7 @@ public sealed class PlondsGenerator
string Platform,
string Arch,
string? Url,
string? Name,
string? FileName,
string? Sha256,
long Size);

View File

@@ -9,4 +9,11 @@ public sealed record PlondsPublishOptions(
string Channel = "stable",
string? BaselineRoot = null,
string? RepoBaseUrl = null,
string? InstallerBaseUrl = null);
string? InstallerBaseUrl = null,
string IncrementalStrategy = "release-payload",
string? BaselineVersion = null,
string? BaselineRef = null,
string? SourceCommit = null,
bool IsFullPayloadRelease = false,
string? CommitRangeStart = null,
string? CommitRangeEnd = null);

View File

@@ -82,7 +82,7 @@ public sealed class PlondsPublisher
CurrentDirectory: currentAppDirectory,
Platform: config.Platform,
OutputRoot: options.OutputRoot,
PreviousVersion: previousVersion,
PreviousVersion: string.IsNullOrWhiteSpace(options.BaselineVersion) ? previousVersion : options.BaselineVersion,
PreviousDirectory: previousDirectory,
Channel: options.Channel,
DistributionId: distributionId,
@@ -90,7 +90,14 @@ public sealed class PlondsPublisher
FileMapUrl: fileMapUrl,
FileMapSignatureUrl: fileMapSignatureUrl,
InstallerDirectory: installerSourceDirectory,
InstallerBaseUrl: installerBaseUrl));
InstallerBaseUrl: installerBaseUrl,
IncrementalStrategy: options.IncrementalStrategy,
BaselineVersion: string.IsNullOrWhiteSpace(options.BaselineVersion) ? previousVersion : options.BaselineVersion,
BaselineRef: options.BaselineRef,
SourceCommit: options.SourceCommit,
IsFullPayloadRelease: options.IsFullPayloadRelease,
CommitRangeStart: options.CommitRangeStart,
CommitRangeEnd: options.CommitRangeEnd));
_signer.SignFile(result.FileMapPath, options.PrivateKeyPath, result.SignaturePath);

View File

@@ -86,7 +86,14 @@ internal static class PlondsCli
Channel: Get(options, "channel", "stable") ?? "stable",
BaselineRoot: Get(options, "baseline-root"),
RepoBaseUrl: Get(options, "repo-base-url"),
InstallerBaseUrl: Get(options, "installer-base-url")));
InstallerBaseUrl: Get(options, "installer-base-url"),
IncrementalStrategy: Get(options, "incremental-strategy", "release-payload") ?? "release-payload",
BaselineVersion: Get(options, "baseline-version"),
BaselineRef: Get(options, "baseline-ref"),
SourceCommit: Get(options, "source-commit"),
IsFullPayloadRelease: bool.TryParse(Get(options, "is-full-payload-release", "false"), out var isFullPayloadRelease) && isFullPayloadRelease,
CommitRangeStart: Get(options, "commit-range-start"),
CommitRangeEnd: Get(options, "commit-range-end")));
foreach (var result in results)
{