diff --git a/BSO.Server.csproj b/BSO.Server.csproj
index a8298b1..57b2820 100644
--- a/BSO.Server.csproj
+++ b/BSO.Server.csproj
@@ -19,4 +19,10 @@
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b2b2c02
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Changelog – BSO Server
+
+Neue Version: hier einen Abschnitt `## ` anlegen, `` in `BSO.Server.csproj` und `GameServer.Version`
+erhöhen, dann `dotnet publish -c Release -o publish -p:BsoRelease=true`.
+
+## 0.3.0
+- Serverliste: meldet sich bei der Website an (`--private`, `--public-address`, `--description`, `--master`)
+- Spieler brauchen ein BSO-Konto (Spiel-Ticket aus dem Launcher), `--no-auth` für LAN und Tests
+- IPv4 und IPv6, Protokoll 2
+
+## 0.2.0
+- Erster dedizierter Server: Port 7777, Testbot, Konsole (status, bot, kick, stop)
diff --git a/release.ps1 b/release.ps1
index 4a3273d..cd7ce65 100644
--- a/release.ps1
+++ b/release.ps1
@@ -1,7 +1,10 @@
-# Signiert ein Release und laedt es auf den BSO Master (wie release.ps1 bei MSCO).
+# 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 "..."]
-# Braucht: Release-Schluessel (BSO-Launcher\new-release-key.ps1), BSO_RELEASE_TOKEN (= RELEASE_TOKEN des Masters),
-# optional BSO_MASTER_URL (Standard https://bso.mbm.wtf).
+# 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,
@@ -13,13 +16,44 @@ $ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Security
Add-Type -AssemblyName System.Net.Http
-$master = ($env:BSO_MASTER_URL, "https://bso.mbm.wtf" | Where-Object { $_ } | Select-Object -First 1).TrimEnd('/')
-$token = $env:BSO_RELEASE_TOKEN
+# 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]
-$data = [IO.File]::ReadAllBytes((Resolve-Path $File))
+# 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)
@@ -37,9 +71,9 @@ foreach ($pair in @(@("product", $Product), @("version", $Version), @("changelog
$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 $File -Leaf)`"")
+$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"; return }
+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)"