diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 849881e0..b332f26a 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -8,6 +8,9 @@ on: branches: [ "master" ] workflow_dispatch: +permissions: + contents: write + jobs: BuildClickOnce: @@ -121,8 +124,10 @@ jobs: BUILD_NUMBER: ${{github.run_number}} run: | $buildVersion = Get-Date -Format "yyyy.MM.dd.$env:BUILD_NUMBER" + $releaseTag = "v$buildVersion" "BUILD_VERSION=$buildVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "build-version=$buildVersion" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append + "release-tag=$releaseTag" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append - name: Build Moder.Update updater shell: pwsh run: | @@ -162,6 +167,20 @@ jobs: $catalog = Get-Content $catalogPath -Raw | ConvertFrom-Json $catalog | Add-Member -NotePropertyName UpdaterChecksum -NotePropertyValue ((Get-FileHash .\artifacts\update-feed\moder_update_updater.exe -Algorithm SHA512).Hash.ToLower()) -Force $catalog | ConvertTo-Json -Depth 10 | Set-Content $catalogPath -Encoding utf8 + - name: Package full release asset + shell: pwsh + run: | + $releaseDir = '.\artifacts\release' + if (Test-Path $releaseDir) { + Remove-Item $releaseDir -Recurse -Force + } + New-Item -ItemType Directory -Path $releaseDir | Out-Null + $packageName = "TelegramSearchBot-win-x64-full-$env:BUILD_VERSION.zip" + $packagePath = Join-Path $releaseDir $packageName + Compress-Archive -Path .\artifacts\standalone\* -DestinationPath $packagePath -CompressionLevel Optimal + $hashPath = "$packagePath.sha512" + $hash = (Get-FileHash $packagePath -Algorithm SHA512).Hash.ToLower() + Set-Content -Path $hashPath -Value "$hash $packageName" -Encoding ascii - name: Check legacy bridge presence id: bridge-check shell: pwsh @@ -173,7 +192,7 @@ jobs: pip install --quiet --cache-dir C:\pip-cache b2 b2 account authorize $env:B2_APPKEY_ID $env:B2_APPKEY --quiet $markerPath = "TelegramSearchBot/$env:LEGACY_BRIDGE_MARKER" - $existingMarker = b2 ls --recursive $env:B2_BUCKET $markerPath + $existingMarker = b2 ls --recursive "b2://$env:B2_BUCKET/$markerPath" if ([string]::IsNullOrWhiteSpace($existingMarker)) { "publish-bridge=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append } else { @@ -205,12 +224,119 @@ jobs: B2_APPKEY_ID: ${{ secrets.B2_APPKEY_ID }} B2_APPKEY: ${{ secrets.B2_APPKEY }} run: | + function Get-B2Versions { + param( + [Parameter(Mandatory = $true)] + [string]$B2Uri, + [switch]$Recursive + ) + + $arguments = @('ls', '--json', '--versions') + if ($Recursive) { + $arguments += '--recursive' + } + $arguments += $B2Uri + + $output = & b2 @arguments 2>&1 + if ($LASTEXITCODE -ne 0) { + return @() + } + + $raw = ($output | Out-String).Trim() + if ([string]::IsNullOrWhiteSpace($raw)) { + return @() + } + + return @($raw | ConvertFrom-Json) + } + + function Remove-B2VersionById { + param( + [Parameter(Mandatory = $true)] + [string]$FileId + ) + + & b2 rm --no-progress "b2id://$FileId" | Out-Null + } + + function Prune-B2FileVersions { + param( + [Parameter(Mandatory = $true)] + [string]$RelativePath, + [int]$KeepLatest = 1 + ) + + $uri = "b2://$env:B2_BUCKET/$RelativePath" + $items = @(Get-B2Versions -B2Uri $uri) + if ($items.Count -le $KeepLatest) { + return + } + + $staleItems = $items | Sort-Object uploadTimestamp -Descending | Select-Object -Skip $KeepLatest + foreach ($item in $staleItems) { + Remove-B2VersionById -FileId $item.fileId + } + } + + function Prune-B2PackageVersions { + param( + [Parameter(Mandatory = $true)] + [string[]]$KeepRelativePaths + ) + + $items = @(Get-B2Versions -B2Uri "b2://$env:B2_BUCKET/TelegramSearchBot/packages/" -Recursive) + if ($items.Count -eq 0) { + return + } + + $keepSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($relativePath in $KeepRelativePaths) { + $normalizedPath = $relativePath.Replace('\', '/') + [void]$keepSet.Add("TelegramSearchBot/packages/$normalizedPath") + } + + foreach ($group in ($items | Group-Object fileName)) { + $sortedItems = $group.Group | Sort-Object uploadTimestamp -Descending + if ($keepSet.Contains($group.Name)) { + $sortedItems = $sortedItems | Select-Object -Skip 1 + } + + foreach ($item in $sortedItems) { + Remove-B2VersionById -FileId $item.fileId + } + } + } + pip install --quiet --cache-dir C:\pip-cache b2 b2 account authorize $env:B2_APPKEY_ID $env:B2_APPKEY --quiet + if ("${{ steps.bridge-check.outputs.publish-bridge }}" -eq "true") { + $bridgeRoot = (Resolve-Path .\artifacts\legacy-bridge).Path b2 sync .\artifacts\legacy-bridge b2://$env:B2_BUCKET/TelegramSearchBot --quiet + Get-ChildItem $bridgeRoot -File -Recurse | ForEach-Object { + $relativePath = [System.IO.Path]::GetRelativePath($bridgeRoot, $_.FullName).Replace('\', '/') + Prune-B2FileVersions -RelativePath "TelegramSearchBot/$relativePath" + } } + b2 sync .\artifacts\update-feed\packages b2://$env:B2_BUCKET/TelegramSearchBot/packages --quiet b2 upload-file $env:B2_BUCKET .\artifacts\update-feed\moder_update_updater.exe TelegramSearchBot/moder_update_updater.exe b2 upload-file $env:B2_BUCKET .\artifacts\update-feed\catalog.json TelegramSearchBot/catalog.json + $packageFiles = Get-ChildItem .\artifacts\update-feed\packages -File -Recurse | ForEach-Object { + [System.IO.Path]::GetRelativePath((Resolve-Path .\artifacts\update-feed\packages).Path, $_.FullName) + } + Prune-B2PackageVersions -KeepRelativePaths $packageFiles + Prune-B2FileVersions -RelativePath 'TelegramSearchBot/moder_update_updater.exe' + Prune-B2FileVersions -RelativePath 'TelegramSearchBot/catalog.json' b2 clear-account + - name: Publish full package to GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.versioning.outputs.release-tag }} + target_commitish: ${{ github.sha }} + name: Release ${{ steps.versioning.outputs.build-version }} + generate_release_notes: true + fail_on_unmatched_files: true + overwrite_files: true + files: | + artifacts/release/* diff --git a/Docs/Build_and_Test_Guide.md b/Docs/Build_and_Test_Guide.md index bed47413..3eb793c6 100644 --- a/Docs/Build_and_Test_Guide.md +++ b/Docs/Build_and_Test_Guide.md @@ -237,6 +237,7 @@ jobs: 仓库内实际的 `push.yml` 现在维护两条发布线: - 保留根目录中的最终 ClickOnce bridge,供旧安装版本过渡到新更新链路。 - 每次主分支发布都会生成 `catalog.json`、`packages/` 和 `moder_update_updater.exe`,供 `%LOCALAPPDATA%\TelegramSearchBot\app` 中的独立安装目录继续使用 Moder.Update 协议升级。 +- 同一条发布流水线会在新文件上传成功后裁剪 Backblaze B2 上重复或过期的更新包版本,并把 `TelegramSearchBot-win-x64-full-.zip` 全量包上传到 GitHub Releases,便于手动分发和回滚。 ### 监控与日志 diff --git a/README.md b/README.md index 4a6e8fc9..f7481822 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,9 @@ ### 快速开始 1. 下载[最新版本](https://clickonce.miaostay.com/TelegramSearchBot/Publish.html)(首次安装仍使用 ClickOnce,后续版本会迁移到 `%LOCALAPPDATA%\TelegramSearchBot\app` 并由内置更新器继续升级) -2. 首次运行会自动生成配置目录 -3. 编辑`AppData/Local/TelegramSearchBot/Config.json`: +2. 如需手动部署完整包,可从 [GitHub Releases](https://github.com/ModerRAS/TelegramSearchBot/releases) 下载 `TelegramSearchBot-win-x64-full-*.zip` +3. 首次运行会自动生成配置目录 +4. 编辑`AppData/Local/TelegramSearchBot/Config.json`: ```json { @@ -94,7 +95,7 @@ - **自动更新**: - `EnableAutoUpdate`: 是否启用内置自更新流程(默认true) - `UpdateBaseUrl`: 更新目录根地址,默认使用 `https://clickonce.miaostay.com/TelegramSearchBot` - - **说明**: 首次安装仍通过 `Publish.html` 分发桥接版;之后程序会从 `catalog.json`、`packages/` 和 `moder_update_updater.exe` 拉取更新并升级独立安装目录 + - **说明**: 首次安装仍通过 `Publish.html` 分发桥接版;之后程序会从 `catalog.json`、`packages/` 和 `moder_update_updater.exe` 拉取更新并升级独立安装目录。每次主分支发布也会同步上传一份 `TelegramSearchBot-win-x64-full-*.zip` 到 GitHub Releases,便于手动全量更新或回滚。 - **AI相关**: - `OllamaModelName`: 本地模型名称(默认"qwen2.5:72b-instruct-q2_K")