Release #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| platform: | |
| description: 'Platform to build' | |
| required: false | |
| default: 'all' | |
| type: choice | |
| options: | |
| - all | |
| - macos | |
| - linux | |
| - windows | |
| linux-runner: | |
| description: 'Linux runner label (custom image or vanilla fallback)' | |
| required: false | |
| default: 'blacksmith-4vcpu-ubuntu-2404' | |
| type: choice | |
| options: | |
| - blacksmith-4vcpu-ubuntu-2404 | |
| - blacksmith-4vcpu-ubuntu-2404-mt | |
| release: | |
| types: [published] | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| build-macos: | |
| name: Build macOS (ARM64) | |
| if: > | |
| github.event_name == 'release' || | |
| inputs.platform == 'all' || inputs.platform == 'macos' || inputs.platform == '' | |
| runs-on: [macOS, ARM64] | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Tauri build environment | |
| uses: ./.github/actions/setup-tauri-build | |
| - name: Import Apple certificate | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| run: | | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" ci_signing.keychain | |
| security default-keychain -s ci_signing.keychain | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" ci_signing.keychain | |
| security set-keychain-settings -t 3600 -u ci_signing.keychain | |
| security list-keychains -d user -s ci_signing.keychain login.keychain | |
| security import certificate.p12 -k ci_signing.keychain \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PASSWORD" ci_signing.keychain | |
| security find-identity -v -p codesigning ci_signing.keychain | |
| rm certificate.p12 | |
| - name: Decode App Store Connect API key | |
| env: | |
| APPLE_API_KEY_B64: ${{ secrets.APPLE_API_KEY_B64 }} | |
| run: | | |
| echo "$APPLE_API_KEY_B64" | base64 --decode > /tmp/auth_key.p8 | |
| - name: Build binary | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| LASTFM_API_KEY: ${{ secrets.LASTFM_API_KEY }} | |
| LASTFM_API_SECRET: ${{ secrets.LASTFM_API_SECRET }} | |
| run: task ci:build TARGET=aarch64-apple-darwin | |
| - name: Bundle and sign | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| run: task ci:bundle TARGET=aarch64-apple-darwin | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: macos-arm64 | |
| path: | | |
| target/aarch64-apple-darwin/release/bundle/dmg/*.dmg | |
| target/aarch64-apple-darwin/release/bundle/macos/*.app | |
| retention-days: 7 | |
| - name: Notarize | |
| env: | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| run: task ci:notarize TARGET=aarch64-apple-darwin | |
| - name: Upload release assets | |
| if: github.event_name == 'release' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release upload "${{ github.event.release.tag_name }}" \ | |
| target/aarch64-apple-darwin/release/bundle/dmg/*.dmg | |
| - name: Cleanup CI keychain | |
| if: always() | |
| run: | | |
| security default-keychain -s login.keychain-db | |
| security delete-keychain ci_signing.keychain || true | |
| rm -f /tmp/auth_key.p8 | |
| build-linux-amd64: | |
| name: Build Linux (amd64) | |
| if: > | |
| github.event_name == 'release' || | |
| inputs.platform == 'all' || inputs.platform == 'linux' || inputs.platform == '' | |
| runs-on: ${{ inputs.linux-runner || 'blacksmith-4vcpu-ubuntu-2404' }} | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Docker builder | |
| uses: useblacksmith/setup-docker-builder@v1 | |
| - name: Build and bundle via Docker | |
| uses: useblacksmith/build-push-action@v2 | |
| with: | |
| context: . | |
| file: docker/linux/Dockerfile | |
| target: artifacts | |
| push: false | |
| outputs: type=local,dest=dist | |
| build-args: | | |
| LASTFM_API_KEY=${{ secrets.LASTFM_API_KEY }} | |
| LASTFM_API_SECRET=${{ secrets.LASTFM_API_SECRET }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: linux-amd64 | |
| path: dist/*.deb | |
| retention-days: 7 | |
| - name: Upload release assets | |
| if: github.event_name == 'release' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release upload "${{ github.event.release.tag_name }}" \ | |
| dist/*.deb | |
| build-windows: | |
| name: Build Windows (x64) | |
| if: > | |
| github.event_name == 'release' || | |
| inputs.platform == 'all' || inputs.platform == 'windows' || inputs.platform == '' | |
| runs-on: blacksmith-4vcpu-windows-2025 | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Tauri build environment | |
| uses: ./.github/actions/setup-tauri-build | |
| - name: Install Windows SDK (signtool) | |
| shell: pwsh | |
| run: | | |
| # Find existing signtool first; install SDK only if missing | |
| $signtool = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin" ` | |
| -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue ` | |
| | Where-Object { $_.FullName -match '\\x64\\' } ` | |
| | Sort-Object { [version]($_.FullName -replace '.*\\(\d+\.\d+\.\d+\.\d+)\\.*','$1') } ` | |
| | Select-Object -Last 1 -ExpandProperty FullName | |
| if (-not $signtool) { | |
| Write-Host "signtool not found, installing Windows SDK via winget..." | |
| winget install --id Microsoft.WindowsSDK.10.0.26100 --silent --accept-source-agreements --accept-package-agreements | |
| $signtool = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin" ` | |
| -Recurse -Filter signtool.exe | Where-Object { $_.FullName -match '\\x64\\' } ` | |
| | Sort-Object { [version]($_.FullName -replace '.*\\(\d+\.\d+\.\d+\.\d+)\\.*','$1') } ` | |
| | Select-Object -Last 1 -ExpandProperty FullName | |
| } | |
| if (-not $signtool) { throw "signtool.exe not found after SDK install" } | |
| Write-Host "Using signtool: $signtool" | |
| echo "SIGNTOOL_PATH=$signtool" >> $env:GITHUB_ENV | |
| - name: Generate self-signed code signing certificate | |
| shell: pwsh | |
| env: | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| $password = ConvertTo-SecureString -String $env:WINDOWS_CERT_PASSWORD -Force -AsPlainText | |
| $cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject 'CN=MT' ` | |
| -CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(5) | |
| Export-PfxCertificate -Cert $cert -FilePath "${{ runner.temp }}\mt-cert.pfx" -Password $password | |
| echo "CERT_PATH=${{ runner.temp }}\mt-cert.pfx" >> $env:GITHUB_ENV | |
| - name: Write signing config override | |
| shell: pwsh | |
| env: | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| $signScript = Join-Path "${{ runner.temp }}" 'sign.cmd' | |
| @" | |
| @echo off | |
| "$env:SIGNTOOL_PATH" sign /f "$env:CERT_PATH" /p "$env:WINDOWS_CERT_PASSWORD" /t http://timestamp.digicert.com /fd SHA256 %1 | |
| "@ | Out-File -FilePath $signScript -Encoding ascii | |
| $config = @{ | |
| build = @{ beforeBuildCommand = "" } | |
| bundle = @{ windows = @{ signCommand = @{ cmd = "cmd"; args = @("/C", $signScript, "%1") } } } | |
| } | ConvertTo-Json -Depth 5 | |
| $configPath = "${{ runner.temp }}\sign-override.json" | |
| $config | Out-File -FilePath $configPath -Encoding utf8 | |
| # Use forward slashes so Task template expansion doesn't eat backslashes | |
| echo "SIGN_CONFIG_PATH=$($configPath -replace '\\','/')" >> $env:GITHUB_ENV | |
| - name: Build binary | |
| shell: pwsh | |
| env: | |
| LASTFM_API_KEY: ${{ secrets.LASTFM_API_KEY }} | |
| LASTFM_API_SECRET: ${{ secrets.LASTFM_API_SECRET }} | |
| run: task ci:build TARGET=x86_64-pc-windows-msvc CONFIG=${{ env.SIGN_CONFIG_PATH }} | |
| - name: Bundle NSIS installer | |
| shell: pwsh | |
| run: task ci:bundle TARGET=x86_64-pc-windows-msvc BUNDLES=nsis CONFIG=${{ env.SIGN_CONFIG_PATH }} | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: windows-x64 | |
| path: target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe | |
| retention-days: 7 | |
| - name: Upload release assets | |
| if: github.event_name == 'release' | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh release upload "${{ github.event.release.tag_name }}" (Get-Item target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe) | |
| - name: Cleanup certificate | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| Remove-Item -Force "${{ runner.temp }}\mt-cert.pfx" -ErrorAction SilentlyContinue | |
| Remove-Item -Force "${{ runner.temp }}\sign-override.json" -ErrorAction SilentlyContinue | |
| Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Where-Object { $_.Subject -eq 'CN=MT' } | Remove-Item | |
| update-readme: | |
| name: Update README download links | |
| needs: [build-macos, build-linux-amd64, build-windows] | |
| if: github.event_name == 'release' | |
| runs-on: blacksmith-4vcpu-ubuntu-2404 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: main | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Update download table | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ github.event.release.tag_name }} | |
| run: | | |
| python3 << 'EOF' | |
| import json, os, re, subprocess | |
| tag = os.environ['TAG'] | |
| result = subprocess.run( | |
| ['gh', 'api', f'repos/pythoninthegrass/mt/releases/tags/{tag}'], | |
| capture_output=True, text=True, check=True | |
| ) | |
| assets = json.loads(result.stdout)['assets'] | |
| PLATFORM_MAP = [ | |
| ('.dmg', 'macOS', 'Apple Silicon (ARM64)'), | |
| ('amd64', 'Linux', 'x86_64 (amd64)'), | |
| ('.exe', 'Windows', 'x64'), | |
| ] | |
| rows = [] | |
| for asset in sorted(assets, key=lambda a: a['name']): | |
| name = asset['name'] | |
| url = asset['browser_download_url'] | |
| for key, platform, arch in PLATFORM_MAP: | |
| if key in name: | |
| rows.append(f'| {platform} | {arch} | [{name}]({url}) |') | |
| break | |
| table = '\n'.join([ | |
| '| Platform | Architecture | Download |', | |
| '|----------|--------------|----------|', | |
| *rows, | |
| ]) | |
| section = f'<!-- DOWNLOADS:START -->\n{table}\n<!-- DOWNLOADS:END -->' | |
| readme = open('README.md').read() | |
| updated = re.sub( | |
| r'<!-- DOWNLOADS:START -->.*?<!-- DOWNLOADS:END -->', | |
| section, readme, flags=re.DOTALL | |
| ) | |
| open('README.md', 'w').write(updated) | |
| print(f"Updated README with {len(rows)} download link(s)") | |
| EOF | |
| - name: Commit and push | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add README.md | |
| git diff --staged --quiet || git commit -m "chore: update download links for ${{ github.event.release.tag_name }} [skip ci]" | |
| git push origin main |