mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-01 15:36:08 +00:00
196 lines
8.3 KiB
YAML
196 lines
8.3 KiB
YAML
# Desktop release builds — macOS (.dmg, arm64 + Intel) and Windows (.msi + NSIS .exe).
|
|
#
|
|
# CI calls the SAME scripts developers run locally (packaging/build_dmg.sh and
|
|
# build_windows.ps1); this file only provisions the toolchain (Node, Rust, a Python venv at
|
|
# .venv with PyInstaller) and publishes the results.
|
|
#
|
|
# Triggers:
|
|
# - tag push `v*` → builds all targets and attaches them to a DRAFT GitHub Release
|
|
# (review, then publish by hand).
|
|
# - manual run → builds and uploads workflow artifacts only (no release).
|
|
#
|
|
# Each installer is uploaded twice: once with its versioned name (archive) and once with a
|
|
# stable name (OpenWorker-macos-arm64.dmg, …) so the website can link to
|
|
# github.com/<repo>/releases/latest/download/<stable-name>
|
|
# and never need updating.
|
|
#
|
|
# macOS signing + notarization: Tauri's bundler handles both during `tauri build` when the
|
|
# APPLE_* env vars are present (import cert → sign app + sidecar with hardened runtime →
|
|
# notarize via notarytool → staple). Driven by repo secrets:
|
|
# APPLE_CERTIFICATE base64 .p12 (Developer ID Application cert + key)
|
|
# APPLE_CERTIFICATE_PASSWORD the .p12 export password
|
|
# APPLE_SIGNING_IDENTITY e.g. "Developer ID Application: Name (TEAMID)"
|
|
# APPLE_API_KEY_CONTENT base64 App Store Connect API .p8 (notarytool)
|
|
# APPLE_API_KEY the API key id
|
|
# APPLE_API_ISSUER the API issuer id
|
|
# When the secrets are absent (forks, scratch runs) the build degrades to unsigned —
|
|
# installable via `xattr -cr`. Windows remains unsigned (Authenticode is a later step).
|
|
|
|
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags: ["v*", "app-v*"]
|
|
workflow_dispatch:
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
build:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: macos-latest # Apple Silicon
|
|
slug: macos-arm64
|
|
# Intel macOS: macos-13 retired in Dec 2025; macos-15-intel replaced it and is
|
|
# the LAST x86_64 image Actions will offer (available until Aug 2027). Builds
|
|
# natively — the sidecar is a PyInstaller freeze, which cannot cross-compile.
|
|
- os: macos-15-intel
|
|
slug: macos-x64
|
|
- os: windows-latest
|
|
slug: windows
|
|
runs-on: ${{ matrix.os }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: surfaces/gui/package-lock.json
|
|
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: surfaces/gui/src-tauri
|
|
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Set up the sidecar venv (.venv)
|
|
# The build scripts expect a venv at .venv with the package + PyInstaller.
|
|
# typer/tzdata are build-time-only (PyInstaller walks mcp.cli, which needs typer;
|
|
# tzdata ships zoneinfo for Windows). aisuite installs like any other dependency
|
|
# (git-pinned in pyproject.toml).
|
|
run: |
|
|
python -m venv .venv
|
|
if [ "$RUNNER_OS" = "Windows" ]; then VPY=.venv/Scripts/python; else VPY=.venv/bin/python; fi
|
|
"$VPY" -m pip install --upgrade pip
|
|
"$VPY" -m pip install -e ".[bedrock]" pyinstaller typer tzdata
|
|
"$VPY" -c "import aisuite, coworker" # fail fast if either import breaks
|
|
|
|
- name: npm ci
|
|
working-directory: surfaces/gui
|
|
run: npm ci
|
|
|
|
- name: Build .dmg (macOS)
|
|
if: runner.os == 'macOS'
|
|
env:
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_CONTENT }}
|
|
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
# Auto-update artifact signing (minisign, separate from Apple signing). Absent →
|
|
# the build script skips updater artifacts with a warning (fork/scratch runs).
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
# Unset empty APPLE_* vars so runs without secrets stay cleanly unsigned
|
|
# (Tauri treats a present-but-empty var as a config error).
|
|
for v in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY APPLE_API_KEY APPLE_API_ISSUER; do
|
|
[ -n "$(eval echo "\${$v:-}")" ] || unset "$v"
|
|
done
|
|
if [ -n "${APPLE_API_KEY_CONTENT:-}" ]; then
|
|
echo "$APPLE_API_KEY_CONTENT" | base64 -d > "$RUNNER_TEMP/AuthKey.p8"
|
|
export APPLE_API_KEY_PATH="$RUNNER_TEMP/AuthKey.p8"
|
|
fi
|
|
unset APPLE_API_KEY_CONTENT
|
|
bash packaging/build_dmg.sh
|
|
|
|
- name: Build .msi + NSIS .exe (Windows)
|
|
if: runner.os == 'Windows'
|
|
shell: pwsh
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: ./packaging/build_windows.ps1
|
|
|
|
- name: Stage artifacts (versioned + stable names)
|
|
run: |
|
|
mkdir -p out
|
|
BUNDLE=surfaces/gui/src-tauri/target/release/bundle
|
|
if [ "$RUNNER_OS" = "Windows" ]; then
|
|
cp "$BUNDLE"/nsis/*.exe out/
|
|
cp "$BUNDLE"/nsis/*.exe out/OpenWorker-windows-setup.exe
|
|
cp "$BUNDLE"/msi/*.msi out/
|
|
cp "$BUNDLE"/msi/*.msi out/OpenWorker-windows.msi
|
|
# Updater signature for the NSIS installer (present only when the updater key
|
|
# secret is configured). The .sig signs CONTENT, so the stable rename is safe.
|
|
SIG=$(ls "$BUNDLE"/nsis/*.exe.sig 2>/dev/null | head -1 || true)
|
|
[ -n "$SIG" ] && cp "$SIG" out/OpenWorker-windows-setup.exe.sig
|
|
else
|
|
cp "$BUNDLE"/dmg/*.dmg out/
|
|
cp "$BUNDLE"/dmg/*.dmg out/OpenWorker-${{ matrix.slug }}.dmg
|
|
# macOS updater artifact: the signed .app tarball the installed app swaps in.
|
|
if [ -f "$BUNDLE"/macos/OpenWorker.app.tar.gz ]; then
|
|
cp "$BUNDLE"/macos/OpenWorker.app.tar.gz out/OpenWorker-${{ matrix.slug }}.app.tar.gz
|
|
cp "$BUNDLE"/macos/OpenWorker.app.tar.gz.sig out/OpenWorker-${{ matrix.slug }}.app.tar.gz.sig
|
|
fi
|
|
fi
|
|
ls -la out
|
|
|
|
- uses: actions/upload-artifact@v4
|
|
with:
|
|
name: ${{ matrix.slug }}
|
|
path: out/*
|
|
if-no-files-found: error
|
|
|
|
release:
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/download-artifact@v4
|
|
with:
|
|
path: dist
|
|
merge-multiple: true
|
|
|
|
- name: Compose the auto-update manifest (latest.json)
|
|
# Shipped apps poll releases/latest/download/latest.json (via the branded
|
|
# download.openworker.com redirect) — publishing this release IS pushing the
|
|
# update. The tag must match tauri.conf.json's version or installed apps would
|
|
# see a permanent phantom update; fail loudly on drift. Runs only when signed
|
|
# updater artifacts exist (i.e. the TAURI_SIGNING_PRIVATE_KEY secret is set).
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME}"
|
|
CONF_VERSION=$(python3 -c "import json; print(json.load(open('surfaces/gui/src-tauri/tauri.conf.json'))['version'])")
|
|
if [ "${TAG#v}" != "$CONF_VERSION" ]; then
|
|
echo "::error::tag $TAG != tauri.conf.json version $CONF_VERSION — bump the config before tagging"
|
|
exit 1
|
|
fi
|
|
if ls dist/*.sig >/dev/null 2>&1; then
|
|
python3 packaging/make_update_manifest.py \
|
|
--version "${TAG#v}" --tag "$TAG" --repo "$GITHUB_REPOSITORY" \
|
|
--dist dist --out dist/latest.json \
|
|
--notes "OpenWorker ${TAG#v}"
|
|
else
|
|
echo "::warning::no updater signatures in dist/ — release ships WITHOUT auto-update manifest"
|
|
fi
|
|
|
|
- uses: softprops/action-gh-release@v2
|
|
with:
|
|
draft: true
|
|
files: dist/*
|
|
generate_release_notes: true
|