202 lines
8.5 KiB
PowerShell
202 lines
8.5 KiB
PowerShell
param(
|
|
[string]$Version = "",
|
|
[switch]$ValidateOnly
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
Add-Type -AssemblyName System.Net.Http
|
|
|
|
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$BaseUrl = "https://gitea.davito.synology.me"
|
|
$Owner = "Davito"
|
|
$Repository = "SyncVito"
|
|
$LocalAppData = [Environment]::GetFolderPath("LocalApplicationData")
|
|
$TokenPaths = @(
|
|
(Join-Path $LocalAppData "SyncVito\developer\gitea-token.dpapi"),
|
|
(Join-Path $LocalAppData "MusicBrain\developer\gitea-token.dpapi")
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Version)) {
|
|
$Version = (Get-Content -LiteralPath (Join-Path $Root "version.txt") -Raw -Encoding UTF8).Trim()
|
|
}
|
|
if ($Version -notmatch '^\d+\.\d+\.\d+$') { throw "Ungültige Release-Version: $Version" }
|
|
|
|
$Tag = "v$Version"
|
|
$ReleaseDir = Join-Path $Root "release"
|
|
$InstallerName = "SyncVitoSetup-$Version.exe"
|
|
$ChecksumName = "$InstallerName.sha256"
|
|
$ChangelogName = "CHANGELOG-$Version.md"
|
|
$AssetPaths = @(
|
|
(Join-Path $ReleaseDir $InstallerName),
|
|
(Join-Path $ReleaseDir $ChecksumName),
|
|
(Join-Path $ReleaseDir $ChangelogName)
|
|
)
|
|
foreach ($assetPath in $AssetPaths) {
|
|
if (-not (Test-Path -LiteralPath $assetPath -PathType Leaf)) { throw "Release-Datei fehlt: $assetPath" }
|
|
}
|
|
|
|
$actualHash = (Get-FileHash -LiteralPath $AssetPaths[0] -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
$checksumText = (Get-Content -LiteralPath $AssetPaths[1] -Raw -Encoding UTF8).Trim()
|
|
$checksumPattern = '^([0-9a-fA-F]{64})\s+\*?' + [regex]::Escape($InstallerName) + '$'
|
|
if ($checksumText -notmatch $checksumPattern -or $Matches[1].ToLowerInvariant() -ne $actualHash) {
|
|
throw "SHA-256-Prüfung fehlgeschlagen. Der Release wird nicht hochgeladen."
|
|
}
|
|
|
|
$plainToken = $null
|
|
$tokenPointer = [IntPtr]::Zero
|
|
if (-not [string]::IsNullOrWhiteSpace($env:GITEA_TOKEN)) {
|
|
$plainToken = $env:GITEA_TOKEN.Trim()
|
|
}
|
|
else {
|
|
$tokenPath = $TokenPaths | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
|
if (-not $tokenPath) {
|
|
throw "Gitea-Token fehlt. Erwarte GITEA_TOKEN oder eine lokale DPAPI-Token-Datei."
|
|
}
|
|
try {
|
|
$secureToken = (Get-Content -LiteralPath $tokenPath -Raw -Encoding UTF8).Trim() | ConvertTo-SecureString
|
|
$tokenPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureToken)
|
|
$plainToken = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($tokenPointer)
|
|
}
|
|
catch {
|
|
throw "Der lokal gespeicherte Gitea-Token konnte nicht entschlüsselt werden: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
$client = $null
|
|
|
|
function Read-ResponseJson {
|
|
param([System.Net.Http.HttpResponseMessage]$Response, [string]$Action)
|
|
$text = $Response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
|
if (-not $Response.IsSuccessStatusCode) {
|
|
$detail = $text
|
|
try {
|
|
$errorPayload = $text | ConvertFrom-Json
|
|
if ($errorPayload.message) { $detail = $errorPayload.message }
|
|
}
|
|
catch { }
|
|
throw "$Action fehlgeschlagen (HTTP $([int]$Response.StatusCode)): $detail"
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($text)) { return $null }
|
|
return $text | ConvertFrom-Json
|
|
}
|
|
|
|
function Send-GiteaJson {
|
|
param([string]$Method, [string]$Path, [object]$Body, [string]$Action)
|
|
$request = [System.Net.Http.HttpRequestMessage]::new(
|
|
[System.Net.Http.HttpMethod]::new($Method),
|
|
"$BaseUrl/api/v1$Path"
|
|
)
|
|
try {
|
|
if ($null -ne $Body) {
|
|
$json = $Body | ConvertTo-Json -Depth 8 -Compress
|
|
$request.Content = [System.Net.Http.StringContent]::new(
|
|
$json,
|
|
[System.Text.Encoding]::UTF8,
|
|
"application/json"
|
|
)
|
|
}
|
|
$response = $client.SendAsync($request).GetAwaiter().GetResult()
|
|
try { return Read-ResponseJson -Response $response -Action $Action }
|
|
finally { $response.Dispose() }
|
|
}
|
|
finally { $request.Dispose() }
|
|
}
|
|
|
|
try {
|
|
if ([string]::IsNullOrWhiteSpace($plainToken)) { throw "Der gespeicherte Gitea-Token ist leer." }
|
|
$client = [System.Net.Http.HttpClient]::new()
|
|
$client.Timeout = [TimeSpan]::FromMinutes(20)
|
|
$client.DefaultRequestHeaders.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("token", $plainToken)
|
|
$client.DefaultRequestHeaders.UserAgent.ParseAdd("SyncVito-Release-Publisher/$Version")
|
|
$client.DefaultRequestHeaders.Accept.ParseAdd("application/json")
|
|
|
|
$repositoryInfo = Send-GiteaJson -Method "GET" -Path "/repos/$Owner/$Repository" -Body $null -Action "Repository-Zugriff prüfen"
|
|
if (-not $repositoryInfo.permissions.push) { throw "Der Token besitzt keine Schreibrechte für $Owner/$Repository." }
|
|
if ($ValidateOnly) {
|
|
Write-Host "Gitea-Publisher ist bereit: $($repositoryInfo.full_name), Version $Version"
|
|
return
|
|
}
|
|
|
|
$encodedTag = [Uri]::EscapeDataString($Tag)
|
|
$release = $null
|
|
$lookup = [System.Net.Http.HttpRequestMessage]::new(
|
|
[System.Net.Http.HttpMethod]::Get,
|
|
"$BaseUrl/api/v1/repos/$Owner/$Repository/releases/tags/$encodedTag"
|
|
)
|
|
try {
|
|
$lookupResponse = $client.SendAsync($lookup).GetAwaiter().GetResult()
|
|
try {
|
|
if ($lookupResponse.IsSuccessStatusCode) {
|
|
$release = Read-ResponseJson -Response $lookupResponse -Action "Release lesen"
|
|
}
|
|
elseif ([int]$lookupResponse.StatusCode -ne 404) {
|
|
Read-ResponseJson -Response $lookupResponse -Action "Release prüfen" | Out-Null
|
|
}
|
|
}
|
|
finally { $lookupResponse.Dispose() }
|
|
}
|
|
finally { $lookup.Dispose() }
|
|
|
|
$releaseBody = (Get-Content -LiteralPath $AssetPaths[2] -Raw -Encoding UTF8).Trim()
|
|
$metadata = @{
|
|
tag_name = $Tag
|
|
target_commitish = "main"
|
|
name = "SyncVito $Version"
|
|
body = $releaseBody
|
|
draft = $false
|
|
prerelease = $false
|
|
}
|
|
if ($null -eq $release) {
|
|
Write-Host "Lege Gitea-Release $Tag an …"
|
|
$release = Send-GiteaJson -Method "POST" -Path "/repos/$Owner/$Repository/releases" -Body $metadata -Action "Release anlegen"
|
|
}
|
|
else {
|
|
Write-Host "Aktualisiere Gitea-Release $Tag …"
|
|
$release = Send-GiteaJson -Method "PATCH" -Path "/repos/$Owner/$Repository/releases/$($release.id)" -Body $metadata -Action "Release aktualisieren"
|
|
}
|
|
if (-not $release.id) { throw "Gitea hat keine Release-ID zurückgegeben." }
|
|
|
|
$localNames = @($AssetPaths | ForEach-Object { [System.IO.Path]::GetFileName($_) })
|
|
foreach ($existing in @($release.assets | Where-Object { $_.name -in $localNames })) {
|
|
Send-GiteaJson -Method "DELETE" -Path "/repos/$Owner/$Repository/releases/$($release.id)/assets/$($existing.id)" -Body $null -Action "Altes Asset entfernen" | Out-Null
|
|
}
|
|
|
|
foreach ($assetPath in $AssetPaths) {
|
|
$assetName = [System.IO.Path]::GetFileName($assetPath)
|
|
Write-Host "Lade hoch: $assetName"
|
|
$uploadUrl = "$BaseUrl/api/v1/repos/$Owner/$Repository/releases/$($release.id)/assets?name=$([Uri]::EscapeDataString($assetName))"
|
|
$request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Post, $uploadUrl)
|
|
$multipart = [System.Net.Http.MultipartFormDataContent]::new()
|
|
$stream = [System.IO.File]::OpenRead($assetPath)
|
|
$content = [System.Net.Http.StreamContent]::new($stream)
|
|
$content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new("application/octet-stream")
|
|
$multipart.Add($content, "attachment", $assetName)
|
|
$request.Content = $multipart
|
|
try {
|
|
$response = $client.SendAsync($request).GetAwaiter().GetResult()
|
|
try { Read-ResponseJson -Response $response -Action "Upload von $assetName" | Out-Null }
|
|
finally { $response.Dispose() }
|
|
}
|
|
finally {
|
|
$request.Dispose()
|
|
$multipart.Dispose()
|
|
$content.Dispose()
|
|
$stream.Dispose()
|
|
}
|
|
}
|
|
|
|
$published = Send-GiteaJson -Method "GET" -Path "/repos/$Owner/$Repository/releases/tags/$encodedTag" -Body $null -Action "Release verifizieren"
|
|
$publishedNames = @($published.assets | ForEach-Object { $_.name })
|
|
foreach ($required in $localNames) {
|
|
if ($required -notin $publishedNames) { throw "Veröffentlichtes Asset fehlt: $required" }
|
|
}
|
|
Write-Host "Gitea-Release erfolgreich veröffentlicht: $($published.html_url)"
|
|
Write-Host "SHA256: $actualHash"
|
|
}
|
|
finally {
|
|
if ($client) { $client.Dispose() }
|
|
if ($tokenPointer -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($tokenPointer) }
|
|
$plainToken = $null
|
|
Remove-Variable plainToken -ErrorAction SilentlyContinue
|
|
}
|