feat(core): RDP client on IronRDP
clients::rdp connects over any caller-dialled TCP stream (direct, proxy or SSH tunnel), upgrades to TLS and logs on with NLA (CredSSP/NTLM), optionally falling back to TLS-only logon for servers without NLA such as xrdp (credentials are passed so they log straight in). The server certificate is pinned through a caller check on its SHA256 fingerprint before any credentials are sent. The desktop decodes into the VNC client's RGBA framebuffer; input (IronRDP's scancode/Unicode/mouse operations), Display Control resizes and the deactivation-reactivation sequence run on the session thread. IronRDP's releases need three vendored patches (vendor/README.md): picky and sspi with their RustCrypto release-candidate pins relaxed so they share russh's stable versions (their own suites, 402 tests, pass against them), and ironrdp-session with two unreleased upstream bitmap fixes (#1486 stride, #1524 RDP6 orientation) without which xrdp desktops render sheared. Verified live against xrdp (scottyhardy/docker-remote-desktop): logon, full desktop rendering, keyboard and mouse input.
This commit is contained in:
+322
@@ -0,0 +1,322 @@
|
||||
name: Publish NuGet package
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry-run:
|
||||
description: 'Dry run'
|
||||
required: true
|
||||
type: boolean
|
||||
default: true
|
||||
version:
|
||||
description: 'Package version'
|
||||
default: "latest"
|
||||
required: true
|
||||
schedule:
|
||||
- cron: '21 3 * * 1' # 3:21 AM UTC every Monday
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
name: Preflight
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
dry-run: ${{ steps.get-dry-run.outputs.dry-run }}
|
||||
package-version: ${{ steps.info.outputs.package-version }}
|
||||
|
||||
steps:
|
||||
- name: Get dry run
|
||||
id: get-dry-run
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-PSDebug -Trace 1
|
||||
|
||||
$IsDryRun = '${{ github.event.inputs.dry-run }}' -Eq 'true' -Or '${{ github.event_name }}' -Eq 'schedule'
|
||||
|
||||
if ($IsDryRun) {
|
||||
echo "dry-run=true" >> $Env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "dry-run=false" >> $Env:GITHUB_OUTPUT
|
||||
}
|
||||
|
||||
- name: Package information
|
||||
id: info
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PackageVersion = '${{ inputs.version }}'
|
||||
if ([string]::IsNullOrEmpty($PackageVersion) -or $PackageVersion -eq 'latest') {
|
||||
$PackageVersion = (Get-Date -Format "yyyy.MM.dd") + ".0"
|
||||
}
|
||||
|
||||
if ($PackageVersion -NotMatch '^\d+\.\d+\.\d+\.\d+$') {
|
||||
throw "invalid version format: $PackageVersion, expected: 1.2.3.4"
|
||||
}
|
||||
|
||||
echo "package-version=$PackageVersion" >> $Env:GITHUB_OUTPUT
|
||||
echo "::notice::Version: $PackageVersion"
|
||||
|
||||
build-native:
|
||||
uses: ./.github/workflows/build-native.yml
|
||||
|
||||
build-managed:
|
||||
name: Managed build
|
||||
runs-on: windows-2022
|
||||
needs: [ preflight, build-native ]
|
||||
|
||||
steps:
|
||||
- name: Check out ${{ github.repository }}
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Prepare dependencies
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Path "dependencies/runtimes" | Out-Null
|
||||
|
||||
- name: Download native components
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: dependencies/runtimes
|
||||
|
||||
- name: Rename dependencies
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-PSDebug -Trace 1
|
||||
|
||||
Set-Location "dependencies/runtimes"
|
||||
|
||||
$(Get-Item ".\sspi-*-release") | ForEach-Object { Rename-Item $_ $_.Name.Replace("-release", "") }
|
||||
$(Get-Item ".\sspi-*") | ForEach-Object { Rename-Item $_ $_.Name.Replace("sspi-", "") }
|
||||
Get-ChildItem * -Recurse
|
||||
|
||||
- name: Set package version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
$csprojPath = "ffi\dotnet\Devolutions.Sspi\Devolutions.Sspi.csproj"
|
||||
$csprojContent = Get-Content $csprojPath -Raw
|
||||
$csprojContent = $csprojContent -Replace '(<Version>).*?(</Version>)', "<Version>$PackageVersion</Version>"
|
||||
Set-Content -Path $csprojPath -Value $csprojContent -Encoding UTF8
|
||||
|
||||
- name: Build sspi (managed)
|
||||
shell: pwsh
|
||||
run: |
|
||||
dotnet build .\ffi\dotnet\Devolutions.Sspi\Devolutions.Sspi.csproj -o package
|
||||
|
||||
- name: Upload managed components
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sspi-nupkg
|
||||
path: package/*.nupkg
|
||||
|
||||
publish:
|
||||
name: Publish NuGet package
|
||||
runs-on: ubuntu-latest
|
||||
environment: nuget-publish
|
||||
if: needs.preflight.outputs.dry-run == 'false'
|
||||
needs:
|
||||
- preflight
|
||||
- build-managed
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download NuGet package artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: sspi-nupkg
|
||||
path: package
|
||||
|
||||
- name: NuGet login (OIDC)
|
||||
uses: NuGet/login@v1
|
||||
id: nuget-login
|
||||
with:
|
||||
user: ${{ secrets.NUGET_BOT_USERNAME }}
|
||||
|
||||
- name: Publish to nuget.org
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-PSDebug -Trace 1
|
||||
|
||||
$Files = Get-ChildItem -Recurse package/*.nupkg
|
||||
|
||||
foreach ($File in $Files) {
|
||||
$PushCmd = @(
|
||||
'dotnet',
|
||||
'nuget',
|
||||
'push',
|
||||
"$File",
|
||||
'--api-key',
|
||||
'${{ steps.nuget-login.outputs.NUGET_API_KEY }}',
|
||||
'--source',
|
||||
'https://api.nuget.org/v3/index.json',
|
||||
'--skip-duplicate'
|
||||
)
|
||||
|
||||
Write-Host "Publishing $($File.Name)..."
|
||||
$PushCmd = $PushCmd -Join ' '
|
||||
Invoke-Expression $PushCmd
|
||||
}
|
||||
|
||||
package-symbols:
|
||||
name: Package debug symbols
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ preflight, build-native ]
|
||||
|
||||
steps:
|
||||
- name: Download all symbol artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: sspi-*-release-symbols
|
||||
|
||||
- name: Package symbols into zip
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-PSDebug -Trace 1
|
||||
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
$SymbolsDir = "symbols"
|
||||
New-Item -ItemType Directory -Path $SymbolsDir -Force | Out-Null
|
||||
|
||||
# Organize symbols by RID
|
||||
$SymbolArtifacts = Get-ChildItem -Directory "artifacts" -Filter "sspi-*-release-symbols"
|
||||
foreach ($Artifact in $SymbolArtifacts) {
|
||||
# Extract RID from artifact name (e.g., sspi-win-x64-release-symbols -> win-x64)
|
||||
$Rid = $Artifact.Name -Replace '^sspi-(.+)-release-symbols$', '$1'
|
||||
$DestDir = Join-Path $SymbolsDir $Rid
|
||||
New-Item -ItemType Directory -Path $DestDir -Force | Out-Null
|
||||
|
||||
# Copy all contents from the artifact
|
||||
Get-ChildItem -Path $Artifact.FullName -Recurse | ForEach-Object {
|
||||
if (-not $_.PSIsContainer) {
|
||||
$RelativePath = $_.FullName.Substring($Artifact.FullName.Length + 1)
|
||||
$DestPath = Join-Path $DestDir $RelativePath
|
||||
$DestParent = Split-Path $DestPath -Parent
|
||||
if (-not (Test-Path $DestParent)) {
|
||||
New-Item -ItemType Directory -Path $DestParent -Force | Out-Null
|
||||
}
|
||||
Copy-Item $_.FullName $DestPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Symbol directory structure:"
|
||||
Get-ChildItem -Recurse $SymbolsDir | ForEach-Object { Write-Host $_.FullName }
|
||||
|
||||
# Create the zip file
|
||||
$ZipName = "Devolutions.Sspi.$PackageVersion.symbols.zip"
|
||||
Compress-Archive -Path "$SymbolsDir/*" -DestinationPath $ZipName -Force
|
||||
Write-Host "Created symbols archive: $ZipName"
|
||||
|
||||
- name: Upload symbols zip
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sspi-symbols-zip
|
||||
path: Devolutions.Sspi.*.symbols.zip
|
||||
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.preflight.outputs.dry-run == 'false'
|
||||
needs:
|
||||
- preflight
|
||||
- build-managed
|
||||
- package-symbols
|
||||
- publish
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download NuGet package
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: sspi-nupkg
|
||||
path: release-assets
|
||||
|
||||
- name: Download symbols zip
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: sspi-symbols-zip
|
||||
path: release-assets
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Set-PSDebug -Trace 1
|
||||
|
||||
$PackageVersion = '${{ needs.preflight.outputs.package-version }}'
|
||||
$TagName = "v$PackageVersion"
|
||||
$ReleaseName = "Devolutions.Sspi $PackageVersion"
|
||||
|
||||
# Get the list of assets to upload
|
||||
$Assets = Get-ChildItem -Path "release-assets" -File
|
||||
Write-Host "Release assets:"
|
||||
$Assets | ForEach-Object { Write-Host " - $($_.Name)" }
|
||||
|
||||
# Build the release notes
|
||||
$ReleaseNotes = @"
|
||||
## Devolutions.Sspi $PackageVersion
|
||||
|
||||
### NuGet Package
|
||||
- [Devolutions.Sspi on NuGet.org](https://www.nuget.org/packages/Devolutions.Sspi/$PackageVersion)
|
||||
|
||||
### Included Platforms
|
||||
- Windows (x64, arm64)
|
||||
- macOS (x64, arm64, universal)
|
||||
- Linux (x64, arm64)
|
||||
- iOS device (arm64)
|
||||
- iOS Simulator (arm64)
|
||||
- Android (x86, x64, arm, arm64)
|
||||
|
||||
### Debug Symbols
|
||||
The `Devolutions.Sspi.$PackageVersion.symbols.zip` archive contains debug symbols for all platforms:
|
||||
- Windows: `.pdb` files
|
||||
- macOS/iOS: `.dSYM` bundles
|
||||
- Linux/Android: `.debug` or `.dwp` files
|
||||
"@
|
||||
|
||||
# Create the release with assets
|
||||
$AssetArgs = @()
|
||||
foreach ($Asset in $Assets) {
|
||||
$AssetArgs += $Asset.FullName
|
||||
}
|
||||
|
||||
gh release create $TagName `
|
||||
--title $ReleaseName `
|
||||
--notes $ReleaseNotes `
|
||||
--target ${{ github.sha }} `
|
||||
@AssetArgs
|
||||
|
||||
Write-Host "Created release: $ReleaseName with tag $TagName"
|
||||
|
||||
notify:
|
||||
name: Notify failure
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
|
||||
needs:
|
||||
- preflight
|
||||
- build-native
|
||||
- build-managed
|
||||
- package-symbols
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
steps:
|
||||
- name: Send slack notification
|
||||
id: slack
|
||||
uses: slackapi/slack-github-action@v1.26.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*${{ github.repository }}* :fire::fire::fire::fire::fire: \n The scheduled build for *${{ github.repository }}* is <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|broken>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user