Skip to content

Release

Release #104

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
run:
description: The Package workflow run to release
required: true
dry-run:
description: If true, the workflow only indicates which artifacts would be uploaded
required: true
type: boolean
default: true
workflow_call:
inputs:
dispatch:
description: Marker to indicate that the workflow was dispatched via workflow_call
type: string
required: false
default: workflow_call
dry-run:
description: If true, the workflow only indicates which artifacts would be uploaded
required: true
type: boolean
default: true
concurrency: gateway-release
jobs:
preflight:
name: Preflight
runs-on: ubuntu-latest
outputs:
run: ${{ steps.get-run.outputs.run }}
version: ${{ steps.get-version.outputs.version }}
skip-publishing: ${{ steps.check-release.outputs.skip-publishing }}
steps:
## workflow_dispatch: The run_id is read from the inputs
## workflow_call: The run_id is the current run_id
- name: Get run
id: get-run
run: |
if ('${{ github.event.inputs.run }}') {
echo "run=${{ github.event.inputs.run }}" >> $Env:GITHUB_OUTPUT
} else {
echo "run=${{ github.run_id }}" >> $Env:GITHUB_OUTPUT
}
shell: pwsh
- name: Get dry run
id: get-dry-run
run: |
$DryRun = "false"
if ('${{ inputs.dry-run }}') {
$DryRun = "${{ inputs.dry-run }}"
}
if ([System.Convert]::ToBoolean($DryRun)) {
echo "::notice::This is a dry run; publishing will be skipped"
} else {
echo "::warning::This is not a dry run, release will be published!"
}
shell: pwsh
- name: Download version
run: gh run download ${{ steps.get-run.outputs.run }} -n version --repo $Env:GITHUB_REPOSITORY
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version
id: get-version
run: |
$Version = Get-Content VERSION -TotalCount 1
echo "version=$Version" >> $Env:GITHUB_OUTPUT
echo "::notice::Releasing artifacts for version $Version from run ${{ steps.get-run.outputs.run }}"
shell: pwsh
## If we already released this version to GitHub, publishing will be skipped
- name: Check GitHub releases
id: check-release
run: |
$Output = (gh release list --repo $Env:GITHUB_REPOSITORY) | Out-String
$Releases = ( $Output -split '\r?\n' ).Trim()
$Versions = ForEach($Release in $Releases) {
$Version = ( $Release -Split '\s+' ).Trim()
$Version = $Version.TrimStart("v")
$Version[0]
}
$SkipPublishing = 'false'
if ($Versions -Contains "${{ steps.get-version.outputs.version }}") {
echo "::warning::GitHub already has a release version ${{ steps.get-version.outputs.version }}; publishing will be skipped"
$SkipPublishing = 'true'
}
echo "skip-publishing=$SkipPublishing" >> $Env:GITHUB_OUTPUT
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
container:
name: Container [${{ matrix.os }} ${{ matrix.base-image }}]
environment: publish-prod
if: ${{ needs.preflight.outputs.skip-publishing == 'false' || inputs.dry-run }}
needs: [preflight]
runs-on: ${{ matrix.runner }}
permissions:
id-token: write
strategy:
fail-fast: false
matrix:
os: [linux]
base-image: [bookworm-slim]
include:
- os: linux
runner: ubuntu-latest
steps:
- name: Download artifacts
run: |
gh run download ${{ needs.preflight.outputs.run }} -n webapp-client -n webapp-player -n docker -n devolutions-gateway-signed -n native-libs --repo $Env:GITHUB_REPOSITORY
Move-Item -Path devolutions-gateway-signed -Destination devolutions-gateway
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Multi-arch Docker build strategy:
# We prepare separate build contexts for amd64 and arm64 because each architecture
# needs its own pre-compiled binary and architecture-specific native libraries (libxmf.so).
# This approach is more reliable than trying to use COPY --platform in the Dockerfile,
# which would require the binaries to be organized in a specific directory structure.
- name: Prepare artifacts
id: prepare-artifacts
run: |
$PowerShellArchive = Get-ChildItem -Recurse -Filter 'DevolutionsGateway-ps-*.tar' | Select-Object -First 1
foreach ($Arch in @(@{Ci='x64'; Docker='amd64'}, @{Ci='arm64'; Docker='arm64'})) {
$PkgDir = Join-Path docker $Env:RUNNER_OS $Arch.Docker
New-Item -ItemType Directory -Path $PkgDir -Force | Out-Null
echo "package-path-$($Arch.Docker)=$PkgDir" >> $Env:GITHUB_OUTPUT
Copy-Item -Path (Join-Path devolutions-gateway linux $Arch.Ci devolutions-gateway) -Destination $PkgDir
chmod +x (Join-Path $PkgDir devolutions-gateway)
Copy-Item -Path (Join-Path native-libs linux $Arch.Ci libxmf.so) -Destination $PkgDir
$ClientTarget = Join-Path $PkgDir webapp client
New-Item -ItemType Directory -Path $ClientTarget -Force | Out-Null
Copy-Item -Path webapp-client/* -Destination $ClientTarget -Recurse
$PlayerTarget = Join-Path $PkgDir webapp player
New-Item -ItemType Directory -Path $PlayerTarget -Force | Out-Null
Copy-Item -Path webapp-player/* -Destination $PlayerTarget -Recurse
tar -xf $PowerShellArchive.FullName -C $PkgDir
Copy-Item -Path docker/Linux/Dockerfile -Destination $PkgDir
Copy-Item -Path docker/Linux/entrypoint.ps1 -Destination $PkgDir
}
shell: pwsh
# QEMU is required for cross-platform Docker builds on x86_64 runners.
# It enables the runner to emulate ARM64 architecture during the build process.
# Without QEMU, we would need native ARM64 runners (which are more expensive and less available).
# Note: QEMU is only used during the IMAGE BUILD, not at runtime - the final images are native.
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
# Docker Buildx is required for multi-platform builds and creating manifest lists.
# It provides:
# 1. The ability to build for multiple architectures in a single command
# 2. The 'docker buildx imagetools' command for creating multi-arch manifests
# 3. Better caching and build performance compared to legacy docker build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
username: devolutionsbot
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
# Multi-arch build strategy:
# 1. Build separate images for each architecture with arch-specific tags
# 2. Push each image to the registry
# 3. Create a multi-arch manifest that references both images
#
# When users pull the image without specifying architecture, Docker automatically
# selects the correct variant based on their platform. This works because:
# - Each image is tagged with the architecture (e.g., :2025.3.3-amd64)
# - The manifest list (:2025.3.3) contains references to both arch-specific images
# - Docker client inspects the manifest and pulls the matching architecture
#
# Why we use separate contexts instead of --platform linux/amd64,linux/arm64:
# - Each architecture needs different pre-compiled binaries and native libraries
# - Separate contexts allow us to use architecture-specific artifacts directly
# - More explicit and easier to debug than complex Dockerfile conditionals
- name: Build and push multi-arch container
id: build-container
run: |
$Version = "${{ needs.preflight.outputs.version }}"
$ImageName = "devolutions/devolutions-gateway"
$DryRun = [System.Convert]::ToBoolean('${{ inputs.dry-run }}')
$Amd64Context = "${{ steps.prepare-artifacts.outputs.package-path-amd64 }}"
$Arm64Context = "${{ steps.prepare-artifacts.outputs.package-path-arm64 }}"
foreach ($P in @(@{Platform='linux/amd64'; Tag='amd64'; Context=$Amd64Context}, @{Platform='linux/arm64'; Tag='arm64'; Context=$Arm64Context})) {
$Args = @('buildx', 'build', "--platform=$($P.Platform)", "--tag=${ImageName}:${Version}-$($P.Tag)", $P.Context)
if (-Not $DryRun) { $Args += '--push' }
docker @Args
}
if (-Not $DryRun) {
foreach ($Tag in @($Version, 'latest')) {
docker buildx imagetools create `
--tag "${ImageName}:${Tag}" `
"${ImageName}:${Version}-amd64" `
"${ImageName}:${Version}-arm64"
}
} else {
Write-Host "Dry run: skipping manifest creation and push"
}
echo "image-name=${ImageName}:${Version}" >> $Env:GITHUB_OUTPUT
echo "latest-image-name=${ImageName}:latest" >> $Env:GITHUB_OUTPUT
shell: pwsh
- name: Install Cosign
if: ${{ !inputs.dry-run }}
uses: sigstore/cosign-installer@v4.0.0
- name: Sign image with Cosign
if: ${{ !inputs.dry-run }}
run: |
cosign sign --yes --recursive ${{ steps.build-container.outputs.image-name }}
cosign sign --yes --recursive ${{ steps.build-container.outputs.latest-image-name }}
github-release:
name: GitHub release
environment: publish-prod
if: ${{ needs.preflight.outputs.skip-publishing == 'false' || inputs.dry-run }}
needs: [preflight]
runs-on: ubuntu-latest
steps:
- name: Configure runner
run: cargo install parse-changelog
- name: Download artifacts
run: |
gh run download ${{ needs.preflight.outputs.run }} -n jetsocat-signed -n devolutions-gateway-signed -n devolutions-agent-signed -n webapp-client -n webapp-player -n native-libs -n changelog -D downloads --repo $Env:GITHUB_REPOSITORY
Move-Item downloads/jetsocat-signed downloads/jetsocat
Move-Item downloads/devolutions-gateway-signed downloads/devolutions-gateway
Move-Item downloads/devolutions-agent-signed downloads/devolutions-agent
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare release assets
run: |
$Version = "${{ needs.preflight.outputs.version }}"
$Dl = "downloads"
$Release = New-Item -Path "release" -ItemType Directory
# Gateway Windows MSI + symbols + self-contained ZIP (binary + xmf.dll + webapp)
Move-Item "$Dl/devolutions-gateway/windows/x64/DevolutionsGateway.msi" "$Release/DevolutionsGateway-${Version}-x64.msi"
Move-Item "$Dl/devolutions-gateway/windows/x64/DevolutionsGateway.symbols.zip" "$Release/DevolutionsGateway-${Version}-x64.symbols.zip"
$StageDir = Join-Path $Env:RUNNER_TEMP "dgw-windows-x64"
New-Item -Path $StageDir -ItemType Directory -Force | Out-Null
Copy-Item "$Dl/devolutions-gateway/windows/x64/DevolutionsGateway.exe" (Join-Path $StageDir 'DevolutionsGateway.exe')
Copy-Item "$Dl/native-libs/windows/x64/xmf.dll" (Join-Path $StageDir 'xmf.dll')
$ClientTarget = Join-Path $StageDir "webapp" "client"
New-Item -Path $ClientTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "$Dl/webapp-client/*" -Destination $ClientTarget -Recurse
$PlayerTarget = Join-Path $StageDir "webapp" "player"
New-Item -Path $PlayerTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "$Dl/webapp-player/*" -Destination $PlayerTarget -Recurse
Compress-Archive -Path (Join-Path $StageDir '*') -DestinationPath "$Release/DevolutionsGateway-${Version}-windows-x64.zip" -Force
Write-Host "Created DevolutionsGateway-${Version}-windows-x64.zip"
Remove-Item -Path $StageDir -Recurse -Force
# Gateway PowerShell nupkg
Get-ChildItem "$Dl/devolutions-gateway/PowerShell" -Filter "*.nupkg" | Move-Item -Destination $Release
# Gateway Linux DEB/RPM/changes + self-contained tar.xz (binary + libxmf.so + webapp)
foreach ($Arch in @('x64', 'arm64')) {
Move-Item "$Dl/devolutions-gateway/linux/${Arch}/devolutions-gateway.deb" "$Release/devolutions-gateway-${Version}-${Arch}.deb"
Move-Item "$Dl/devolutions-gateway/linux/${Arch}/devolutions-gateway.rpm" "$Release/devolutions-gateway-${Version}-${Arch}.rpm"
Move-Item "$Dl/devolutions-gateway/linux/${Arch}/devolutions-gateway.changes" "$Release/devolutions-gateway-${Version}-${Arch}.changes"
# Stage into a temp dir so the tar.xz root contains the files directly,
# not embedded under the download path.
$StageDir = Join-Path $Env:RUNNER_TEMP "dgw-linux-$Arch"
New-Item -Path $StageDir -ItemType Directory -Force | Out-Null
Copy-Item "$Dl/devolutions-gateway/linux/${Arch}/devolutions-gateway" (Join-Path $StageDir 'devolutions-gateway')
chmod +x (Join-Path $StageDir 'devolutions-gateway')
Copy-Item "$Dl/native-libs/linux/${Arch}/libxmf.so" (Join-Path $StageDir 'libxmf.so')
$ClientTarget = Join-Path $StageDir "webapp" "client"
New-Item -Path $ClientTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "$Dl/webapp-client/*" -Destination $ClientTarget -Recurse
$PlayerTarget = Join-Path $StageDir "webapp" "player"
New-Item -Path $PlayerTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "$Dl/webapp-player/*" -Destination $PlayerTarget -Recurse
tar -cJf "$Release/devolutions-gateway-${Version}-linux-${Arch}.tar.xz" -C $StageDir .
Write-Host "Created devolutions-gateway-${Version}-linux-${Arch}.tar.xz"
Remove-Item -Path $StageDir -Recurse -Force
}
# Agent Windows MSI + symbols; Linux Agent artifacts are not yet published.
foreach ($Arch in @('x64', 'arm64')) {
Move-Item "$Dl/devolutions-agent/windows/${Arch}/DevolutionsAgent.msi" "$Release/DevolutionsAgent-${Version}-${Arch}.msi"
Move-Item "$Dl/devolutions-agent/windows/${Arch}/DevolutionsAgent.symbols.zip" "$Release/DevolutionsAgent-${Version}-${Arch}.symbols.zip"
}
# Jetsocat archives: .tar.xz for Linux, .zip for Windows and macOS
foreach ($P in @(
@{ OS = "windows"; Arch = "x64"; Ext = ".exe"; Archive = "zip" },
@{ OS = "windows"; Arch = "arm64"; Ext = ".exe"; Archive = "zip" },
@{ OS = "linux"; Arch = "x64"; Ext = ""; Archive = "tar.xz" },
@{ OS = "linux"; Arch = "arm64"; Ext = ""; Archive = "tar.xz" },
@{ OS = "macos"; Arch = "x64"; Ext = ""; Archive = "tar.xz" },
@{ OS = "macos"; Arch = "arm64"; Ext = ""; Archive = "tar.xz" },
@{ OS = "macos"; Arch = "universal"; Ext = ""; Archive = "tar.xz" }
)) {
$BinaryPath = Join-Path $Dl jetsocat $P.OS $P.Arch "jetsocat$($P.Ext)"
if (Test-Path $BinaryPath) {
$ArchivePath = "$Release/jetsocat-${Version}-$($P.OS)-$($P.Arch).$($P.Archive)"
$StageDir = Join-Path $Env:RUNNER_TEMP "jetsocat-$($P.OS)-$($P.Arch)"
New-Item -ItemType Directory -Path $StageDir -Force | Out-Null
Copy-Item $BinaryPath (Join-Path $StageDir (Split-Path $BinaryPath -Leaf)) -Force
if ($P.Archive -eq 'tar.xz') {
tar -cJf $ArchivePath -C $StageDir .
} else {
# Compress-Archive preserves the source path structure inside the ZIP.
# Staging to a flat temp dir ensures the binary lands at the ZIP root.
Compress-Archive -Path (Join-Path $StageDir '*') -DestinationPath $ArchivePath -Force
}
Remove-Item -Path $StageDir -Recurse -Force
Write-Host "Created jetsocat-${Version}-$($P.OS)-$($P.Arch).$($P.Archive)"
} else {
throw "Jetsocat binary not found: $BinaryPath"
}
}
foreach ($Arch in @('x64', 'arm64')) {
$SymbolsPath = Join-Path $Dl jetsocat windows $Arch jetsocat.symbols.zip
if (Test-Path $SymbolsPath) {
Move-Item $SymbolsPath "$Release/jetsocat-${Version}-windows-${Arch}.symbols.zip"
} else {
throw "Jetsocat Windows symbols not found: $SymbolsPath"
}
}
shell: pwsh
- name: Create GitHub release
run: |
$Version = "${{ needs.preflight.outputs.version }}"
$Files = Get-ChildItem -Path release -Recurse -File | % { Get-FileHash -Algorithm SHA256 $_.FullName }
$HashPath = 'checksums'
$Files | % { "$($_.Hash) $(Split-Path $_.Path -leaf)" } | Out-File -FilePath $HashPath -Append -Encoding ASCII
echo "::group::checksums"
Get-Content $HashPath
echo "::endgroup::"
$ChangesPath = 'changes'
parse-changelog $(Join-Path downloads changelog CHANGELOG.md) $Version | Out-File -Encoding UTF8NoBOM $ChangesPath
echo "::group::changes"
Get-Content $ChangesPath
echo "::endgroup::"
$GhCmd = $(@('gh', 'release', 'create', "v$Version", "--repo", $Env:GITHUB_REPOSITORY, "--notes-file", $ChangesPath, $HashPath) + $Files.Path) -Join ' '
Write-Host $GhCmd
$DryRun = [System.Convert]::ToBoolean('${{ inputs.dry-run }}')
if (-Not $DryRun) {
Invoke-Expression $GhCmd
}
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
psgallery-release:
name: PowerShell release
environment: publish-prod
if: ${{ needs.preflight.outputs.skip-publishing == 'false' || inputs.dry-run }}
needs: [preflight]
runs-on: ubuntu-latest
steps:
- name: Download artifacts
run: gh run download ${{ needs.preflight.outputs.run }} -n devolutions-gateway-signed --repo $Env:GITHUB_REPOSITORY
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
## workflow_call: The same artifacts persist across the entire run, so the PowerShell/DevolutionsGateway directory will still exist from the CI workflow
- name: Manage artifacts
run: Remove-Item -Path (Join-Path PowerShell DevolutionsGateway) -Recurse -ErrorAction Ignore
shell: pwsh
- name: Install PSResourceGet
run: |
Install-PSResource Microsoft.PowerShell.PSResourceGet -Scope CurrentUser -TrustRepository
shell: pwsh
- name: Publish PowerShell module
run: |
$Archive = Get-ChildItem -Recurse -Filter "*-ps-*.tar" -File
Write-Host "Archive = $Archive"
tar -xvf "$Archive" -C './PowerShell'
Get-ChildItem -Path "./PowerShell" -Recurse
$PublishCmd = @('Publish-PSResource', '-Repository', 'PSGallery', '-Path', (Join-Path PowerShell DevolutionsGateway), '-ApiKey', '${{ secrets.PS_GALLERY_NUGET_API_KEY }}')
$DryRun = [System.Convert]::ToBoolean('${{ inputs.dry-run }}')
if ($DryRun) {
$PublishCmd += '-WhatIf'
}
$PublishCmd = $PublishCmd -Join ' '
Write-Host "PublishCmd = $PublishCmd"
try {
Invoke-Expression $PublishCmd
} catch {
if ($_.Exception.Message -ilike "*cannot be published as the current version*is already available in the repository*") {
echo "::warning::PowerShell module not published; this version is already listed on PSGallery"
} else {
Write-Error $_
exit 1
}
}
shell: pwsh
onedrive-gateway:
name: OneDrive (Devolutions Gateway)
environment: onedrive-upload # for OneDrive secrets
if: ${{ needs.preflight.outputs.skip-publishing == 'false' || inputs.dry-run }}
needs: [preflight]
runs-on: ubuntu-latest
permissions:
attestations: write
contents: read
id-token: write
steps:
- name: Check out Devolutions/actions
uses: actions/checkout@v6
with:
repository: Devolutions/actions
ref: v1
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
path: ./.github/workflows
## Devolutions Toolbox is required for OneDrive uploading
- name: Install Devolutions Toolbox
uses: ./.github/workflows/toolbox-install
with:
github_token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
- name: Download artifacts
run: |
gh run download ${{ needs.preflight.outputs.run }} -n devolutions-gateway-signed -n native-libs -n webapp-client -n webapp-player --repo $Env:GITHUB_REPOSITORY
Move-Item devolutions-gateway-signed devolutions-gateway
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare upload
id: prepare
run: |
$destinationFolder = "${{ runner.temp }}/artifacts"
$version = "${{ needs.preflight.outputs.version }}"
# Note that ".0" is appended here (required by release tooling downstream)
$versionFull = "$version.0"
Write-Host "Preparing Gateway artifacts in $destinationFolder for version $versionFull"
echo "version=${versionFull}" >> $Env:GITHUB_OUTPUT
echo "files-to-upload=$destinationFolder" >> $Env:GITHUB_OUTPUT
New-Item -Path $destinationFolder -ItemType Directory
Move-Item -Path "./devolutions-gateway/windows/x64/DevolutionsGateway.msi" -Destination "$destinationFolder/DevolutionsGateway-${versionFull}-x64.msi"
Move-Item -Path "./devolutions-gateway/windows/x64/DevolutionsGateway.symbols.zip" -Destination "$destinationFolder/DevolutionsGateway-${versionFull}-x64.symbols.zip"
$StageDir = Join-Path $Env:RUNNER_TEMP "dgw-windows-x64"
New-Item -Path $StageDir -ItemType Directory -Force | Out-Null
Copy-Item -Path "./devolutions-gateway/windows/x64/DevolutionsGateway.exe" -Destination (Join-Path $StageDir 'DevolutionsGateway.exe')
Copy-Item -Path "./native-libs/windows/x64/xmf.dll" -Destination (Join-Path $StageDir 'xmf.dll')
$ClientTarget = Join-Path $StageDir "webapp" "client"
New-Item -Path $ClientTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "webapp-client/*" -Destination $ClientTarget -Recurse
$PlayerTarget = Join-Path $StageDir "webapp" "player"
New-Item -Path $PlayerTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "webapp-player/*" -Destination $PlayerTarget -Recurse
Compress-Archive -Path (Join-Path $StageDir '*') -DestinationPath "$destinationFolder/DevolutionsGateway-${versionFull}-windows-x64.zip" -Force
Write-Host "Created DevolutionsGateway-${versionFull}-windows-x64.zip"
Remove-Item -Path $StageDir -Recurse -Force
foreach ($Arch in @('x64', 'arm64')) {
Move-Item -Path "./devolutions-gateway/linux/${Arch}/devolutions-gateway.deb" -Destination "$destinationFolder/devolutions-gateway-${versionFull}-${Arch}.deb"
Move-Item -Path "./devolutions-gateway/linux/${Arch}/devolutions-gateway.rpm" -Destination "$destinationFolder/devolutions-gateway-${versionFull}-${Arch}.rpm"
Move-Item -Path "./devolutions-gateway/linux/${Arch}/devolutions-gateway.changes" -Destination "$destinationFolder/devolutions-gateway-${versionFull}-${Arch}.changes"
$BinaryPath = "./devolutions-gateway/linux/${Arch}/devolutions-gateway"
$StageDir = Join-Path $Env:RUNNER_TEMP "dgw-linux-$Arch"
New-Item -Path $StageDir -ItemType Directory -Force | Out-Null
Copy-Item -Path $BinaryPath -Destination (Join-Path $StageDir 'devolutions-gateway')
chmod +x (Join-Path $StageDir 'devolutions-gateway')
Copy-Item -Path "./native-libs/linux/${Arch}/libxmf.so" -Destination (Join-Path $StageDir 'libxmf.so')
$ClientTarget = Join-Path $StageDir "webapp" "client"
New-Item -Path $ClientTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "webapp-client/*" -Destination $ClientTarget -Recurse
$PlayerTarget = Join-Path $StageDir "webapp" "player"
New-Item -Path $PlayerTarget -ItemType Directory -Force | Out-Null
Copy-Item -Path "webapp-player/*" -Destination $PlayerTarget -Recurse
$TarPath = "$destinationFolder/devolutions-gateway-${versionFull}-linux-${Arch}.tar.xz"
tar -cJf $TarPath -C $StageDir .
Write-Host "Created devolutions-gateway-${versionFull}-linux-${Arch}.tar.xz"
Remove-Item -Path $StageDir -Recurse -Force
}
shell: pwsh
- name: Attest build provenance
if: ${{ needs.preflight.outputs.skip-publishing == 'false' && !inputs.dry-run }}
uses: actions/attest@v4
with:
subject-path: ${{ steps.prepare.outputs.files-to-upload }}/*
- name: Upload to OneDrive
uses: ./.github/workflows/onedrive-upload
if: ${{ needs.preflight.outputs.skip-publishing == 'false' && !inputs.dry-run }}
with:
azure_client_id: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_ID }}
azure_client_secret: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_SECRET }}
conflict_behavior: fail
destination_path: /Gateway/${{ steps.prepare.outputs.version }}
remote: releases
source_path: ${{ steps.prepare.outputs.files-to-upload }}
onedrive-agent:
name: OneDrive (Devolutions Agent)
environment: onedrive-upload # for OneDrive secrets
if: ${{ needs.preflight.outputs.skip-publishing == 'false' || inputs.dry-run }}
needs: [preflight]
runs-on: ubuntu-latest
permissions:
attestations: write
contents: read
id-token: write
steps:
- name: Check out Devolutions/actions
uses: actions/checkout@v6
with:
repository: Devolutions/actions
ref: v1
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
path: ./.github/workflows
## Devolutions Toolbox is required for OneDrive uploading
- name: Install Devolutions Toolbox
uses: ./.github/workflows/toolbox-install
with:
github_token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
- name: Download artifacts
run: |
gh run download ${{ needs.preflight.outputs.run }} -n devolutions-agent-signed --repo $Env:GITHUB_REPOSITORY
Move-Item devolutions-agent-signed devolutions-agent
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare upload
id: prepare
run: |
$destinationFolder = "${{ runner.temp }}/artifacts"
$version="${{ needs.preflight.outputs.version }}"
# Note that ".0" is appended here (required by release tooling downstream)
$versionFull="$version.0"
Write-Host "Preparing Agent artifacts in $destinationFolder for version $versionFull"
echo "version=${versionFull}" >> $Env:GITHUB_OUTPUT
echo "files-to-upload=$destinationFolder" >> $Env:GITHUB_OUTPUT
New-Item -Path "$destinationFolder" -ItemType "directory"
foreach ($Arch in @('x64', 'arm64')) {
Move-Item -Path "./devolutions-agent/windows/${Arch}/DevolutionsAgent.msi" -Destination "$destinationFolder/DevolutionsAgent-${versionFull}-${Arch}.msi"
Move-Item -Path "./devolutions-agent/windows/${Arch}/DevolutionsAgent.symbols.zip" -Destination "$destinationFolder/DevolutionsAgent-${versionFull}-${Arch}.symbols.zip"
}
foreach ($Arch in @('x64', 'arm64')) {
Move-Item -Path "./devolutions-agent/linux/${Arch}/devolutions-agent.deb" -Destination "$destinationFolder/devolutions-agent-${versionFull}-${Arch}.deb"
Move-Item -Path "./devolutions-agent/linux/${Arch}/devolutions-agent.rpm" -Destination "$destinationFolder/devolutions-agent-${versionFull}-${Arch}.rpm"
Move-Item -Path "./devolutions-agent/linux/${Arch}/devolutions-agent.changes" -Destination "$destinationFolder/devolutions-agent-${versionFull}-${Arch}.changes"
}
shell: pwsh
- name: Attest build provenance
if: ${{ needs.preflight.outputs.skip-publishing == 'false' && !inputs.dry-run }}
uses: actions/attest@v4
with:
subject-path: ${{ steps.prepare.outputs.files-to-upload }}/*
- name: Upload to OneDrive
uses: ./.github/workflows/onedrive-upload
if: ${{ needs.preflight.outputs.skip-publishing == 'false' && !inputs.dry-run }}
with:
azure_client_id: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_ID }}
azure_client_secret: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_SECRET }}
conflict_behavior: fail
destination_path: /Agent/${{ steps.prepare.outputs.version }}
remote: releases
source_path: ${{ steps.prepare.outputs.files-to-upload }}
jetsocat-publish:
name: Jetsocat NuGet publish
environment: publish-prod
if: ${{ needs.preflight.outputs.skip-publishing == 'false' || inputs.dry-run }}
needs: [preflight]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download jetsocat-nuget artifact
run: gh run download ${{ needs.preflight.outputs.run }} -n jetsocat-nuget --repo $Env:GITHUB_REPOSITORY
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: NuGet login (OIDC)
id: nuget-login
uses: NuGet/login@v1
with:
user: ${{ secrets.NUGET_BOT_USERNAME }}
- name: Publish Jetsocat NuGet package
run: |
$Package = Get-ChildItem -Recurse -Filter "Devolutions.Jetsocat.*.nupkg" -File | Select-Object -First 1
Write-Host "Package = $Package"
$PushCmd = @(
'dotnet',
'nuget',
'push',
"$($Package.FullName)",
'--api-key',
'${{ steps.nuget-login.outputs.NUGET_API_KEY }}',
'--source',
'https://api.nuget.org/v3/index.json',
'--skip-duplicate'
)
$PushCmd = $PushCmd -Join ' '
Write-Host $PushCmd
$DryRun = [System.Convert]::ToBoolean('${{ inputs.dry-run }}')
if (-Not $DryRun) {
try {
Invoke-Expression $PushCmd
} catch {
if ($_.Exception.Message -ilike "*package already exists*" -Or $_.Exception.Message -ilike "*already has a package*") {
echo "::warning::Jetsocat NuGet package not published; this version is already listed on NuGet.org"
} else {
Write-Error $_
exit 1
}
}
}
shell: pwsh
remove-labels:
name: Remove release-required labels
if: ${{ needs.preflight.outputs.skip-publishing == 'false' && !inputs.dry-run }}
needs: [preflight, container, github-release, psgallery-release, onedrive-gateway, onedrive-agent, jetsocat-publish]
runs-on: ubuntu-latest
steps:
- name: Check out ${{ github.repository }}
uses: actions/checkout@v6
- name: Remove labels
run: ./ci/remove-labels.ps1 -Label 'release-required'
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
upload-sbom:
name: Upload SBOM
environment: sbom
if: ${{ needs.preflight.outputs.skip-publishing == 'false' && !inputs.dry-run }}
needs: [preflight]
runs-on: ubuntu-latest
steps:
- name: Check out Devolutions/actions
uses: actions/checkout@v6
with:
repository: Devolutions/actions
ref: v1
token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
path: ./.github/workflows
## Devolutions Toolbox is required for OneDrive uploading
- name: Install Devolutions Toolbox
uses: ./.github/workflows/toolbox-install
with:
github_token: ${{ secrets.DEVOLUTIONSBOT_TOKEN }}
- name: Download SBOM artifact
run: gh run download ${{ needs.preflight.outputs.run }} -n sbom --repo $GITHUB_REPOSITORY
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SBOM to OneDrive Releases
uses: ./.github/workflows/onedrive-upload
with:
azure_client_id: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_ID }}
azure_client_secret: ${{ secrets.ONEDRIVE_AUTOMATION_CLIENT_SECRET }}
conflict_behavior: replace
# Append ".0" to match the 4-part version format in OneDrive.
destination_path: /Gateway/${{ needs.preflight.outputs.version }}.0
remote: releases
source_path: bom.json
- name: Upload SBOM to Dependency-Track
uses: ./.github/workflows/dtrack-upload-sbom
with:
api_key: ${{ secrets.DTRACK_AUTOMATION_API_KEY }}
autocreate: 'true'
bom_filename: bom.json
project_name: devolutions-gateway
project_version: ${{ needs.preflight.outputs.version }}
server_hostname: 'dtrack-api.devolutions.com'