Skip to content

release: v0.5.7

release: v0.5.7 #42

Workflow file for this run

name: release
# Tag-triggered release. Push a semver tag to cut a release:
# git tag v0.1.0 && git push origin v0.1.0
#
# The `build` matrix runs the Tauri bundler on each host OS to produce a
# platform-native INSTALLER (not a raw binary):
# - macOS -> .dmg
# - Linux -> .deb + .AppImage
# - Windows -> NSIS .exe setup
# Each installer is uploaded as an artifact. The single `release` job then
# gathers all artifacts and publishes ONE draft GitHub Release — building the
# release in one job avoids the create-race you'd hit if three matrix runners
# each tried to create it.
#
# macOS signing: the build step is wired for Developer ID signing +
# notarization, driven by repo secrets (APPLE_CERTIFICATE, APPLE_SIGNING_IDENTITY,
# APPLE_ID, ... — see the "macOS signing" section of the release-runbook for how
# to generate them). When those secrets are unset the .dmg builds UNSIGNED and
# macOS Gatekeeper shows "is damaged" on first launch — provision the secrets to
# get a clean launch. Windows installers remain UNSIGNED (SmartScreen warns).
#
# GUI-first: the unified `git-sync` binary lives INSIDE the bundle
# (Sync.app/Contents/MacOS/git-sync, Program Files, /usr/bin/Sync). The CLI /
# MCP server is reached by extracting or symlinking it onto PATH — see the
# "CLI from an installer" section of the release-runbook.
on:
push:
tags: ["v*"]
# Least privilege: only the publishing job needs write.
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
build:
name: build / ${{ matrix.target }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
target: aarch64-apple-darwin
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- os: windows-latest
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Reclaim runner disk space
if: runner.os == 'Linux'
run: |
# The release build of this dependency tree (datafusion + arrow +
# lance + llama.cpp) sits close to the runner's ~21 GB of free
# space. Drop preinstalled toolchains we never use to reclaim ~20 GB.
sudo rm -rf \
/usr/local/lib/android \
/usr/share/dotnet \
/opt/ghc \
/opt/hostedtoolcache/CodeQL \
/usr/local/share/boost \
/usr/local/graalvm
sudo docker image prune --all --force || true
df -h /
- name: Install Linux Tauri prerequisites
if: runner.os == 'Linux'
run: |
sudo apt-get update
# patchelf + file are required by the AppImage bundler (on top of
# the webkit/gtk deps the build itself links).
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libssl-dev \
patchelf \
file
- uses: actions/setup-node@v4
with:
node-version: "22.12.0"
cache: "npm"
cache-dependency-path: web/package-lock.json
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build frontend
# The bundler embeds web/dist. We build it explicitly here and disable
# the config's beforeBuildCommand (via tauri.ci.conf.json below) so the
# bundler doesn't try to re-run it — the in-repo beforeBuildCommand
# path is relative to a dev cwd and isn't reliable in CI.
working-directory: web
run: |
npm ci --ignore-scripts
npm run build
- name: Build installer (tauri bundle)
uses: tauri-apps/tauri-action@v0
env:
# macOS code signing + notarization. tauri-action imports the cert
# into a throwaway keychain and notarizes the .dmg when these are set.
# When the secrets are absent (forks, or before an Apple Developer ID
# cert is provisioned) the values are empty strings and the bundler
# falls back to an UNSIGNED build — the release still succeeds, users
# just hit the Gatekeeper "damaged" warning. A clean first launch
# needs ALL THREE signing vars AND one notarization method below.
# Non-macOS runners ignore these. See release-runbook "macOS signing".
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
# Notarization via Apple ID + app-specific password (APPLE_TEAM_ID
# disambiguates if the Apple ID belongs to multiple teams).
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Updater artifact signing (s-auto-update): a minisign keypair,
# separate from the Apple cert. The private key signs the
# .app.tar.gz / .AppImage / -setup.exe so installed apps verify
# downloads against the pubkey baked into tauri.conf.json. The
# password var is intentionally set even when empty — an unset var
# makes the bundler prompt interactively and hang the runner.
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
# Non-root Tauri crate -> point at it explicitly. The layered
# tauri.ci.conf.json (resolved relative to projectPath) empties
# beforeBuildCommand since the frontend is already built above.
# tauri.updater.conf.json (createUpdaterArtifacts) is layered on
# ONLY when the signing key is provisioned: with it on and no key
# the bundler hard-fails, which would break forks — without it the
# build just skips the updater artifacts and stays green.
projectPath: crates/sync-app
args: --target ${{ matrix.target }} --config tauri.ci.conf.json ${{ secrets.TAURI_SIGNING_PRIVATE_KEY != '' && '--config tauri.updater.conf.json' || '' }}
- name: Collect installers
shell: bash
run: |
set -euo pipefail
mkdir -p dist
# Cargo workspaces share one target dir at the repo root.
base="target/${{ matrix.target }}/release/bundle"
shopt -s nullglob
# Copy each installer to a STABLE, version-less filename. The README
# links to github.com/.../releases/latest/download/<stable-name>, which
# GitHub resolves to the newest release — so those links are evergreen
# and never need editing on a new release. The version is carried by
# the release tag/title, not the filename. Keep these names in sync
# with the download links in README.md.
stable_copy() { # <src-glob> <stable-name>
local src
for src in $1; do
cp "$src" "dist/$2"
done
}
stable_copy "$base/dmg/*.dmg" "Sync_macOS_aarch64.dmg"
stable_copy "$base/appimage/*.AppImage" "Sync_Linux_x86_64.AppImage"
stable_copy "$base/deb/*.deb" "Sync_Linux_x86_64.deb"
stable_copy "$base/nsis/*-setup.exe" "Sync_Windows_x64-setup.exe"
# Updater artifacts (s-auto-update): the signed packages the
# in-app updater downloads, plus their minisign signatures.
# `scripts/release.sh --publish-manifest` later reads the .sig
# assets off the GitHub release to assemble updater/latest.json.
# Absent on unsigned (fork) builds — nullglob just skips them.
# macOS updates ship as .app.tar.gz; Linux re-uses the AppImage
# and Windows the NSIS setup copied above, so only their .sig
# files are added here.
stable_copy "$base/macos/*.app.tar.gz" "Sync_macOS_aarch64.app.tar.gz"
stable_copy "$base/macos/*.app.tar.gz.sig" "Sync_macOS_aarch64.app.tar.gz.sig"
stable_copy "$base/appimage/*.AppImage.sig" "Sync_Linux_x86_64.AppImage.sig"
stable_copy "$base/nsis/*-setup.exe.sig" "Sync_Windows_x64-setup.exe.sig"
ls -la dist
# Only the wrapped installers are shipped — the loose .app / unpacked
# dirs are intentionally not uploaded. Fail loudly if nothing matched.
[ -n "$(ls -A dist)" ] || { echo "::error::no installers produced for ${{ matrix.target }}"; exit 1; }
- name: Upload installer artifact
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.target }}
path: dist/*
if-no-files-found: error
retention-days: 7
release:
name: publish github release
needs: build
runs-on: ubuntu-24.04
permissions:
contents: write # create the release + upload assets
steps:
- name: Gather all built installers
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true # flatten every release-* artifact into dist/
- name: Publish release
uses: softprops/action-gh-release@v2
with:
files: dist/*
fail_on_unmatched_files: true
generate_release_notes: true
# Created as a draft: review the attached installers + notes, then
# click "Publish" in the GitHub UI. Set to false for fully
# hands-off publishing the moment the tag is pushed.
draft: false