Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ name: Release

on:
push:
branches:
- master
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
tag_name:
description: 'Release tag (example: v2.1.0)'
description: 'Release tag (example: v2.2.0)'
required: true
type: string

Expand All @@ -28,20 +30,47 @@ jobs:
with:
dotnet-version: '9.0.x'

- name: Resolve tag
id: tag
- name: Resolve release tag
id: resolve
shell: pwsh
run: |
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
"tag=${{ inputs.tag_name }}" >> $env:GITHUB_OUTPUT
} else {
"tag=${{ github.ref_name }}" >> $env:GITHUB_OUTPUT
$proceed = 'true'
if ($env:GITHUB_EVENT_NAME -eq 'push' -and $env:GITHUB_REF -like 'refs/tags/*') {
# Manual tag push: use the pushed tag.
$tag = $env:GITHUB_REF_NAME
}
elseif ($env:GITHUB_EVENT_NAME -eq 'workflow_dispatch') {
# Manual run: use the provided tag.
$tag = '${{ inputs.tag_name }}'
}
else {
# Push to master: derive the tag from the project version and
# only release if that tag does not already exist.
$csproj = Get-Content "Proxmox Desktop/ProxmoxDesktop.csproj" -Raw
if ($csproj -notmatch '<InformationalVersion>([^<]+)</InformationalVersion>') {
Write-Error "InformationalVersion not found in csproj"; exit 1
}
$version = $Matches[1].Trim()
$tag = "v$version"
git fetch --tags --quiet
if (git tag -l $tag) {
Write-Host "Tag $tag already exists - nothing to release."
$proceed = 'false'
}
else {
Write-Host "New version detected: $tag"
}
}
"tag=$tag" >> $env:GITHUB_OUTPUT
"proceed=$proceed" >> $env:GITHUB_OUTPUT
Write-Host "Resolved tag=$tag proceed=$proceed"

- name: Restore
if: steps.resolve.outputs.proceed == 'true'
run: dotnet restore "Proxmox Desktop/ProxmoxDesktop.csproj"

- name: Publish (self-contained win-x64)
if: steps.resolve.outputs.proceed == 'true'
shell: pwsh
run: |
dotnet publish "Proxmox Desktop/ProxmoxDesktop.csproj" `
Expand All @@ -53,20 +82,22 @@ jobs:
-o output/ProxmoxDesktop

- name: Create ZIP
if: steps.resolve.outputs.proceed == 'true'
shell: pwsh
run: |
Compress-Archive `
-Path output/ProxmoxDesktop/* `
-DestinationPath output/ProxmoxDesktop-${{ steps.tag.outputs.tag }}-win-x64.zip
-DestinationPath output/ProxmoxDesktop-${{ steps.resolve.outputs.tag }}-win-x64.zip

- name: Create GitHub Release
if: steps.resolve.outputs.proceed == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: "Proxmox Desktop ${{ steps.tag.outputs.tag }}"
tag_name: ${{ steps.resolve.outputs.tag }}
name: "Proxmox Desktop ${{ steps.resolve.outputs.tag }}"
body: |
## 📦 Installation
1. Download `ProxmoxDesktop-${{ steps.tag.outputs.tag }}-win-x64.zip`
1. Download `ProxmoxDesktop-${{ steps.resolve.outputs.tag }}-win-x64.zip`
2. Extract anywhere
3. Run `ProxmoxDesktop.exe`

Expand All @@ -76,9 +107,9 @@ jobs:
- Windows 10 (build 17763+) or Windows 11
- [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) *(pre-installed on Windows 11)*
- [Virt-Viewer + UsbDk](https://www.spice-space.org/download.html) *(SPICE only)*
files: output/ProxmoxDesktop-${{ steps.tag.outputs.tag }}-win-x64.zip
files: output/ProxmoxDesktop-${{ steps.resolve.outputs.tag }}-win-x64.zip
generate_release_notes: true
draft: false
prerelease: ${{ contains(steps.tag.outputs.tag, '-beta') || contains(steps.tag.outputs.tag, '-rc') }}
prerelease: ${{ contains(steps.resolve.outputs.tag, '-beta') || contains(steps.resolve.outputs.tag, '-rc') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 changes: 12 additions & 4 deletions Proxmox Desktop/Api/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ public async Task<LoginResult> LoginAsync(
if (!string.IsNullOrWhiteSpace(otp)) form["otp"] = otp;

TicketResponse? result;
try { result = await PostTicketAsync(form, ct); }
HttpStatusCode status;
try { (result, status) = await PostTicketWithStatusAsync(form, ct); }
catch (Exception ex) { return LoginResult.Failure($"Cannot reach server: {ex.Message}"); }

if (result is null) return LoginResult.Failure("Invalid server response.");
if (result is null)
return LoginResult.Failure(status == HttpStatusCode.Unauthorized
? "Login rejected — check username, password and realm, and enter the TOTP code if 2FA is enabled."
: $"Login rejected by the server (HTTP {(int)status}).");

if (result.Ticket?.Contains("PVE:!tfa!") == true)
return string.IsNullOrWhiteSpace(otp)
Expand Down Expand Up @@ -261,12 +265,16 @@ private async Task<string> GetRawAsync(string path, CancellationToken ct)

private async Task<TicketResponse?> PostTicketAsync(
Dictionary<string, string> form, CancellationToken ct)
=> (await PostTicketWithStatusAsync(form, ct)).Data;

private async Task<(TicketResponse? Data, HttpStatusCode Status)> PostTicketWithStatusAsync(
Dictionary<string, string> form, CancellationToken ct)
{
var resp = await _http.PostAsync(
"access/ticket", new FormUrlEncodedContent(form), ct);
if (!resp.IsSuccessStatusCode) return null;
var body = await resp.Content.ReadAsStringAsync(ct);
return JsonSerializer.Deserialize<PveResponse<TicketResponse>>(body, _json)?.Data;
if (!resp.IsSuccessStatusCode) return (null, resp.StatusCode);
return (JsonSerializer.Deserialize<PveResponse<TicketResponse>>(body, _json)?.Data, resp.StatusCode);
}

private async Task<List<MachineData>> FetchMachinesAsync(
Expand Down
4 changes: 2 additions & 2 deletions Proxmox Desktop/ProxmoxDesktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<UseWPF>true</UseWPF>
<RootNamespace>ProxmoxDesktop</RootNamespace>
<AssemblyName>ProxmoxDesktop</AssemblyName>
<AssemblyVersion>2.2.0.0</AssemblyVersion>
<InformationalVersion>2.2.0</InformationalVersion>
<AssemblyVersion>2.2.1.0</AssemblyVersion>
<InformationalVersion>2.2.1</InformationalVersion>
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win-x64</RuntimeIdentifiers>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,21 @@ Every push to `master` and every pull request automatically triggers a build. If

### Release

Releases are created in two ways:
Releases are created three ways:

**Option 1 — Git tag** (from your machine):
**Option 1 — Automatic on version bump** *(recommended)*:
Bump `<InformationalVersion>` in `Proxmox Desktop/ProxmoxDesktop.csproj`, commit and merge to `master`. The Release workflow reads the version, and if no `v<version>` tag exists yet, it builds, zips and publishes the GitHub Release automatically. Pushing `master` without changing the version is a no-op, so it never spams releases.

**Option 2 — Git tag** (from your machine):
```bash
git tag v2.1.0
git push origin v2.1.0
git tag v2.2.0
git push origin v2.2.0
```

**Option 2 — Manual trigger** (from GitHub UI):
**Option 3 — Manual trigger** (from GitHub UI):
1. Go to [Actions → Release](../../actions/workflows/release.yml)
2. Click **Run workflow**
3. Enter the tag name (e.g. `v2.1.0`) and confirm
3. Enter the tag name (e.g. `v2.2.0`) and confirm

Tags containing `-beta` or `-rc` are automatically marked as pre-releases.

Expand Down
Loading