name: Release on: workflow_dispatch: inputs: version: description: "Version to release, e.g. 1.1.0 (no leading v)" required: true type: string dry_run: description: "Dry run: build & compute everything, push/publish nothing" required: true type: boolean default: true permissions: contents: read jobs: prepare: runs-on: ubuntu-latest outputs: version: ${{ inputs.version }} prepared_ref: ${{ steps.prepared-ref.outputs.ref }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ secrets.RELEASE_PAT }} persist-credentials: true fetch-depth: 0 fetch-tags: true - name: Validate version env: VERSION: ${{ inputs.version }} run: | if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then echo "invalid release version: $VERSION (expected MAJOR.MINOR.PATCH)" >&2 exit 2 fi - name: Configure git identity run: | git config user.name "veeso" git config user.email "christian.visintin@veeso.dev" - name: Install git-cliff uses: taiki-e/install-action@e67fa11c4b9316fa714ddf0abed07a0c3143b95b # v2.87.4 with: tool: git-cliff,dprint - name: Install just uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - name: Bump version env: VERSION: ${{ inputs.version }} run: dist/release/bump_version.sh "$VERSION" "$(date +%F)" - name: Generate CHANGELOG env: VERSION: ${{ inputs.version }} run: just changelog "$VERSION" - name: Generate release notes env: VERSION: ${{ inputs.version }} run: git-cliff --unreleased --tag "v$VERSION" --strip header -o RELEASE_NOTES.md - name: Verify Cargo.lock is unchanged run: git diff --exit-code -- Cargo.lock - name: Upload release notes uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-notes path: RELEASE_NOTES.md retention-days: 1 if-no-files-found: error - name: Show diff (dry run) if: ${{ inputs.dry_run }} run: git --no-pager diff - name: Commit & push version bump if: ${{ !inputs.dry_run }} env: VERSION: ${{ inputs.version }} run: | rm -f RELEASE_NOTES.md git add -A -- . ':!Cargo.lock' git diff --cached --exit-code -- Cargo.lock git commit -m "chore: release v$VERSION" git push origin HEAD:main - name: Export prepared ref id: prepared-ref env: DRY_RUN: ${{ inputs.dry_run }} run: | if [ "$DRY_RUN" = "true" ]; then echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT" else echo "ref=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" fi build: needs: prepare name: build-${{ matrix.target }} strategy: fail-fast: false matrix: include: - target: x86_64-unknown-linux-musl os: ubuntu-24.04 kind: linux - target: aarch64-unknown-linux-musl os: ubuntu-24.04-arm kind: linux - target: aarch64-apple-darwin os: macos-latest kind: macos features: "--features smb-vendored" - target: x86_64-apple-darwin os: macos-latest kind: macos features: "--no-default-features --features keyring" - target: x86_64-pc-windows-msvc os: windows-latest kind: windows - target: aarch64-pc-windows-msvc os: windows-11-arm kind: windows runs-on: ${{ matrix.os }} env: VERSION: ${{ needs.prepare.outputs.version }} TARGET: ${{ matrix.target }} FEATURES: ${{ matrix.features }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.prepare.outputs.prepared_ref }} persist-credentials: false fetch-depth: 0 fetch-tags: true - name: Prepare release version shell: bash run: | dist/release/bump_version.sh "$VERSION" "$(date +%F)" cargo update --package termscp --precise "$VERSION" cargo metadata --locked --no-deps --format-version 1 > /dev/null - name: Install just uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - name: Install Rust target if: matrix.kind == 'macos' run: rustup target add "$TARGET" - name: Install Rust target if: matrix.kind == 'windows' run: rustup target add "$env:TARGET" # ---- Linux: static musl build in a pinned Alpine container ---- - name: Install cargo-deb (Linux) if: matrix.kind == 'linux' run: cargo install cargo-deb --locked - name: Build (Linux) if: matrix.kind == 'linux' run: just build_musl "$TARGET" - name: Build deb (Linux) if: matrix.kind == 'linux' run: cargo deb --locked --no-build --target "$TARGET" --features smb-vendored - name: Verify deb declares no runtime dependencies (Linux) if: matrix.kind == 'linux' run: | deb=$(ls target/"$TARGET"/debian/*.deb) depends=$(dpkg-deb -f "$deb" Depends) if [ -n "$depends" ]; then echo "static deb must have no Depends, got: $depends" >&2 exit 1 fi # ---- macOS ---- - name: Install deps (macOS) if: matrix.kind == 'macos' run: | brew update brew install bison cpanminus cups flex gettext gmp gnutls icu4c jansson \ libarchive libbsd libunistring libgit2 libtirpc openldap pkg-config zlib for p in bison cups flex gettext gmp gnutls icu4c jansson libarchive \ libbsd libgit2 libtirpc libunistring openldap zlib; do brew link --force "$p"; done cpanm Parse::Yapp::Driver - name: Build (macOS) if: matrix.kind == 'macos' run: just build_release "$TARGET" "$FEATURES" # ---- Windows ---- - name: Build (Windows) if: matrix.kind == 'windows' run: just build_release "$env:TARGET" "--features smb-vendored" # ---- Package posix (tar.gz) ---- - name: Package (posix) if: matrix.kind != 'windows' run: | mkdir -p artifact cp "target/$TARGET/release/termscp" artifact/termscp tar -czf "artifact/termscp-v$VERSION-$TARGET.tar.gz" -C artifact termscp shasum -a 256 "artifact/termscp-v$VERSION-$TARGET.tar.gz" | awk '{print $1}' > "artifact/$TARGET.sha256" # ---- Package windows (zip) ---- - name: Package (windows) if: matrix.kind == 'windows' shell: pwsh run: | New-Item -ItemType Directory -Force artifact | Out-Null Copy-Item "target/$env:TARGET/release/termscp.exe" artifact/termscp.exe Compress-Archive -Path artifact/termscp.exe -DestinationPath "artifact/termscp-v$env:VERSION-$env:TARGET.zip" (Get-FileHash "artifact/termscp-v$env:VERSION-$env:TARGET.zip" -Algorithm SHA256).Hash.ToLower() | Out-File -NoNewline "artifact/$env:TARGET.sha256" - name: Move deb into artifact dir (Linux) if: matrix.kind == 'linux' run: cp target/"$TARGET"/debian/*.deb artifact/ - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: build-${{ matrix.target }} path: artifact/* retention-days: 1 if-no-files-found: error publish-homebrew: needs: [prepare, build, release] runs-on: ubuntu-latest env: VERSION: ${{ needs.prepare.outputs.version }} steps: - name: Download build artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: build-* path: dl merge-multiple: true - name: Checkout homebrew tap uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: veeso/homebrew-termscp token: ${{ secrets.RELEASE_PAT }} path: tap persist-credentials: true - name: Rewrite formula run: | set -euo pipefail cd "$GITHUB_WORKSPACE" SHA_MAC_ARM=$(cat dl/aarch64-apple-darwin.sha256) SHA_MAC_X64=$(cat dl/x86_64-apple-darwin.sha256) SHA_LIN_ARM=$(cat dl/aarch64-unknown-linux-musl.sha256) SHA_LIN_X64=$(cat dl/x86_64-unknown-linux-musl.sha256) BASE="https://github.com/veeso/termscp/releases/latest/download" cat > tap/Formula/termscp.rb < /dev/null - name: Download build artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: build-* path: dl merge-multiple: true - name: Download release notes uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release-notes path: notes - name: Patch chocolatey checksums & pack run: | set -euo pipefail SHA_WIN_X64=$(cat dl/x86_64-pc-windows-msvc.sha256) SHA_WIN_ARM=$(cat dl/aarch64-pc-windows-msvc.sha256) PS=dist/chocolatey/tools/chocolateyinstall.ps1 # arm checksum is the first $checksum line, x64 the second (matches file order) SHA_WIN_ARM="$SHA_WIN_ARM" SHA_WIN_X64="$SHA_WIN_X64" \ perl -0pi -e 'BEGIN{our $n=0} s/(\$checksum\s*=\s*'"'"')[0-9a-f]*('"'"')/ $n++==0 ? "${1}$ENV{SHA_WIN_ARM}${2}" : "${1}$ENV{SHA_WIN_X64}${2}" /ge' "$PS" docker run --rm -v "$PWD/dist/chocolatey:/work" -w /work \ chocolatey/choco:latest choco pack --output-directory /work - name: Assemble release assets run: | mkdir -p out cp dl/*.tar.gz dl/*.deb dl/*.zip out/ 2>/dev/null || true cp dist/chocolatey/*.nupkg out/ - name: Upload assets artifact (dry run) if: ${{ inputs.dry_run }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-assets-dryrun path: out/* retention-days: 3 - name: Create GitHub release if: ${{ !inputs.dry_run }} env: GH_TOKEN: ${{ secrets.RELEASE_PAT }} run: | gh release create "v$VERSION" out/* \ --title "v$VERSION" \ --notes-file notes/RELEASE_NOTES.md publish-crate: needs: [prepare, release] if: ${{ !inputs.dry_run }} runs-on: ubuntu-latest permissions: contents: read id-token: write env: VERSION: ${{ needs.prepare.outputs.version }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.prepare.outputs.prepared_ref }} persist-credentials: false fetch-depth: 0 fetch-tags: true - name: Prepare release version run: | dist/release/bump_version.sh "$VERSION" "$(date +%F)" cargo update --package termscp --precise "$VERSION" cargo metadata --locked --no-deps --format-version 1 > /dev/null - name: Install just uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - name: Install dependencies (Linux) run: | sudo apt-get update sudo apt-get install -y \ make \ libgit2-dev \ build-essential \ pkg-config \ libbsd-dev \ libcap-dev \ libcups2-dev \ libgnutls28-dev \ libicu-dev \ libjansson-dev \ libkeyutils-dev \ libldap2-dev \ zlib1g-dev \ libpam0g-dev \ libacl1-dev \ libarchive-dev \ flex \ bison \ libntirpc-dev \ libtracker-sparql-3.0-dev \ libglib2.0-dev \ libdbus-1-dev \ libsasl2-dev \ libunistring-dev \ cpanminus sudo cpanm Parse::Yapp::Driver - name: Authenticate to crates.io id: auth uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 - name: Publish to crates.io env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} run: just publish_crate publish-choco: needs: [prepare, release] if: ${{ !inputs.dry_run }} runs-on: windows-latest env: VERSION: ${{ needs.prepare.outputs.version }} steps: - name: Download nupkg from release env: GH_TOKEN: ${{ secrets.RELEASE_PAT }} run: gh release download "v$env:VERSION" --repo veeso/termscp --pattern "*.nupkg" - name: Push to Chocolatey env: CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }} run: | choco apikey --key $env:CHOCO_API_KEY --source https://push.chocolatey.org/ choco push (Get-ChildItem *.nupkg).Name --source https://push.chocolatey.org/