diff --git a/.github/workflows/packer.yml b/.github/workflows/packer.yml index 57176bcd408b..b1148cba9f38 100644 --- a/.github/workflows/packer.yml +++ b/.github/workflows/packer.yml @@ -10,7 +10,6 @@ on: - 'projects/**' - jobs: build-packer: name: Build / Cache Packer @@ -63,54 +62,16 @@ jobs: if: steps.cache-uploader.outputs.cache-hit != 'true' run: dotnet publish .\src\Uploader\Uploader.csproj -o ./ -r win-x64 - initialize-release: - name: Initialize Release - runs-on: windows-latest - steps: - - - name: Create timestamp - id: create_timestamp - run: echo "timestamp=$(date '+%Y%m%d%H%M%s')" >> $GITHUB_OUTPUT - shell: bash - - # Create the release: https://github.com/actions/create-release - - name: Create release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: Snapshot-${{ steps.create_timestamp.outputs.timestamp }} - release_name: 汉化资源包-Snapshot-${{ steps.create_timestamp.outputs.timestamp }} - draft: false - prerelease: false - outputs: - upload-url: ${{ steps.create_release.outputs.upload_url }} - tag-name: Snapshot-${{ steps.create_timestamp.outputs.timestamp }} - - pack: name: Pack Resources and Upload Artifacts/Releases - needs: [ build-packer, initialize-release ] # 显然,需要存在打包程序,才能打包。 + needs: [ build-packer ] strategy: - fail-fast: false # 把正常的文件先打包了,避免一处错误阻塞整个仓库。 + fail-fast: false matrix: # 版本列表。将对这里的每个版本判断,按需打包。 # 如需添加新版本,在这里添加即可。 version: [ "1.12.2", "1.16", "1.16-fabric", "1.18", "1.18-fabric", "1.19", "1.20", "1.20-fabric", "1.21", "1.21-fabric" ] runs-on: windows-latest - outputs: - # 为每个版本创建独立的输出变量 - updated_versions_1_12_2: ${{ steps.collect-updated.outputs.version_1_12_2 }} - updated_versions_1_16: ${{ steps.collect-updated.outputs.version_1_16 }} - updated_versions_1_16_fabric: ${{ steps.collect-updated.outputs.version_1_16_fabric }} - updated_versions_1_18: ${{ steps.collect-updated.outputs.version_1_18 }} - updated_versions_1_18_fabric: ${{ steps.collect-updated.outputs.version_1_18_fabric }} - updated_versions_1_19: ${{ steps.collect-updated.outputs.version_1_19 }} - updated_versions_1_20: ${{ steps.collect-updated.outputs.version_1_20 }} - updated_versions_1_20_fabric: ${{ steps.collect-updated.outputs.version_1_20_fabric }} - updated_versions_1_21: ${{ steps.collect-updated.outputs.version_1_21 }} - updated_versions_1_21_fabric: ${{ steps.collect-updated.outputs.version_1_21_fabric }} steps: - uses: actions/checkout@v2 with: @@ -125,7 +86,7 @@ jobs: path: | Packer.exe git2-*.dll - fail-on-cache-miss: true # 前一步理应构造过的。如果不命中,肯定有问题,不如直接挂掉。 + fail-on-cache-miss: true # 应由前序保证 - name: Check changed path on ${{ matrix.version }} uses: MarceloPrado/has-changed-path@v1.0 @@ -153,140 +114,15 @@ jobs: ${{ matrix.version }}.md5 if: steps.check-changes.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' - - name: Collect updated versions - id: collect-updated - run: | - if [ "${{ steps.check-changes.outputs.changed }}" == "true" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Replace all periods and hyphens with underscores - # 将.转换为_,适配变量名 - output_key=$(echo "${{ matrix.version }}" | sed 's/[\.-]/_/g') - echo "version_$output_key=${{ matrix.version }}" >> $GITHUB_OUTPUT - fi - shell: bash - continue-on-error: true - - upload-release-assets: - name: Upload Release Assets - needs: [ pack, initialize-release ] - runs-on: windows-latest - steps: - - name: Download all Artifacts - uses: actions/download-artifact@v4 - with: - path: artifacts/ - - - name: Upload Release Assets - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Get the upload URL from the previous job - $upload_url = "${{ needs.initialize-release.outputs.upload-url }}" - - # Clean up the URL by removing the template part {?name,label} - $clean_upload_url = $upload_url.Split('{')[0] - - # Iterate through downloaded artifact directories - Get-ChildItem -Path "artifacts" -Directory | ForEach-Object { - $artifact_dir_name = $_.Name - $version_tag = ($artifact_dir_name -split '-Modpack-')[1] - - # Generate the correct asset names - if ($version_tag -eq '1.12.2') { - $zip_asset_name = "Minecraft-Mod-Language-Modpack.zip" - } else { - $formatted_version = $version_tag -replace '\.', '-' - $formatted_version = $formatted_version -replace 'fabric', 'Fabric' - $zip_asset_name = "Minecraft-Mod-Language-Modpack-$formatted_version.zip" - } - - # Build file paths using sub-expression operator - $zip_path = "$(Join-Path -Path $_.FullName -ChildPath ($artifact_dir_name + '.zip'))" - $md5_path = "$(Join-Path -Path $_.FullName -ChildPath ($version_tag + '.md5'))" - - # Build the full URL using the format operator -f - $zip_upload_url = "{0}?name={1}" -f $clean_upload_url, $zip_asset_name - $md5_upload_url = "{0}?name={1}" -f $clean_upload_url, ($version_tag + ".md5") - - # Upload ZIP file - echo "Uploading ZIP: $zip_path as $zip_asset_name" - curl.exe -X POST ` - -H "Authorization: token $env:GITHUB_TOKEN" ` - -H "Content-Type: application/zip" ` - --data-binary "@$zip_path" ` - $zip_upload_url - - # Upload MD5 file - echo "Uploading MD5: $md5_path as $version_tag.md5" - $md5_content = Get-Content -Path "$md5_path" - curl.exe -X POST ` - -H "Authorization: token $env:GITHUB_TOKEN" ` - -H "Content-Type: text/plain" ` - --data-raw "$md5_content" ` - $md5_upload_url - } - shell: pwsh - - update-index: - name: Update Version Index (Optional) - needs: [pack, initialize-release] - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Set up index branch - run: | - git fetch origin index - git checkout index || git checkout -b index - - - name: Download existing index.json - run: | - if [ ! -f version-index.json ]; then - echo "{}" > version-index.json - fi - - - name: Update index.json - env: - RELEASE_TAG: ${{ needs.initialize-release.outputs.tag-name }} - PACK_OUTPUTS: ${{ toJSON(needs.pack.outputs) }} - run: | - python3 - < + { + var fileExtensionName = _.Extension; // 带点名称,应当为 ".zip" + var fileName = _.Name[0..^fileExtensionName.Length] + .RegulateFileName(); + return (name: fileName + fileExtensionName, file: _); + }); + var md5s = artifactDirectory + .EnumerateFiles("*.md5", SearchOption.AllDirectories) + .Select(_ => (name: _.Name, file: _)); + var files = packs.Concat(md5s); + + Console.WriteLine("待上传的文件数目:{0}", files.Count()); + + IEnumerable tasks = + [ + UploadToServer(host, name, password, files), + UploadSnapshotAssets(client, files), + UpdateAutobuildAssets(client, files) + ]; + await Task.WhenAll(tasks); + } + + async static Task UploadToServer(string host, string username, string password, IEnumerable<(string name, FileInfo file)> files) + { + using var scpClient = new ScpClient(host, port: 22, username, password); scpClient.Connect(); // 与下载服务器建立连接 - + // 确认连接状态 if (scpClient.IsConnected) { @@ -28,59 +73,91 @@ static int Main(string host, string name, string password) else { Log.Error("SCP服务器连接失败"); - return -1; + throw new InvalidOperationException(); } - - // 获取可用的资源包,准备上传 - var artifactDirectory = new DirectoryInfo(Path.Join(Directory.GetCurrentDirectory(), "artifacts")); - var packList = artifactDirectory - .EnumerateFiles("Minecraft-Mod-Language-Modpack-*.zip", SearchOption.AllDirectories); - - Log.Information("检测到的资源包数目:{0}", packList.Count()); + foreach (var (name, file) in files) + { + var destinationName = $"/var/www/html/files/{name}"; + scpClient.Upload(file, destinationName); // 没有async :( + Log.Information(" 写入文件:{0}", destinationName); + } + } - packList.ToList() - .ForEach(_ => + async static Task UploadSnapshotAssets(GitHubClient client, IEnumerable<(string name, FileInfo file)> files) + { + var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); + Log.Information(" 时间戳:{0}", timestamp); + var newRelease = new NewRelease($"Snapshot-{timestamp}") + { + TargetCommitish = Environment.GetEnvironmentVariable("SHA"), + Name = $"汉化资源包-{timestamp}" + }; + var result = await client.Repository.Release.Create(long.Parse(Environment.GetEnvironmentVariable("REPO_ID")!), newRelease); + Log.Information(" 创建 Release"); + foreach (var (name, file) in files) + { + using var fileStream = file.OpenRead(); + var newAsset = new ReleaseAssetUpload( + name, + file.Extension switch { - using var stream = _.OpenRead(); - var md5 = stream.ComputeMD5(); - - // 文件名格式:Minecraft-Mod-Language-Modpack-[dashed-version]-[md5-hash].zip - // 如:Minecraft-Mod-Language-Modpack-1-16-Fabric-0000000000000000.zip - // hash的对象是文件内容,不包括文件名(当然) - // hash应该是全大写 - - var fileExtensionName = _.Extension; // 带点名称,应当为 ".zip" - var fileName = _.Name[0..^fileExtensionName.Length] - .RegulateFileName(); // 无后缀的文件名,应当已修正 - - // 选择性地加上该文件的md5值,以便生成patch - var tweakedName = fileName + "-" + md5; - - var destinationName = $"/var/www/html/files/{fileName + fileExtensionName}"; - var tweakedDestinationName = $"/var/www/html/files/{tweakedName + fileExtensionName}"; - - // 传递不带md5值的最新版本;会覆写已有文件 - scpClient.Upload(_.OpenRead(), destinationName); - Log.Information("向远程服务器写入文件:{0}", destinationName); - - //// 传递带md5值的历史版本,一般不会覆写已有文件 - //scpClient.Upload(_.OpenRead(), tweakedDestinationName); - //Log.Information("向远程服务器写入文件:{0}", tweakedDestinationName); - }); - - // 临时操作 在使用旧md5校验的程序弃用以后需要删除 - var md5List = artifactDirectory - .EnumerateFiles("*.md5", SearchOption.AllDirectories); - md5List.ToList() - .ForEach(_ => + ".zip" => "application/zip", + ".md5" => "text/plain", + _ => throw new ArgumentException($"Unexpected extension: {file.Extension}") + }, + fileStream, + timeout: null); + await client.Repository.Release.UploadAsset(result, newAsset); + Log.Information(" 上传文件:{0}", name); + } + } + + async static Task UpdateAutobuildAssets(GitHubClient client, IEnumerable<(string name, FileInfo file)> files) + { + var repoId = long.Parse(Environment.GetEnvironmentVariable("REPO_ID")!); + var release = await client.Repository.Release.Get(repoId, "autobuild"); + Log.Information(" 获取 autobuild Release"); + + var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"); + var desc = new ReleaseUpdate() { - scpClient.Upload(_.OpenRead(), $"/var/www/html/files/{_.Name}"); - Log.Information("向远程服务器写入文件:{0}", $"/var/www/html/files/{_.Name}"); - }); + Body = $""" + ## 汉化资源包 Autobuild + + ### 最后更新时间 + + - {timestamp} + """ + }; + await client.Repository.Release.Edit(repoId, release.Id, desc); + Log.Information(" 更新 Release 简介:时间 {0}", timestamp); - Log.Information("资源包传递完毕"); - return 0; + var assets = release.Assets; + var lookup = assets.Select(_ => (_.Name, _)).ToDictionary(); + foreach (var (name, file) in files) + { + using var fileStream = file.OpenRead(); + + if (lookup.TryGetValue(name, out ReleaseAsset? asset)) + { + await client.Repository.Release.DeleteAsset(repoId, asset.Id); + Log.Information(" 删除旧文件:{0}", name); + } + var newAsset = new ReleaseAssetUpload( + name, + file.Extension switch + { + ".zip" => "application/zip", + ".md5" => "text/plain", + _ => throw new ArgumentException($"Unexpected extension: {file.Extension}") + }, + fileStream, + timeout: null); + await client.Repository.Release.UploadAsset(release, newAsset); + Log.Information(" 上传文件:{0}", name); + + } } public static string RegulateFileName(this string fileName) @@ -98,16 +175,5 @@ string Capitalize(string text) => string.Join("", text[0..1].ToUpper(), text[1..]); } - - /// - /// 计算给定流中全体内容的MD5值。 - /// - /// 被计算的流 - /// - public static string ComputeMD5(this Stream stream) - { - stream.Seek(0, SeekOrigin.Begin); // 确保文件流的位置被重置 - return Convert.ToHexString(MD5.Create().ComputeHash(stream)); - } } } diff --git a/src/Uploader/Uploader.csproj b/src/Uploader/Uploader.csproj index 4efac1109753..3138052add63 100644 --- a/src/Uploader/Uploader.csproj +++ b/src/Uploader/Uploader.csproj @@ -4,10 +4,12 @@ Exe net10.0 true + enable +