diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4303ba6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,125 @@ +name: release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Release tag to publish, for example v1.2.23" + required: true + type: string + +permissions: + contents: write + +jobs: + windows: + name: Publish Windows release + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "8.0.x" + + - name: Resolve release metadata + id: meta + shell: pwsh + run: | + $tag = "${{ github.ref_name }}" + if ("${{ github.event_name }}" -eq "workflow_dispatch") { + $tag = "${{ inputs.tag }}" + } + + if ($tag -notmatch '^v\d+\.\d+\.\d+$') { + throw "Release tag must use vMAJOR.MINOR.PATCH format. Received: $tag" + } + + [xml]$project = Get-Content "AppTunnel/AppTunnel.csproj" + $projectVersion = $project.Project.PropertyGroup.Version + $tagVersion = $tag.TrimStart("v") + + if ($projectVersion -ne $tagVersion) { + throw "Tag version ($tagVersion) does not match AppTunnel.csproj Version ($projectVersion)." + } + + $artifactName = "TunnelX-$tag-standalone-compressed.exe" + + "tag=$tag" >> $env:GITHUB_OUTPUT + "version=$tagVersion" >> $env:GITHUB_OUTPUT + "artifact_name=$artifactName" >> $env:GITHUB_OUTPUT + + - name: Restore + run: dotnet restore AppTunnel.sln + + - name: Build + run: dotnet build AppTunnel.sln -c Release --no-restore + + - name: Publish standalone executable + run: > + dotnet publish AppTunnel\AppTunnel.csproj + -c Release + -r win-x64 + --self-contained true + -p:PublishSingleFile=true + -p:EnableCompressionInSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -p:DebugType=None + -p:DebugSymbols=false + -o publish\TunnelX + + - name: Package release asset + id: package + shell: pwsh + run: | + $source = "publish/TunnelX/TunnelX.exe" + $asset = "publish/${{ steps.meta.outputs.artifact_name }}" + $checksum = "$asset.sha256" + + if (-not (Test-Path $source)) { + throw "Published executable was not found at $source" + } + + Move-Item -LiteralPath $source -Destination $asset + $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $asset).Hash.ToLowerInvariant() + "$hash ${{ steps.meta.outputs.artifact_name }}" | Set-Content -Encoding ASCII -LiteralPath $checksum + + "asset=$asset" >> $env:GITHUB_OUTPUT + "checksum=$checksum" >> $env:GITHUB_OUTPUT + + - name: Upload workflow artifact + uses: actions/upload-artifact@v4 + with: + name: TunnelX-${{ steps.meta.outputs.tag }}-win-x64 + path: | + ${{ steps.package.outputs.asset }} + ${{ steps.package.outputs.checksum }} + if-no-files-found: error + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + shell: pwsh + run: | + $tag = "${{ steps.meta.outputs.tag }}" + + if ("${{ github.event_name }}" -eq "workflow_dispatch") { + git fetch --tags origin + if (-not (git tag --list $tag)) { + git tag $tag + git push origin $tag + } + } + + gh release create $tag ` + "${{ steps.package.outputs.asset }}" ` + "${{ steps.package.outputs.checksum }}" ` + --title "TunnelX $tag" ` + --generate-notes ` + --latest diff --git a/docs/BUILD.md b/docs/BUILD.md index f91c39e..7627d79 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -24,6 +24,27 @@ Rename the final executable with the app version: TunnelX-v1.2.23-standalone-compressed.exe ``` +## GitHub Actions Release + +Public releases are published by `.github/workflows/release.yml`. + +The release workflow: + +- runs on `windows-latest` with .NET 8; +- accepts tags in `vMAJOR.MINOR.PATCH` format, such as `v1.2.23`; +- verifies that the tag version matches `` in `AppTunnel/AppTunnel.csproj`; +- builds and publishes the `win-x64` self-contained single-file executable; +- attaches `TunnelX-vX.Y.Z-standalone-compressed.exe` and a `.sha256` checksum to the GitHub Release. + +To publish a release from the command line: + +```powershell +git tag v1.2.23 +git push origin v1.2.23 +``` + +The same workflow can also be run manually from GitHub Actions by providing the release tag. + ## 32-bit Windows 32-bit Windows builds are not supported at this time. Supporting `win-x86` would require a separate compatibility pass, x86-compatible native binaries for every bundled network component, and separate testing for WinDivert/Wintun, Xray/sing-box, packet interception, route management, and the standalone extraction path.