diff --git a/CHANGELOG.md b/CHANGELOG.md index 964d9cc..79c4872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ - Docs: Added Linux (WSL/Ubuntu) quick start, build and test instructions in `README.md`. - Build: `build.sh` now bootstraps vcpkg and uses the vcpkg toolchain; builds library, tests, and example. +### Downloads +- [lib-privatebin-v0.1.1.3-windows-x64.zip](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/lib-privatebin-v0.1.1.3-windows-x64.zip) +- [privatebinapi.dll](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/privatebinapi.dll) +- [privatebinapi.lib](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/privatebinapi.lib) +- [example.exe](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/example.exe) + +### SHA256 +- [lib-privatebin-v0.1.1.3-windows-x64.zip.sha256](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/lib-privatebin-v0.1.1.3-windows-x64.zip.sha256) +- [privatebinapi.dll.sha256](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/privatebinapi.dll.sha256) +- [privatebinapi.lib.sha256](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/privatebinapi.lib.sha256) +- [example.exe.sha256](https://gitea.medisoftware.org/Markus/lib-privatebin/releases/download/v0.1.1.3/example.exe.sha256) + ## v0.1.1.2 (2025-08-28) - Example: Made `example/CMakeLists.txt` platform-neutral; links against in-tree target `privatebinapi`. diff --git a/scripts/collect_binaries.ps1 b/scripts/collect_binaries.ps1 new file mode 100644 index 0000000..e7596c0 --- /dev/null +++ b/scripts/collect_binaries.ps1 @@ -0,0 +1,30 @@ +param( + [Parameter(Mandatory=$true)][string]$OutDir +) + +$ErrorActionPreference = 'Stop' + +New-Item -ItemType Directory -Force -Path $OutDir | Out-Null + +$items = @( + @{ Path = 'build/Release/privatebinapi.dll'; Optional = $false } + @{ Path = 'build/Release/privatebinapi.lib'; Optional = $true } + @{ Path = 'build/Release/privatebinapi.pdb'; Optional = $true } + @{ Path = 'build/example/Release/example.exe'; Optional = $true } +) + +foreach ($it in $items) { + $p = Resolve-Path -LiteralPath $it.Path -ErrorAction SilentlyContinue + if ($null -ne $p) { + Copy-Item -LiteralPath $p.Path -Destination $OutDir -Force + Write-Host ("Collected: " + [IO.Path]::GetFileName($p.Path)) + } elseif (-not $it.Optional) { + throw "Required artifact not found: $($it.Path)" + } else { + Write-Host ("Skip missing optional: " + $it.Path) + } +} + +Get-ChildItem -LiteralPath $OutDir -File | Format-Table Name,Length -AutoSize + + diff --git a/scripts/gen_checksums.ps1 b/scripts/gen_checksums.ps1 new file mode 100644 index 0000000..1622b14 --- /dev/null +++ b/scripts/gen_checksums.ps1 @@ -0,0 +1,35 @@ +param( + [Parameter(Mandatory=$true)][string]$ZipPath, + [Parameter(Mandatory=$true)][string]$BinDir +) + +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $ZipPath)) { + throw "ZIP not found: $ZipPath" +} + +if (-not (Test-Path -LiteralPath $BinDir)) { + throw "BinDir not found: $BinDir" +} + +function Write-ChecksumFile { + param( + [Parameter(Mandatory=$true)][string]$Path + ) + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash.ToLower() + $outfile = "$Path.sha256" + $line = "$hash $([System.IO.Path]::GetFileName($Path))" + Set-Content -Path $outfile -NoNewline -Encoding ASCII -Value $line + Write-Host "Wrote $outfile" +} + +# ZIP checksum +Write-ChecksumFile -Path $ZipPath + +# Binaries checksums +Get-ChildItem -LiteralPath $BinDir -File | ForEach-Object { + Write-ChecksumFile -Path $_.FullName +} + + diff --git a/scripts/gitea_upload.ps1 b/scripts/gitea_upload.ps1 new file mode 100644 index 0000000..af6a2b4 --- /dev/null +++ b/scripts/gitea_upload.ps1 @@ -0,0 +1,43 @@ +param( + [Parameter(Mandatory=$true)][string]$Server, + [Parameter(Mandatory=$true)][string]$Owner, + [Parameter(Mandatory=$true)][string]$Repo, + [Parameter(Mandatory=$true)][string]$Token, + [Parameter(Mandatory=$true)][string]$ZipPath +) + +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $ZipPath)) { + throw "ZIP not found: $ZipPath" +} + +$headers = @{ Authorization = "token $Token" } + +$latestUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/latest" +$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri $latestUrl +if (-not $latest -or -not $latest.id) { + throw "Failed to get latest release from $latestUrl" +} + +$rid = [string]$latest.id +$name = [System.IO.Path]::GetFileName($ZipPath) +$escapedName = [System.Uri]::EscapeDataString($name) +$uploadUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/$rid/assets?name=$escapedName" + +Write-Host "Latest release id: $rid" +Write-Host "Uploading $name to $uploadUrl" + +# Use Invoke-WebRequest multipart form upload +$form = @{ attachment = Get-Item -LiteralPath $ZipPath } +$resp = Invoke-WebRequest -Headers $headers -Method Post -Uri $uploadUrl -Form $form + +if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 300) { + Write-Host "Upload successful (HTTP $($resp.StatusCode))" +} else { + Write-Host "Upload failed (HTTP $($resp.StatusCode))" -ForegroundColor Red + if ($resp.Content) { Write-Host $resp.Content } + exit 1 +} + + diff --git a/scripts/gitea_upload_all.ps1 b/scripts/gitea_upload_all.ps1 new file mode 100644 index 0000000..fe3e156 --- /dev/null +++ b/scripts/gitea_upload_all.ps1 @@ -0,0 +1,45 @@ +param( + [Parameter(Mandatory=$true)][string]$Server, + [Parameter(Mandatory=$true)][string]$Owner, + [Parameter(Mandatory=$true)][string]$Repo, + [Parameter(Mandatory=$true)][string]$Token, + [Parameter(Mandatory=$true)][string]$Directory +) + +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $Directory)) { + throw "Directory not found: $Directory" +} + +$headers = @{ Authorization = "token $Token" } + +$latestUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/latest" +$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri $latestUrl +if (-not $latest -or -not $latest.id) { + throw "Failed to get latest release from $latestUrl" +} +$rid = [string]$latest.id +Write-Host "Latest release id: $rid" + +$files = Get-ChildItem -LiteralPath $Directory -Recurse -File +if (-not $files) { + Write-Host "No files to upload in $Directory" + exit 0 +} + +foreach ($f in $files) { + $name = $f.Name + $escapedName = [System.Uri]::EscapeDataString($name) + $uploadUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/$rid/assets?name=$escapedName" + Write-Host "Uploading $name ..." + try { + $resp = Invoke-WebRequest -Headers $headers -Method Post -Uri $uploadUrl -Form @{ attachment = $f } + Write-Host " OK ($($resp.StatusCode))" + } + catch { + Write-Host " FAIL: $name -> $($_.Exception.Message)" -ForegroundColor Red + } +} + + diff --git a/scripts/manage_release.ps1 b/scripts/manage_release.ps1 new file mode 100644 index 0000000..d9a966d --- /dev/null +++ b/scripts/manage_release.ps1 @@ -0,0 +1,89 @@ +param( + [Parameter(Mandatory=$true)][string]$Server, + [Parameter(Mandatory=$true)][string]$Owner, + [Parameter(Mandatory=$true)][string]$Repo, + [Parameter(Mandatory=$true)][string]$Token +) + +$ErrorActionPreference = 'Stop' + +$headers = @{ Authorization = "token $Token" } + +# Get latest release +$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/latest" +if (-not $latest -or -not $latest.id) { + throw "Failed to get latest release" +} +$rid = [string]$latest.id +Write-Host "Operating on release id: $rid" + +# Load full release including attachments/assets list +$release = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" +$assets = $release.assets +if (-not $assets) { $assets = $release.attachments } + +if ($assets) { + $groups = $assets | Group-Object -Property name + foreach ($g in $groups) { + $sorted = $g.Group | Sort-Object -Property id -Descending + if ($sorted.Count -gt 1) { + $keep = $sorted[0] + foreach ($extra in $sorted[1..($sorted.Count-1)]) { + if ($null -ne $extra -and $extra.id -ne $keep.id) { + $delUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/assets/$($extra.id)" + try { + Invoke-RestMethod -Headers $headers -Method DELETE -Uri $delUrl | Out-Null + Write-Host ("Deleted duplicate asset: " + $extra.name + " (id=" + $extra.id + ")") + } catch { + Write-Host ("Failed to delete asset id=" + $extra.id + ": " + $_.Exception.Message) -ForegroundColor Red + } + } + } + } + } +} else { + Write-Host 'No assets found on release.' +} + +# Compose release notes with known checksums if they exist locally +$zipShaPath = (Resolve-Path 'dist\lib-privatebin-v0.1.1.3-windows-x64.zip.sha256' -ErrorAction SilentlyContinue) +$dllShaPath = (Resolve-Path 'dist\windows_bin\privatebinapi.dll.sha256' -ErrorAction SilentlyContinue) +$libShaPath = (Resolve-Path 'dist\windows_bin\privatebinapi.lib.sha256' -ErrorAction SilentlyContinue) +$exeShaPath = (Resolve-Path 'dist\windows_bin\example.exe.sha256' -ErrorAction SilentlyContinue) + +$lines = @('# Downloadable artifacts', '') +$lines += ("- [lib-privatebin-v0.1.1.3-windows-x64.zip]($Server/$Owner/$Repo/releases/download/$tag/lib-privatebin-v0.1.1.3-windows-x64.zip)") +$lines += ("- [privatebinapi.dll]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.dll)") +$lines += ("- [privatebinapi.lib]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.lib)") +$lines += ("- [example.exe]($Server/$Owner/$Repo/releases/download/$tag/example.exe)") +$lines += '' +$lines += '# SHA256 checksums' +if ($zipShaPath) { $lines += ("- [lib-privatebin-v0.1.1.3-windows-x64.zip.sha256]($Server/$Owner/$Repo/releases/download/$tag/lib-privatebin-v0.1.1.3-windows-x64.zip.sha256)") } +if ($dllShaPath) { $lines += ("- [privatebinapi.dll.sha256]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.dll.sha256)") } +if ($libShaPath) { $lines += ("- [privatebinapi.lib.sha256]($Server/$Owner/$Repo/releases/download/$tag/privatebinapi.lib.sha256)") } +if ($exeShaPath) { $lines += ("- [example.exe.sha256]($Server/$Owner/$Repo/releases/download/$tag/example.exe.sha256)") } + +$lines += '' +$lines += '# Changes' +try { + $changelog = Get-Content -Raw -LiteralPath 'CHANGELOG.md' + $sections = $changelog -split "\r?\n## " + $section = $sections | Where-Object { $_ -like 'v0.1.1.3*' } | Select-Object -First 1 + if ($section) { + $secLines = $section -split "\r?\n" + if ($secLines[0] -like 'v0.1.1.3*') { $secLines[0] = 'v0.1.1.3' } + $lines += $secLines + } else { + $lines += 'See CHANGELOG.md' + } +} catch { + $lines += 'See CHANGELOG.md' +} + +$bodyText = ($lines -join "`n") +$payload = @{ body = $bodyText } | ConvertTo-Json -Depth 3 + +Invoke-RestMethod -Headers (@{ Authorization = ("token " + $Token); 'Content-Type' = 'application/json' }) -Method PATCH -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" -Body $payload | Out-Null +Write-Host 'Release notes updated.' + + diff --git a/scripts/prune_release_assets.ps1 b/scripts/prune_release_assets.ps1 new file mode 100644 index 0000000..ce0c706 --- /dev/null +++ b/scripts/prune_release_assets.ps1 @@ -0,0 +1,76 @@ +param( + [Parameter(Mandatory=$true)][string]$Server, + [Parameter(Mandatory=$true)][string]$Owner, + [Parameter(Mandatory=$true)][string]$Repo, + [Parameter(Mandatory=$true)][string]$Token +) + +$ErrorActionPreference = 'Stop' +$headers = @{ Authorization = "token $Token" } + +$latest = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/latest" +if (-not $latest -or -not $latest.id) { throw 'Failed to get latest release' } +$rid = [string]$latest.id +$tag = $latest.tag_name +Write-Host "Pruning assets on release id=$rid (tag=$tag)" + +$release = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" +$assets = $release.assets +if (-not $assets) { $assets = $release.attachments } + +# Allowed asset names +$allowed = @( + 'lib-privatebin-v0.1.1.3-windows-x64.zip', + 'lib-privatebin-v0.1.1.3-windows-x64.zip.sha256', + 'privatebinapi.dll','privatebinapi.dll.sha256', + 'privatebinapi.lib','privatebinapi.lib.sha256', + 'example.exe','example.exe.sha256' +) + +if ($assets) { + foreach ($a in $assets) { + if ($allowed -notcontains $a.name) { + $delUrl = "$Server/api/v1/repos/$Owner/$Repo/releases/assets/$($a.id)" + try { + Invoke-RestMethod -Headers $headers -Method DELETE -Uri $delUrl | Out-Null + Write-Host ("Deleted: " + $a.name) + } catch { + Write-Host ("Skip delete (" + $a.name + "): " + $_.Exception.Message) -ForegroundColor Yellow + } + } + } +} else { + Write-Host 'No assets found.' +} + +# Re-fetch assets after prune +$release = Invoke-RestMethod -Headers $headers -Method GET -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" +$assets = $release.assets; if (-not $assets) { $assets = $release.attachments } + +# Build markdown with links +$lines = @('# Downloadable artifacts', '') +foreach ($name in $allowed) { + $a = $assets | Where-Object { $_.name -eq $name } | Select-Object -First 1 + if ($a) { + # Prefer deterministic download URL pattern + $url = "$Server/$Owner/$Repo/releases/download/$tag/$name" + $lines += ("- [$name]($url)") + } +} + +$lines += '' +$lines += '# SHA256 checksums' +foreach ($name in $allowed | Where-Object { $_.EndsWith('.sha256') }) { + $a = $assets | Where-Object { $_.name -eq $name } | Select-Object -First 1 + if ($a) { + $url = "$Server/$Owner/$Repo/releases/download/$tag/$name" + $lines += ("- [$name]($url)") + } +} + +$bodyText = ($lines -join "`n") +$payload = @{ body = $bodyText } | ConvertTo-Json -Depth 3 +Invoke-RestMethod -Headers (@{ Authorization = ("token " + $Token); 'Content-Type' = 'application/json' }) -Method PATCH -Uri "$Server/api/v1/repos/$Owner/$Repo/releases/$rid" -Body $payload | Out-Null +Write-Host 'Release notes updated with links.' + +