From 232797110ffa46ade88ef115f947c029672d6037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Fri, 10 Jul 2026 21:43:41 +0900 Subject: [PATCH 1/3] ci: friendly release asset names and drift-free install docs Rename prebuilt binary assets to friendly os-arch names (e.g. ironrdp-agent--linux-x64.tar.gz), dropping the Rust target triple from the filename, and make the triple authoritative by passing --target to cargo so a runner/target mismatch fails loudly instead of silently mislabeling an asset. Move the detailed download/verify instructions out of the READMEs and into the GitHub Release body, generated by the workflow so they can never drift from the actual assets. The block is injected idempotently between sentinel markers, so re-running the workflow (the recovery path for a failed matrix leg) replaces it in place rather than appending. The READMEs are demoted to short evergreen pointers to the Releases page. Extract the duplicated native-dependency install steps (Linux ALSA headers, Windows NASM) into a shared .github/actions/install-build-deps composite action reused by ci.yml and release-binaries.yml. --- .github/actions/install-build-deps/action.yml | 20 +++ .github/workflows/ci.yml | 21 +--- .github/workflows/release-binaries.yml | 119 +++++++++++++++--- README.md | 38 ++---- crates/ironrdp-agent/README.md | 6 + crates/ironrdp-viewer/README.md | 6 + release-plz.toml | 3 +- 7 files changed, 147 insertions(+), 66 deletions(-) create mode 100644 .github/actions/install-build-deps/action.yml diff --git a/.github/actions/install-build-deps/action.yml b/.github/actions/install-build-deps/action.yml new file mode 100644 index 000000000..e055089cd --- /dev/null +++ b/.github/actions/install-build-deps/action.yml @@ -0,0 +1,20 @@ +name: Install build dependencies +description: Install the native build dependencies required to build IronRDP (Linux ALSA headers, Windows NASM). + +runs: + using: composite + steps: + - name: Install devel packages + if: ${{ runner.os == 'Linux' }} + shell: bash + run: | + sudo apt-get update -qq + sudo apt-get -y install libasound2-dev + + - name: Install NASM + if: ${{ runner.os == 'Windows' }} + shell: pwsh + run: | + choco install nasm + $Env:PATH += ";$Env:ProgramFiles\NASM" + echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bca3fadfe..7580c589a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,19 +74,8 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Install devel packages - if: ${{ runner.os == 'Linux' }} - run: | - sudo apt-get update -qq - sudo apt-get -y install libasound2-dev - - - name: Install NASM - if: ${{ runner.os == 'Windows' }} - run: | - choco install nasm - $Env:PATH += ";$Env:ProgramFiles\NASM" - echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV - shell: pwsh + - name: Install build dependencies + uses: ./.github/actions/install-build-deps - name: Rust cache uses: Swatinem/rust-cache@v2.7.3 @@ -232,10 +221,8 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Install devel packages - run: | - sudo apt-get update -qq - sudo apt-get -y install libasound2-dev + - name: Install build dependencies + uses: ./.github/actions/install-build-deps - name: Rust cache uses: Swatinem/rust-cache@v2.7.3 diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index e58278d58..e2b8b34ae 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -51,20 +51,29 @@ jobs: strategy: fail-fast: false matrix: + # `target` is each runner's native triple; it is passed to cargo (see the build step) so a + # runner/target mismatch fails the build loudly instead of silently mislabeling an asset. + # `os_arch` is the friendly, triple-free label used for the asset and artifact names. include: - runner: windows-2022 target: x86_64-pc-windows-msvc + os_arch: windows-x64 - runner: windows-11-arm target: aarch64-pc-windows-msvc + os_arch: windows-arm64 - runner: ubuntu-22.04 target: x86_64-unknown-linux-gnu + os_arch: linux-x64 - runner: ubuntu-22.04-arm target: aarch64-unknown-linux-gnu + os_arch: linux-arm64 - runner: macos-15-intel target: x86_64-apple-darwin + os_arch: macos-x64 macos_deployment_target: '10.13' - runner: macos-14 target: aarch64-apple-darwin + os_arch: macos-arm64 macos_deployment_target: '11.0' steps: @@ -73,19 +82,8 @@ jobs: with: ref: ${{ github.event.release.tag_name }} - - name: Install Linux build dependencies - if: ${{ runner.os == 'Linux' }} - run: | - sudo apt-get update -qq - sudo apt-get -y install libasound2-dev - - - name: Install NASM - if: ${{ runner.os == 'Windows' }} - run: | - choco install nasm - $Env:PATH += ";$Env:ProgramFiles\NASM" - echo "PATH=$Env:PATH" >> $Env:GITHUB_ENV - shell: pwsh + - name: Install build dependencies + uses: ./.github/actions/install-build-deps - name: Rust cache uses: Swatinem/rust-cache@v2.7.3 @@ -101,7 +99,8 @@ jobs: $env:MACOSX_DEPLOYMENT_TARGET = '${{ matrix.macos_deployment_target }}' } - cargo build --locked --release --package '${{ needs.select-package.outputs.package }}' + rustup target add '${{ matrix.target }}' + cargo build --locked --release --target '${{ matrix.target }}' --package '${{ needs.select-package.outputs.package }}' - name: Package binary shell: pwsh @@ -109,10 +108,11 @@ jobs: PACKAGE: ${{ needs.select-package.outputs.package }} VERSION: ${{ needs.select-package.outputs.version }} TARGET: ${{ matrix.target }} + OS_ARCH: ${{ matrix.os_arch }} run: | $extension = if ($env:RUNNER_OS -eq 'Windows') { '.exe' } else { '' } - $binary = Join-Path (Join-Path 'target' 'release') "$env:PACKAGE$extension" - $assetName = "$env:PACKAGE-$env:VERSION-$env:TARGET.tar.gz" + $binary = Join-Path (Join-Path (Join-Path 'target' $env:TARGET) 'release') "$env:PACKAGE$extension" + $assetName = "$env:PACKAGE-$env:VERSION-$env:OS_ARCH.tar.gz" $assetDirectory = 'release-assets' New-Item -ItemType Directory -Force -Path $assetDirectory | Out-Null @@ -128,7 +128,7 @@ jobs: - name: Upload release asset uses: actions/upload-artifact@v7 with: - name: release-assets-${{ matrix.target }} + name: release-assets-${{ matrix.os_arch }} path: release-assets/* if-no-files-found: error retention-days: 1 @@ -136,6 +136,11 @@ jobs: publish: name: Upload release assets needs: [select-package, build] + # All-or-nothing on purpose: with `fail-fast: false`, if any one of the matrix legs fails, + # `needs.build.result` is not 'success' and NO assets are uploaded, rather than publishing a + # partial set. The Release itself is already published by release-plz, so a failed build leaves + # it without binaries; recovery is re-running this workflow. The final `gh release upload + # --clobber` and the notes step below are both idempotent, so re-runs are safe. if: ${{ always() && needs.select-package.outputs.package != '' && needs.build.result == 'success' }} runs-on: ubuntu-latest @@ -152,3 +157,83 @@ jobs: GH_TOKEN: ${{ github.token }} TAG_NAME: ${{ github.event.release.tag_name }} run: gh release upload "$TAG_NAME" release-assets/* --clobber + + - name: Update release notes with install instructions + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ github.event.release.tag_name }} + PACKAGE: ${{ needs.select-package.outputs.package }} + VERSION: ${{ needs.select-package.outputs.version }} + run: | + $ErrorActionPreference = 'Stop' + + $begin = '' + $end = '' + + # Literal template: the ${PACKAGE}/${VERSION} placeholders are substituted below; the tag is + # derived as ${PACKAGE}-v${VERSION}. Example variables ($ASSET, $BASE, $Asset) stay literal + # because the here-string is single-quoted (no interpolation). + $template = @' + + ## Prebuilt binaries + + Prebuilt, checksummed archives of `${PACKAGE}` are attached to this release, one per platform: + + - `${PACKAGE}-${VERSION}-windows-x64.tar.gz` + - `${PACKAGE}-${VERSION}-windows-arm64.tar.gz` + - `${PACKAGE}-${VERSION}-linux-x64.tar.gz` + - `${PACKAGE}-${VERSION}-linux-arm64.tar.gz` + - `${PACKAGE}-${VERSION}-macos-x64.tar.gz` + - `${PACKAGE}-${VERSION}-macos-arm64.tar.gz` + + Each archive has a matching `.sha256` sidecar. The Windows archives contain `${PACKAGE}.exe`; all + other archives contain the bare `${PACKAGE}` executable. + + ### Download and verify + + Pick the archive for your platform (this example uses `linux-x64`): + + ```shell + ASSET="${PACKAGE}-${VERSION}-linux-x64.tar.gz" + BASE="https://github.com/Devolutions/IronRDP/releases/download/${PACKAGE}-v${VERSION}" + curl -fLO "$BASE/$ASSET" + curl -fLO "$BASE/$ASSET.sha256" + sha256sum --check "$ASSET.sha256" + tar -xzf "$ASSET" + ``` + + On Windows, verify with `Get-FileHash` and extract with the bundled `tar` (PowerShell): + + ```powershell + $Asset = "${PACKAGE}-${VERSION}-windows-x64.tar.gz" + (Get-FileHash -Algorithm SHA256 $Asset).Hash.ToLowerInvariant() # compare with the .sha256 sidecar + tar -xzf $Asset + ``` + + ### Build baselines + + - Linux archives are built on Ubuntu 22.04 and require glibc 2.35 or later. + - macOS archives target macOS 10.13 or later on Intel and macOS 11.0 or later on Apple Silicon. + + Assets are attached to this release (tag `${PACKAGE}-v${VERSION}`). Don't rely on the `latest` release URL: + releases are tagged per package (`ironrdp-agent-v*`, `ironrdp-viewer-v*`), so `latest` may resolve + to the other package. + + '@ + + $block = $template.Replace('${PACKAGE}', $env:PACKAGE).Replace('${VERSION}', $env:VERSION) + + $body = (gh release view $env:TAG_NAME --json body -q .body | Out-String).TrimEnd() + + # Idempotent inject: replace the sentinel-delimited region if present, otherwise append. + $beginIndex = $body.IndexOf($begin) + $endIndex = $body.IndexOf($end) + if ($beginIndex -ge 0 -and $endIndex -ge 0) { + $newBody = $body.Substring(0, $beginIndex) + $block + $body.Substring($endIndex + $end.Length) + } else { + $newBody = if ($body) { "$body`n`n$block" } else { $block } + } + + Set-Content -Path newbody.md -Value $newBody -Encoding utf8 + gh release edit $env:TAG_NAME --notes-file newbody.md diff --git a/README.md b/README.md index a74a82601..fb3b3e8ce 100644 --- a/README.md +++ b/README.md @@ -67,39 +67,15 @@ Alternatively, you may change a few group policies using `gpedit.msc`: ## Binary releases -Standalone archives are attached to GitHub Releases for the executable packages: +Prebuilt, checksummed `.tar.gz` archives are attached to each GitHub Release for the executable +packages, one per supported platform: -- [`ironrdp-agent`](./crates/ironrdp-agent) provides the agentic, daemon-backed CLI. -- [`ironrdp-viewer`](./crates/ironrdp-viewer) provides the windowed RDP client CLI. +- [`ironrdp-agent`](./crates/ironrdp-agent) — the agentic, daemon-backed CLI. +- [`ironrdp-viewer`](./crates/ironrdp-viewer) — the windowed RDP client CLI. -Each release provides one `.tar.gz` archive and a SHA-256 sidecar for these native target triples: - -| Platform | Target triple | -| --- | --- | -| Windows x64 | `x86_64-pc-windows-msvc` | -| Windows ARM64 | `aarch64-pc-windows-msvc` | -| Linux x64 | `x86_64-unknown-linux-gnu` | -| Linux ARM64 | `aarch64-unknown-linux-gnu` | -| macOS x64 | `x86_64-apple-darwin` | -| macOS ARM64 | `aarch64-apple-darwin` | - -Linux archives use an Ubuntu 22.04 build baseline and require glibc 2.35 or later. macOS archives -target macOS 10.13 or later on Intel and macOS 11.0 or later on Apple Silicon. - -For example, download and extract the Linux x64 agent from its release: - -```shell -VERSION= -ASSET="ironrdp-agent-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" -curl -fLO "https://github.com/Devolutions/IronRDP/releases/download/ironrdp-agent-v${VERSION}/${ASSET}" -curl -fLO "https://github.com/Devolutions/IronRDP/releases/download/ironrdp-agent-v${VERSION}/${ASSET}.sha256" -sha256sum --check "${ASSET}.sha256" -tar -xzf "${ASSET}" -``` - -Replace `ironrdp-agent` with `ironrdp-viewer` to download the windowed client from its corresponding -package release. Windows archives contain an `.exe`; all other archives contain the executable without -an extension. +Each package is released under its own tag (`ironrdp-agent-v*`, `ironrdp-viewer-v*`). See the +[Releases page](https://github.com/Devolutions/IronRDP/releases) to pick a release and follow the +per-platform download, checksum, and extraction instructions included in its notes. ## Rust version (MSRV) diff --git a/crates/ironrdp-agent/README.md b/crates/ironrdp-agent/README.md index ba376a9ea..4d59f08de 100644 --- a/crates/ironrdp-agent/README.md +++ b/crates/ironrdp-agent/README.md @@ -13,6 +13,12 @@ The single `ironrdp-agent` binary bundles two roles: Run `ironrdp-agent --help-agent` for a structured, machine-readable description of every operation. +## Prebuilt binaries + +Prebuilt, checksummed archives are attached to each GitHub Release under the `ironrdp-agent-v*` +tags. See the [Releases page](https://github.com/Devolutions/IronRDP/releases) for per-platform +download and verification instructions. + ## Wire format Messages are encoded with [`ironrdp-core`]'s `Encode`/`Decode` traits, length-delimited with a diff --git a/crates/ironrdp-viewer/README.md b/crates/ironrdp-viewer/README.md index 18710c2fb..2361ec078 100644 --- a/crates/ironrdp-viewer/README.md +++ b/crates/ironrdp-viewer/README.md @@ -6,6 +6,12 @@ This is a a full-fledged RDP client based on IronRDP crates suite, and implement non-blocking, asynchronous I/O. Portability is achieved by using softbuffer for rendering and winit for windowing. +## Prebuilt binaries + +Prebuilt, checksummed archives are attached to each GitHub Release under the `ironrdp-viewer-v*` +tags. See the [Releases page](https://github.com/Devolutions/IronRDP/releases) for per-platform +download and verification instructions. + ## Sample usage ```shell diff --git a/release-plz.toml b/release-plz.toml index 080b4d51a..4f3a3395e 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -7,7 +7,8 @@ pr_name = "chore(release): prepare for publishing" changelog_config = "cliff.toml" release_commits = "^(feat|docs|fix|build|perf)" -# Flagship crate for which we push a GitHub release. +# Executable crates for which we push a GitHub release (and attach prebuilt binaries via +# release-binaries.yml). Each is released under its own tag (ironrdp-agent-v*, ironrdp-viewer-v*). [[package]] name = "ironrdp-agent" git_release_enable = true From 17f8f7e90a6d9e854d903e12bce890658554260c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Fri, 10 Jul 2026 22:08:52 +0900 Subject: [PATCH 2/3] . --- .github/workflows/release-binaries.yml | 42 ++++++++++++++------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index e2b8b34ae..59e1044b7 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -132,6 +132,9 @@ jobs: path: release-assets/* if-no-files-found: error retention-days: 1 + # Allow a full "Re-run all jobs" (the recovery path) to re-upload over a prior attempt's + # artifact of the same name instead of failing. + overwrite: true publish: name: Upload release assets @@ -178,37 +181,34 @@ jobs: ## Prebuilt binaries - Prebuilt, checksummed archives of `${PACKAGE}` are attached to this release, one per platform: + Prebuilt, checksummed archives of `${PACKAGE}` are attached to this release: - - `${PACKAGE}-${VERSION}-windows-x64.tar.gz` - - `${PACKAGE}-${VERSION}-windows-arm64.tar.gz` - - `${PACKAGE}-${VERSION}-linux-x64.tar.gz` - - `${PACKAGE}-${VERSION}-linux-arm64.tar.gz` - - `${PACKAGE}-${VERSION}-macos-x64.tar.gz` - - `${PACKAGE}-${VERSION}-macos-arm64.tar.gz` + ${ASSET_LIST} - Each archive has a matching `.sha256` sidecar. The Windows archives contain `${PACKAGE}.exe`; all - other archives contain the bare `${PACKAGE}` executable. + Each archive has a matching `.sha256` sidecar. The Windows archive contains `${PACKAGE}.exe`; + the others contain the bare `${PACKAGE}` executable. ### Download and verify - Pick the archive for your platform (this example uses `linux-x64`): + Pick an archive from the list above and download it together with its `.sha256` sidecar: ```shell - ASSET="${PACKAGE}-${VERSION}-linux-x64.tar.gz" + ASSET="" BASE="https://github.com/Devolutions/IronRDP/releases/download/${PACKAGE}-v${VERSION}" curl -fLO "$BASE/$ASSET" curl -fLO "$BASE/$ASSET.sha256" - sha256sum --check "$ASSET.sha256" - tar -xzf "$ASSET" ``` - On Windows, verify with `Get-FileHash` and extract with the bundled `tar` (PowerShell): + Verify the checksum with the tool available on your platform: + + - Linux: `sha256sum --check "$ASSET.sha256"` + - macOS: `shasum -a 256 --check "$ASSET.sha256"` + - Windows (PowerShell): `(Get-FileHash -Algorithm SHA256 $ASSET).Hash.ToLowerInvariant() -eq (Get-Content "$ASSET.sha256").Split(' ')[0]` + + Then extract (`tar` ships with Linux, macOS, and Windows): - ```powershell - $Asset = "${PACKAGE}-${VERSION}-windows-x64.tar.gz" - (Get-FileHash -Algorithm SHA256 $Asset).Hash.ToLowerInvariant() # compare with the .sha256 sidecar - tar -xzf $Asset + ```shell + tar -xzf "$ASSET" ``` ### Build baselines @@ -222,7 +222,11 @@ jobs: '@ - $block = $template.Replace('${PACKAGE}', $env:PACKAGE).Replace('${VERSION}', $env:VERSION) + # Build the asset list from the archives actually attached to this release, so it can never + # drift from the build matrix (adding/renaming a target updates the notes automatically). + $assetList = (Get-ChildItem -Path release-assets -Filter *.tar.gz | Sort-Object Name | ForEach-Object { "- ``$($_.Name)``" }) -join "`n" + + $block = $template.Replace('${PACKAGE}', $env:PACKAGE).Replace('${VERSION}', $env:VERSION).Replace('${ASSET_LIST}', $assetList) $body = (gh release view $env:TAG_NAME --json body -q .body | Out-String).TrimEnd() From d0503f0ef5e026d96a9477830a5a3bdeaec2c0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Fri, 10 Jul 2026 22:17:34 +0900 Subject: [PATCH 3/3] . --- .github/workflows/release-binaries.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 59e1044b7..182bb9528 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -190,25 +190,29 @@ jobs: ### Download and verify - Pick an archive from the list above and download it together with its `.sha256` sidecar: + Pick an archive from the list above, then download, verify, and extract it. The two blocks + below are self-contained; use the one for your platform. + + Linux and macOS (POSIX shell): ```shell ASSET="" BASE="https://github.com/Devolutions/IronRDP/releases/download/${PACKAGE}-v${VERSION}" curl -fLO "$BASE/$ASSET" curl -fLO "$BASE/$ASSET.sha256" + sha256sum --check "$ASSET.sha256" # on macOS: shasum -a 256 --check "$ASSET.sha256" + tar -xzf "$ASSET" ``` - Verify the checksum with the tool available on your platform: - - - Linux: `sha256sum --check "$ASSET.sha256"` - - macOS: `shasum -a 256 --check "$ASSET.sha256"` - - Windows (PowerShell): `(Get-FileHash -Algorithm SHA256 $ASSET).Hash.ToLowerInvariant() -eq (Get-Content "$ASSET.sha256").Split(' ')[0]` + Windows (PowerShell): - Then extract (`tar` ships with Linux, macOS, and Windows): - - ```shell - tar -xzf "$ASSET" + ```powershell + $Asset = "" + $Base = "https://github.com/Devolutions/IronRDP/releases/download/${PACKAGE}-v${VERSION}" + curl.exe -fLO "$Base/$Asset" + curl.exe -fLO "$Base/$Asset.sha256" + if ((Get-FileHash -Algorithm SHA256 $Asset).Hash.ToLowerInvariant() -ne (Get-Content "$Asset.sha256").Split(' ')[0]) { throw 'checksum mismatch' } + tar -xzf $Asset ``` ### Build baselines