179 lines
8.0 KiB
PowerShell
179 lines
8.0 KiB
PowerShell
param(
|
|
[string]$Version = "",
|
|
[string]$Notes = "",
|
|
[switch]$ValidateOnly
|
|
)
|
|
|
|
# Veroeffentlicht den Installer aus dist\ als Gitea-Release (Tag v<Version>) samt SHA-256-Datei.
|
|
# Token: Umgebungsvariable GITEA_TOKEN oder DPAPI-Datei %LOCALAPPDATA%\EuerChat\developer\gitea-token.dpapi
|
|
# (ersatzweise die MusicBrain-Token-Datei).
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
Add-Type -AssemblyName System.Net.Http
|
|
|
|
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$BaseUrl = "https://gitea.davito.synology.me"
|
|
$Owner = "Davito"
|
|
$Repository = "EuerChat"
|
|
$LocalAppData = [Environment]::GetFolderPath("LocalApplicationData")
|
|
$TokenPaths = @(
|
|
(Join-Path $LocalAppData "EuerChat\developer\gitea-token.dpapi"),
|
|
(Join-Path $LocalAppData "MusicBrain\developer\gitea-token.dpapi")
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Version)) {
|
|
$Version = (Get-Content -LiteralPath (Join-Path $Root "src-tauri\tauri.conf.json") -Raw -Encoding UTF8 | ConvertFrom-Json).version
|
|
}
|
|
if ($Version -notmatch '^\d+\.\d+\.\d+$') {
|
|
throw "Ungueltige Release-Version: $Version"
|
|
}
|
|
|
|
$Tag = "v$Version"
|
|
$InstallerName = "EuerChat_${Version}_x64-setup.exe"
|
|
$ChecksumName = "$InstallerName.sha256"
|
|
$InstallerPath = Join-Path $Root "dist\$InstallerName"
|
|
$ChecksumPath = Join-Path $Root "dist\$ChecksumName"
|
|
|
|
if (-not (Test-Path -LiteralPath $InstallerPath -PathType Leaf)) {
|
|
throw "Installer fehlt: $InstallerPath (zuerst build.bat ausfuehren)"
|
|
}
|
|
|
|
$sha256 = [System.Security.Cryptography.SHA256]::Create()
|
|
$stream = [System.IO.File]::OpenRead($InstallerPath)
|
|
try {
|
|
$hash = -join ($sha256.ComputeHash($stream) | ForEach-Object { $_.ToString("x2") })
|
|
}
|
|
finally {
|
|
$stream.Dispose()
|
|
$sha256.Dispose()
|
|
}
|
|
[System.IO.File]::WriteAllText($ChecksumPath, "$hash $InstallerName`n", [System.Text.UTF8Encoding]::new($false))
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Notes)) {
|
|
$notesFile = Join-Path $Root "RELEASE_NOTES.md"
|
|
if (Test-Path -LiteralPath $notesFile) {
|
|
$Notes = (Get-Content -LiteralPath $notesFile -Raw -Encoding UTF8).Trim()
|
|
}
|
|
}
|
|
|
|
$plainToken = $env:GITEA_TOKEN
|
|
if ([string]::IsNullOrWhiteSpace($plainToken)) {
|
|
$tokenPath = $TokenPaths | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1
|
|
if (-not $tokenPath) {
|
|
throw "Gitea-Token fehlt. Erwartet: GITEA_TOKEN oder $($TokenPaths[0])"
|
|
}
|
|
$secure = (Get-Content -LiteralPath $tokenPath -Raw -Encoding UTF8).Trim() | ConvertTo-SecureString
|
|
$pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
|
|
try { $plainToken = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) }
|
|
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) }
|
|
}
|
|
|
|
$client = [System.Net.Http.HttpClient]::new()
|
|
$client.Timeout = [TimeSpan]::FromMinutes(20)
|
|
$client.DefaultRequestHeaders.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("token", $plainToken.Trim())
|
|
$client.DefaultRequestHeaders.UserAgent.ParseAdd("EuerChat-Release-Publisher/$Version")
|
|
$client.DefaultRequestHeaders.Accept.ParseAdd("application/json")
|
|
|
|
function Send-Gitea {
|
|
param([string]$Method, [string]$Path, [object]$Body, [string]$Action, [switch]$AllowNotFound)
|
|
|
|
$request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::new($Method), "$BaseUrl/api/v1$Path")
|
|
try {
|
|
if ($null -ne $Body) {
|
|
$request.Content = [System.Net.Http.StringContent]::new(
|
|
($Body | ConvertTo-Json -Depth 8 -Compress), [System.Text.Encoding]::UTF8, "application/json")
|
|
}
|
|
$response = $client.SendAsync($request).GetAwaiter().GetResult()
|
|
try {
|
|
$text = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
|
if ($AllowNotFound -and [int]$response.StatusCode -eq 404) { return $null }
|
|
if (-not $response.IsSuccessStatusCode) {
|
|
throw "$Action fehlgeschlagen (HTTP $([int]$response.StatusCode)): $text"
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($text)) { return $null }
|
|
return $text | ConvertFrom-Json
|
|
}
|
|
finally { $response.Dispose() }
|
|
}
|
|
finally { $request.Dispose() }
|
|
}
|
|
|
|
try {
|
|
$repo = Send-Gitea -Method GET -Path "/repos/$Owner/$Repository" -Action "Repository-Zugriff pruefen"
|
|
if (-not $repo.permissions.push) {
|
|
throw "Der Token besitzt keine Schreibrechte fuer $Owner/$Repository."
|
|
}
|
|
Write-Host "Repository: $($repo.full_name) Version: $Version SHA256: $hash"
|
|
if ($ValidateOnly) { return }
|
|
|
|
$encodedTag = [Uri]::EscapeDataString($Tag)
|
|
$release = Send-Gitea -Method GET -Path "/repos/$Owner/$Repository/releases/tags/$encodedTag" -Action "Release lesen" -AllowNotFound
|
|
|
|
if ($null -eq $release) {
|
|
# Gitea datiert Releases nach dem Commit von main; ohne frischen Commit bleibt /releases/latest alt.
|
|
$marker = Send-Gitea -Method GET -Path "/repos/$Owner/$Repository/contents/RELEASES.md?ref=main" -Action "Release-Marker lesen" -AllowNotFound
|
|
$markerText = "# EuerChat Releases`n`nLetzter veroeffentlichter Release: v$Version ($([DateTime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss')) UTC)`n"
|
|
$markerBody = @{
|
|
message = "Release v$Version"
|
|
content = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($markerText))
|
|
branch = "main"
|
|
}
|
|
if ($marker) { $markerBody.sha = $marker.sha }
|
|
$markerMethod = if ($marker) { "PUT" } else { "POST" }
|
|
Send-Gitea -Method $markerMethod -Path "/repos/$Owner/$Repository/contents/RELEASES.md" -Body $markerBody -Action "Release-Marker-Commit anlegen" | Out-Null
|
|
|
|
Write-Host "Lege Gitea-Release $Tag an..."
|
|
$release = Send-Gitea -Method POST -Path "/repos/$Owner/$Repository/releases" -Action "Release anlegen" -Body @{
|
|
tag_name = $Tag
|
|
target_commitish = "main"
|
|
name = "EuerChat $Version"
|
|
body = $Notes
|
|
draft = $false
|
|
prerelease = $false
|
|
}
|
|
}
|
|
else {
|
|
Write-Host "Setze vorhandenen Release $Tag fort."
|
|
}
|
|
|
|
$existing = @($release.assets | ForEach-Object { $_.name })
|
|
foreach ($assetPath in @($InstallerPath, $ChecksumPath)) {
|
|
$assetName = [System.IO.Path]::GetFileName($assetPath)
|
|
if ($assetName -in $existing) {
|
|
Write-Host "Bereits vorhanden: $assetName"
|
|
continue
|
|
}
|
|
Write-Host "Lade hoch: $assetName"
|
|
$uploadUrl = "$BaseUrl/api/v1/repos/$Owner/$Repository/releases/$($release.id)/assets?name=$([Uri]::EscapeDataString($assetName))"
|
|
$multipart = [System.Net.Http.MultipartFormDataContent]::new()
|
|
$fileStream = [System.IO.File]::OpenRead($assetPath)
|
|
$fileContent = [System.Net.Http.StreamContent]::new($fileStream)
|
|
$fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new("application/octet-stream")
|
|
$multipart.Add($fileContent, "attachment", $assetName)
|
|
$request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Post, $uploadUrl)
|
|
$request.Content = $multipart
|
|
try {
|
|
$response = $client.SendAsync($request).GetAwaiter().GetResult()
|
|
try {
|
|
if (-not $response.IsSuccessStatusCode) {
|
|
throw "Upload von $assetName fehlgeschlagen (HTTP $([int]$response.StatusCode)): $($response.Content.ReadAsStringAsync().GetAwaiter().GetResult())"
|
|
}
|
|
}
|
|
finally { $response.Dispose() }
|
|
}
|
|
finally {
|
|
$request.Dispose(); $multipart.Dispose(); $fileContent.Dispose(); $fileStream.Dispose()
|
|
}
|
|
}
|
|
|
|
$published = Send-Gitea -Method GET -Path "/repos/$Owner/$Repository/releases/tags/$encodedTag" -Action "Veroeffentlichten Release pruefen"
|
|
$names = @($published.assets | ForEach-Object { $_.name })
|
|
foreach ($required in @($InstallerName, $ChecksumName)) {
|
|
if ($required -notin $names) { throw "Release wurde angelegt, aber das Asset fehlt: $required" }
|
|
}
|
|
Write-Host "Fertig: $BaseUrl/$Owner/$Repository/releases/tag/$Tag"
|
|
}
|
|
finally {
|
|
$client.Dispose()
|
|
}
|