Skip to content

Release

Release #38

Workflow file for this run

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