Skip to content

Electron Release

Electron Release #8

name: Electron Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g. v0.0.1)"
required: true
default: "v0.0.1"
binary_version:
description: "acestep.cpp release tag to bundle (e.g. v0.0.1)"
required: false
default: "v0.0.1"
concurrency:
group: electron-release-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: "20"
# acestep.cpp binary release to bundle; override via workflow_dispatch input.
BINARY_VERSION: ${{ github.event.inputs.binary_version || 'v0.0.1' }}
# ──────────────────────────────────────────────────────────────────────────────
# Shared setup steps are defined as a reusable composite action inline via
# `run` steps repeated in each job. Each job is self-contained so the CI
# log is easy to read and debug per platform.
#
# Archive layout (flat tarball — all files at ./):
# bin/<binary> ace-qwen3, dit-vae, neural-codec, …
# bin/lib<name>.so Linux shared libraries (unversioned names)
# bin/lib<name>.dylib macOS dylibs (versioned + symlink chain)
# ──────────────────────────────────────────────────────────────────────────────
jobs:
# ────────────────────────────────────────────────────────────────────────────
# macOS — Apple Silicon → ACE-Step UI-*.dmg
# ────────────────────────────────────────────────────────────────────────────
build-mac:
name: Build — macOS arm64
runs-on: macos-14
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install root dependencies
run: npm install
- name: Install server dependencies
run: npm install
working-directory: server
- name: Build frontend
run: npm run build
- name: Build server
run: npm run build
working-directory: server
- name: Download & extract acestep.cpp binaries
shell: bash
run: |
ARCHIVE="acestep-macos-arm64-metal.tar.gz"
URL="https://github.com/audiohacking/acestep.cpp/releases/download/${BINARY_VERSION}/${ARCHIVE}"
echo "Downloading ${ARCHIVE} from ${URL} …"
curl -fsSL --retry 3 "${URL}" -o "${ARCHIVE}"
mkdir -p bin
tar -xzf "${ARCHIVE}" -C bin/
echo "bin/ contents:"
ls -lh bin/
- name: Verify macOS dylibs
shell: bash
run: |
# macOS dylibs ship as versioned files + two-level symlink chain:
# lib<name>.0.9.7.dylib → lib<name>.0.dylib → lib<name>.dylib
GGML_VER="0.9.7"
warn=0
for base in libggml libggml-base libggml-metal libggml-cpu libggml-blas; do
for name in "${base}.${GGML_VER}.dylib" "${base}.0.dylib" "${base}.dylib"; do
if [ -e "bin/${name}" ]; then echo "✅ bin/${name}"
else echo "⚠️ bin/${name} — missing"; warn=1; fi
done
done
for bin in ace-qwen3 dit-vae neural-codec; do
if [ -f "bin/${bin}" ] && [ -x "bin/${bin}" ]; then echo "✅ bin/${bin}"
else echo "⚠️ bin/${bin} — not found or not executable"; warn=1; fi
done
[ "$warn" = "0" ] || echo "⚠️ Some files missing — verify BINARY_VERSION=${BINARY_VERSION}"
- name: Resolve release tag
id: tag
shell: bash
run: |
TAG="${GITHUB_REF_NAME:-}"
[[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Rebuild native modules for Electron
run: npx @electron/rebuild --module-dir server --only better-sqlite3
- name: Build Electron app bundle (unpacked)
# Build the unpacked .app directory so we can sign it before packaging.
# electron-builder --dir skips DMG creation; we create the DMG ourselves
# after signing so the delivered image contains a properly signed bundle.
run: npm run electron:build:mac -- --dir
env:
CSC_IDENTITY_AUTO_DISCOVERY: false # we sign manually below
npm_package_version: ${{ steps.tag.outputs.tag }}
- name: Code sign the app bundle
shell: bash
run: |
APP="$(find release/mac-arm64 -maxdepth 1 -name '*.app' | head -1)"
if [ -z "$APP" ]; then
echo "❌ No .app bundle found in release/mac-arm64/"
ls -lh release/mac-arm64/ || true
exit 1
fi
echo "App bundle: $APP"
chmod +x codesign.sh
./codesign.sh "$APP"
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY || '-' }}
- name: Create DMG
shell: bash
run: |
APP="$(find release/mac-arm64 -maxdepth 1 -name '*.app' | head -1)"
APP_NAME="$(basename "$APP" .app)"
VERSION="${{ steps.tag.outputs.tag }}"
DMG_NAME="${APP_NAME}-${VERSION}-arm64.dmg"
echo "Creating DMG: ${DMG_NAME}"
mkdir -p dist_dmg
cp -R "$APP" dist_dmg/
# Add Applications symlink for drag-and-drop install
ln -sf /Applications dist_dmg/Applications
hdiutil create \
-volname "${APP_NAME}" \
-srcfolder dist_dmg \
-ov \
-format UDZO \
"release/${DMG_NAME}"
echo "✅ DMG: release/${DMG_NAME}"
ls -lh "release/${DMG_NAME}"
- name: Upload macOS artifact
uses: actions/upload-artifact@v4
with:
name: electron-macos-arm64
path: release/**/*.dmg
if-no-files-found: warn
retention-days: 7
# ────────────────────────────────────────────────────────────────────────────
# Linux — x86_64 → ACE-Step UI-*.AppImage + ACE-Step UI-*.snap
#
# The Linux ELFs have a hardcoded RUNPATH pointing to the CI build tree.
# At runtime electron/main.js prepends BIN_DIR to LD_LIBRARY_PATH so the
# bundled shared libraries are found regardless.
#
# The archive ships unversioned .so names (libggml.so) but ELFs link against
# versioned sonames (libggml.so.0). We create the missing symlinks before
# packaging so electron-builder includes them in extraResources.
# ────────────────────────────────────────────────────────────────────────────
build-linux:
name: Build — Linux x64
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
- name: Install root dependencies
run: npm install
- name: Install server dependencies
run: npm install
working-directory: server
- name: Build frontend
run: npm run build
- name: Build server
run: npm run build
working-directory: server
- name: Download & extract acestep.cpp binaries
shell: bash
run: |
ARCHIVE="acestep-linux-x64.tar.gz"
URL="https://github.com/audiohacking/acestep.cpp/releases/download/${BINARY_VERSION}/${ARCHIVE}"
echo "Downloading ${ARCHIVE} from ${URL} …"
curl -fsSL --retry 3 "${URL}" -o "${ARCHIVE}"
mkdir -p bin
tar -xzf "${ARCHIVE}" -C bin/
echo "bin/ contents:"
ls -lh bin/
- name: Create versioned soname symlinks
shell: bash
run: |
# ELFs link against libggml.so.0 / libggml-base.so.0 (sonames) but
# the archive ships the unversioned names. Create the missing links.
cd bin
for pair in "libggml.so:libggml.so.0" "libggml-base.so:libggml-base.so.0"; do
real="${pair%%:*}"
soname="${pair##*:}"
if [ -f "$real" ] && [ ! -e "$soname" ]; then
ln -sv "$real" "$soname"
fi
done
echo "Symlinks:"
ls -la | grep " -> " || echo "(none)"
- name: Verify Linux binaries & libraries
shell: bash
run: |
warn=0
for bin in ace-qwen3 dit-vae neural-codec; do
if [ -f "bin/${bin}" ] && [ -x "bin/${bin}" ]; then echo "✅ bin/${bin}"
else echo "⚠️ bin/${bin} — not found or not executable"; warn=1; fi
done
for lib in libggml.so libggml-base.so libggml.so.0 libggml-base.so.0; do
if [ -e "bin/${lib}" ]; then echo "✅ bin/${lib}"
else echo "⚠️ bin/${lib} — missing"; warn=1; fi
done
[ "$warn" = "0" ] || echo "⚠️ Some files missing — verify BINARY_VERSION=${BINARY_VERSION}"
- name: Rebuild native modules for Electron
run: npx @electron/rebuild --module-dir server --only better-sqlite3
- name: Install snapcraft
run: sudo snap install snapcraft --classic
- name: Resolve release tag
id: tag
shell: bash
run: |
TAG="${GITHUB_REF_NAME:-}"
[[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Build Electron package (Linux)
run: npm run electron:build:linux -- --publish=never
env:
SNAPCRAFT_STORE_CREDENTIALS: "" # offline / no store upload
npm_package_version: ${{ steps.tag.outputs.tag }}
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: electron-linux-x64
path: |
release/**/*.AppImage
release/**/*.snap
if-no-files-found: warn
retention-days: 7
# ────────────────────────────────────────────────────────────────────────────
# Publish a GitHub Release with all platform artifacts attached.
# ────────────────────────────────────────────────────────────────────────────
publish:
name: Publish GitHub Release
needs: [build-mac, build-linux]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
pattern: electron-*
path: release-artifacts
merge-multiple: true
- name: List release artifacts
run: find release-artifacts -type f | sort
- name: Resolve release tag
id: tag
shell: bash
run: |
TAG="${GITHUB_REF_NAME:-}"
[[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Upload to release
if: steps.tag.outputs.tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ steps.tag.outputs.tag }}" \
release-artifacts/*.snap \
release-artifacts/*.dmg \
--clobber