Skip to content

fix: avoid empty apple signing env in release #4

fix: avoid empty apple signing env in release

fix: avoid empty apple signing env in release #4

Workflow file for this run

# Auto-generate Tauri updater metadata for GitHub Releases
name: Release with Updater Metadata
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
create-release:
runs-on: ubuntu-latest
outputs:
release_id: ${{ steps.create-release.outputs.result }}
steps:
- name: get version from tag
run: echo "PACKAGE_VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
- name: create or get release
id: create-release
uses: actions/github-script@v7
with:
script: |
const tag = `v${process.env.PACKAGE_VERSION}`;
// Try to find existing release first
try {
const { data } = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: tag,
});
console.log(`Found existing release: ${data.id}`);
return data.id;
} catch (e) {
// Release doesn't exist, create it
const { data } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
name: tag,
body: '⏳ Build in progress...',
generate_release_notes: false,
draft: true,
prerelease: false,
});
console.log(`Created new release: ${data.id}`);
return data.id;
}
build-tauri:
needs: create-release
strategy:
fail-fast: false
matrix:
include:
# macOS - Universal Binary (supports both Intel and Apple Silicon)
- platform: macos-14
args: '--target universal-apple-darwin'
target: 'aarch64-apple-darwin,x86_64-apple-darwin'
# Linux - x64
- platform: ubuntu-22.04
args: ''
target: ''
# Windows - x64
- platform: windows-latest
args: ''
target: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: install just
uses: extractions/setup-just@v2
- name: install pnpm
uses: pnpm/action-setup@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
cache-dependency-path: pnpm-lock.yaml
- name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: verify updater signing secret
shell: bash
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "::error::TAURI_SIGNING_PRIVATE_KEY is required for signed updater artifacts."
echo "::error::Add it in GitHub repository settings before pushing a release tag."
exit 1
fi
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: install macOS universal Rust targets
if: matrix.platform == 'macos-14'
run: rustup target add aarch64-apple-darwin x86_64-apple-darwin
- name: cache Rust dependencies
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
add-rust-environment-hash-key: 'false'
key: ${{ hashFiles('src-tauri/Cargo.toml', 'src-tauri/.cargo/config.toml') }}
cache-workspace-crates: 'true'
cache-on-failure: 'true'
- name: install frontend dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- uses: tauri-apps/tauri-action@v0
id: tauri-build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
# Linux: skip auto-upload so we can post-process the AppImage first
releaseId: ${{ matrix.platform != 'ubuntu-22.04' && needs.create-release.outputs.release_id || '' }}
args: ${{ matrix.args }}
updaterJsonPreferNsis: false
includeUpdaterJson: false
# Create portable Windows zip (no installer required)
- name: Create portable Windows zip
if: matrix.platform == 'windows-latest'
shell: pwsh
run: |
# Derive version from git tag (same source as create-release job)
$version = "${{ github.ref_name }}" -replace '^v', ''
if (-not $version) {
Write-Error "Could not determine version from github.ref_name"
exit 1
}
# Dynamically find the main exe, excluding setup/installer/uninstall binaries
$candidates = @(Get-ChildItem "src-tauri/target/release/*.exe" |
Where-Object { $_.Name -notmatch '(?i)(setup|install|uninstall)' })
if ($candidates.Count -ne 1) {
Write-Error "Expected exactly 1 app exe, found $($candidates.Count): $($candidates.Name -join ', ')"
exit 1
}
$exe = $candidates[0]
Write-Output "Found exe: $($exe.FullName)"
$zipName = "EchoProfile_${version}_x64-portable.zip"
Compress-Archive -Path $exe.FullName -DestinationPath $zipName
if (-not (Test-Path $zipName)) {
Write-Error "Failed to create portable zip"
exit 1
}
Write-Output "Created portable zip: $zipName"
Write-Output "PORTABLE_ZIP_PATH=$zipName" >> $env:GITHUB_ENV
- name: Upload portable Windows zip to release
if: matrix.platform == 'windows-latest'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: pwsh
run: |
gh release upload "${{ github.ref_name }}" `
"${{ env.PORTABLE_ZIP_PATH }}" `
--repo "${{ github.repository }}" `
--clobber
# Fix AppImage EGL crash on Arch Linux and other rolling-release distros.
#
# The AppImage bundles Ubuntu-compiled EGL/Mesa/Wayland libs, but does NOT
# bundle WebKitGPUProcess. The system's WebKitGPUProcess inherits AppRun's
# LD_LIBRARY_PATH, loads the bundled (incompatible) EGL, and crashes with
# EGL_BAD_ALLOC. Removing these GPU-driver-dependent libs forces the system's
# native EGL stack to be used, which is compatible with the system's WebKitGPUProcess.
#
# See: https://github.com/3kyou1/EchoProfile/issues
# See: https://github.com/tauri-apps/tauri/issues/11988
- name: Post-process AppImage (fix EGL crash on Arch Linux)
if: matrix.platform == 'ubuntu-22.04'
run: |
set -euo pipefail
# Find the built AppImage
APPIMAGE=$(find src-tauri/target/release/bundle/appimage -name '*.AppImage' -not -name '*.sig' | head -1)
if [ -z "$APPIMAGE" ]; then
echo "::error::No AppImage found in bundle output"
exit 1
fi
echo "Found AppImage: $APPIMAGE"
# Extract AppImage contents
rm -rf squashfs-root
chmod +x "$APPIMAGE"
"$APPIMAGE" --appimage-extract
# Remove GPU-driver-dependent libs that cause ABI mismatch with host system.
# These Ubuntu-compiled libs conflict with the host's Mesa/GPU drivers when
# the system's WebKitGPUProcess loads them via inherited LD_LIBRARY_PATH.
echo "Removing conflicting EGL/Mesa/Wayland libs..."
pushd squashfs-root/usr/lib/ > /dev/null
rm -fv libEGL.so* libEGL_mesa.so* libGLESv2.so* libgbm.so* \
libwayland-client.so* libwayland-server.so* libwayland-egl.so*
popd > /dev/null
# Inject WEBKIT_DISABLE_DMABUF_RENDERER into GTK apprun hook as defense-in-depth
HOOK_FILE="squashfs-root/apprun-hooks/linuxdeploy-plugin-gtk.sh"
if [ -f "$HOOK_FILE" ]; then
echo '' >> "$HOOK_FILE"
echo '# Fix WebKitGTK EGL crash on rolling-release distros (issue #186)' >> "$HOOK_FILE"
echo 'export WEBKIT_DISABLE_DMABUF_RENDERER=1' >> "$HOOK_FILE"
echo "Injected WEBKIT_DISABLE_DMABUF_RENDERER into apprun hook"
else
echo "::warning::GTK apprun hook not found at $HOOK_FILE"
fi
# Download appimagetool for repackaging (pinned to stable release 1.9.1)
APPIMAGETOOL_SHA256="ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0"
wget -q "https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage"
echo "${APPIMAGETOOL_SHA256} appimagetool-x86_64.AppImage" | sha256sum -c -
chmod +x appimagetool-x86_64.AppImage
# Repackage (APPIMAGE_EXTRACT_AND_RUN bypasses FUSE requirement on CI)
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 ./appimagetool-x86_64.AppImage squashfs-root "$APPIMAGE"
# Cleanup
rm -rf squashfs-root appimagetool-x86_64.AppImage
echo "AppImage post-processing complete: $APPIMAGE"
- name: Sign and upload post-processed Linux artifacts
if: matrix.platform == 'ubuntu-22.04'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
set -euo pipefail
# Find all Linux bundle artifacts
BUNDLE_DIR="src-tauri/target/release/bundle"
RELEASE_TAG="${GITHUB_REF_NAME}"
# Upload AppImage + re-sign
APPIMAGE=$(find "$BUNDLE_DIR/appimage" -name '*.AppImage' -not -name '*.sig' | head -1)
if [ -n "$APPIMAGE" ]; then
# Re-sign for Tauri updater (the post-processed AppImage has a different checksum)
# tauri signer sign auto-creates ${APPIMAGE}.sig on disk — no stdout redirect
pnpm tauri signer sign \
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
--password "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" \
"$APPIMAGE"
[ -s "${APPIMAGE}.sig" ] || { echo "::error::Signature file missing or empty"; exit 1; }
echo "Re-signed: ${APPIMAGE}.sig"
# Upload AppImage and signature to release
gh release upload "$RELEASE_TAG" \
"$APPIMAGE" "${APPIMAGE}.sig" \
--repo "${{ github.repository }}" \
--clobber
echo "Uploaded AppImage to release $RELEASE_TAG"
fi
# Upload deb package (not post-processed, upload as-is)
for DEB in "$BUNDLE_DIR"/deb/*.deb; do
if [ -f "$DEB" ]; then
gh release upload "$RELEASE_TAG" "$DEB" \
--repo "${{ github.repository }}" \
--clobber
echo "Uploaded: $(basename "$DEB")"
fi
done
# Upload rpm package if present
for RPM in "$BUNDLE_DIR"/rpm/*.rpm; do
if [ -f "$RPM" ]; then
gh release upload "$RELEASE_TAG" "$RPM" \
--repo "${{ github.repository }}" \
--clobber
echo "Uploaded: $(basename "$RPM")"
fi
done
generate-updater-metadata:
needs: [create-release, build-tauri]
runs-on: ubuntu-latest
steps:
- name: Generate and upload latest.json
uses: actions/github-script@v7
with:
script: |
const releaseId = ${{ needs.create-release.outputs.release_id }};
// Fetch release metadata
const { data: releaseData } = await github.rest.repos.getRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId
});
const version = releaseData.tag_name.replace(/^v/, '');
const pubDate = new Date().toISOString();
const platforms = {};
// Helper to fetch signature using GitHub API (works for draft releases)
const fetchSignature = async (assetId) => {
try {
const { data } = await github.rest.repos.getReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: assetId,
headers: { accept: 'application/octet-stream' }
});
// data is ArrayBuffer, convert to string
return Buffer.from(data).toString('utf-8');
} catch (e) {
console.log(`Failed to fetch signature for asset ${assetId}:`, e.message);
return '';
}
};
const findAssetPair = (pattern, sigPattern) => {
const asset = releaseData.assets.find(a => a?.name && pattern.test(a.name) && !a.name.endsWith('.sig'));
const sig = releaseData.assets.find(a => a?.name && sigPattern.test(a.name));
return { asset, sig };
};
// Construct public download URL (will be valid after publish)
const getPublicUrl = (filename) =>
`https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${{ github.ref_name }}/${filename}`;
// macOS - Universal Binary (supports both Apple Silicon and Intel)
const macUniversal = findAssetPair(/_universal\.app\.tar\.gz$/, /_universal\.app\.tar\.gz\.sig$/);
if (macUniversal.asset) {
const signature = macUniversal.sig ? await fetchSignature(macUniversal.sig.id) : '';
const url = getPublicUrl(macUniversal.asset.name);
// Both darwin-aarch64 and darwin-x86_64 point to the same universal binary
platforms['darwin-aarch64'] = { signature, url };
platforms['darwin-x86_64'] = { signature, url };
}
// Linux
const linux = findAssetPair(/\.AppImage$/, /\.AppImage\.sig$/);
if (linux.asset) {
const signature = linux.sig ? await fetchSignature(linux.sig.id) : '';
platforms['linux-x86_64'] = { signature, url: getPublicUrl(linux.asset.name) };
}
// Windows
const windows = findAssetPair(/x64-setup\.exe$/, /x64-setup\.exe\.sig$/);
if (windows.asset) {
const signature = windows.sig ? await fetchSignature(windows.sig.id) : '';
platforms['windows-x86_64'] = { signature, url: getPublicUrl(windows.asset.name) };
}
const latestJson = {
version,
notes: `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/tag/${{ github.ref_name }}`,
pub_date: pubDate,
platforms
};
console.log('📄 latest.json:', JSON.stringify(latestJson, null, 2));
// Delete the existing latest.json asset
const existing = releaseData.assets.find(a => a.name === 'latest.json');
if (existing) {
await github.rest.repos.deleteReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: existing.id
});
}
// Upload latest.json
const uploadUrl = releaseData.upload_url.replace('{?name,label}', '?name=latest.json');
await github.request({
method: 'POST',
url: uploadUrl,
headers: { 'content-type': 'application/json' },
data: JSON.stringify(latestJson, null, 2)
});
- name: Publish release
uses: actions/github-script@v7
with:
script: |
const releaseId = ${{ needs.create-release.outputs.release_id }};
const { data: generatedNotes } = await github.rest.repos.generateReleaseNotes({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: '${{ github.ref_name }}',
});
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
body: generatedNotes.body,
draft: false,
});
console.log('🎉 Release published!');