# Signiert ein Release und laedt es auf die BSO-Website (wie release.ps1 bei MSCO). # .\release.ps1 -Product mod|launcher|server -Version 0.2.0 -File dist\BSO-Mod.zip [-Changelog "..."] # Laeuft auch automatisch bei "dotnet publish -p:BsoRelease=true" (Launcher und Server, siehe .csproj). # - Ohne -Changelog: der Abschnitt "## " aus CHANGELOG.md neben diesem Skript. # - Ist -File ein Ordner (Server: publish\), wird er ohne .pdb-Dateien gezippt. # Braucht: Release-Schluessel (BSO-Launcher\new-release-key.ps1), BSO_RELEASE_TOKEN (= RELEASE_TOKEN der Website), # optional BSO_MASTER_URL (Standard https://bso.mbm.wtf). Beide auch als Windows-Benutzervariable. # Signiert wird "BSO-RELEASE|produkt|version|sha256" - der Launcher prueft das mit dem eingebauten oeffentlichen Schluessel. param( [Parameter(Mandatory)][ValidateSet("mod", "launcher", "server")][string]$Product, [Parameter(Mandatory)][string]$Version, [Parameter(Mandatory)][string]$File, [string]$Changelog = "" ) $ErrorActionPreference = "Stop" Add-Type -AssemblyName System.Security Add-Type -AssemblyName System.Net.Http # Aus der Umgebung - sonst die Windows-Benutzervariable (z.B. wenn Visual Studio vor dem Setzen gestartet wurde) function Setting([string]$name) { $v = [Environment]::GetEnvironmentVariable($name) if (-not $v) { $v = [Environment]::GetEnvironmentVariable($name, "User") } return $v } $master = ((Setting "BSO_MASTER_URL"), "https://bso.mbm.wtf" | Where-Object { $_ } | Select-Object -First 1).TrimEnd('/') $token = Setting "BSO_RELEASE_TOKEN" if (-not $token) { Write-Host "BSO_RELEASE_TOKEN nicht gesetzt - nicht hochgeladen"; return } $keyFile = Join-Path $env:APPDATA "BSO\release-key.dpapi" if (-not (Test-Path $keyFile)) { throw "Kein Release-Schluessel ($keyFile) - erst new-release-key.ps1 im BSO-Launcher-Repo" } $Version = $Version.Split('+')[0] # Changelog aus CHANGELOG.md: alles unter "## " bis zur naechsten "## "-Zeile if (-not $Changelog) { $md = Join-Path $PSScriptRoot "CHANGELOG.md" if (Test-Path $md) { $lines = [IO.File]::ReadAllLines($md) $start = [Array]::FindIndex($lines, [Predicate[string]] { param($l) $l -match "^##\s+v?$([regex]::Escape($Version))(\s|$)" }) if ($start -ge 0) { $section = @() for ($i = $start + 1; $i -lt $lines.Length -and $lines[$i] -notmatch '^##\s'; $i++) { $section += $lines[$i] } $Changelog = ($section -join "`n").Trim() } } if ($Changelog) { Write-Host "Changelog aus CHANGELOG.md ($Version)" } else { Write-Host "Kein Changelog fuer $Version in CHANGELOG.md" } } # Ordner (dotnet publish) -> zip $path = (Resolve-Path $File).Path if (Test-Path $path -PathType Container) { $zip = Join-Path $env:TEMP "BSO-$Product-$Version.zip" Remove-Item $zip -ErrorAction SilentlyContinue Compress-Archive -Path (Get-ChildItem $path -File | Where-Object Extension -ne ".pdb").FullName -DestinationPath $zip $path = $zip } $data = [IO.File]::ReadAllBytes($path) $sha = ([System.Security.Cryptography.SHA256]::Create().ComputeHash($data) | ForEach-Object { $_.ToString("x2") }) -join "" $private = [System.Security.Cryptography.ProtectedData]::Unprotect([IO.File]::ReadAllBytes($keyFile), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) $rsa = [System.Security.Cryptography.RSA]::Create() $rsa.FromXmlString([Text.Encoding]::UTF8.GetString($private)) $message = [Text.Encoding]::UTF8.GetBytes("BSO-RELEASE|$Product|$Version|$sha") $signature = [Convert]::ToBase64String($rsa.SignData($message, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)) $http = New-Object System.Net.Http.HttpClient $http.Timeout = [TimeSpan]::FromMinutes(10) $http.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", $token) $form = New-Object System.Net.Http.MultipartFormDataContent foreach ($pair in @(@("product", $Product), @("version", $Version), @("changelog", $Changelog), @("signature", $signature))) { $form.Add((New-Object System.Net.Http.StringContent($pair[1])), "`"$($pair[0])`"") } $content = New-Object System.Net.Http.ByteArrayContent(, $data) $form.Add($content, "`"file`"", "`"$(Split-Path $path -Leaf)`"") $res = $http.PostAsync("$master/api/releases/upload", $form).Result $body = $res.Content.ReadAsStringAsync().Result if ($res.StatusCode -eq [Net.HttpStatusCode]::Conflict) { Write-Host "$Product $Version gibt es schon auf $master - Version in der .csproj erhoehen"; return } if (-not $res.IsSuccessStatusCode) { throw "Hochladen fehlgeschlagen ($([int]$res.StatusCode)): $body" } Write-Host "$Product $Version hochgeladen nach $master (sha256 $sha)"