Electron Release #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Electron Release | |
| # Triggered by a version tag push (v*) or a manual run. | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag (e.g. v1.0.0)" | |
| required: true | |
| default: "v0.0.1-electron" | |
| 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: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| - name: Install root dependencies | |
| run: npm ci | |
| - name: Install server dependencies | |
| run: npm ci | |
| 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: 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 | |
| - 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 build/macos/codesign.sh | |
| ./build/macos/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="$(node -p "require('./package.json').version")" | |
| 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: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| - name: Install root dependencies | |
| run: npm ci | |
| - name: Install server dependencies | |
| run: npm ci | |
| 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: Build Electron package (Linux) | |
| run: npm run electron:build:linux | |
| env: | |
| SNAPCRAFT_STORE_CREDENTIALS: "" # offline / no store upload | |
| - 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: Publish GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.tag }} | |
| name: "ACE-Step UI ${{ steps.tag.outputs.tag }} — Desktop App" | |
| draft: false | |
| prerelease: ${{ contains(steps.tag.outputs.tag, '-') }} | |
| body: | | |
| ## ACE-Step UI ${{ steps.tag.outputs.tag }} — Electron Desktop App | |
| Native desktop application with embedded server and precompiled | |
| [acestep.cpp](https://github.com/audiohacking/acestep.cpp) binaries bundled — no compiler or build step required. | |
| ### Downloads | |
| | File | Platform | | |
| |------|----------| | |
| | `*.dmg` | macOS — Apple Silicon (arm64) | | |
| | `*.AppImage` | Linux — x86_64 (portable, any distro) | | |
| | `*.snap` | Linux — x86_64 (Snap Store / snapd) | | |
| ### First run | |
| On first launch the app will offer to download the default Q8_0 model set | |
| (~8 GB total) from HuggingFace automatically. You can also skip and | |
| copy models manually to: | |
| - **macOS** `~/Library/Application Support/ACE-Step UI/models/` | |
| - **Linux** `~/.config/ACE-Step UI/models/` | |
| Generated audio is saved to `~/Music/ACEStep/`. | |
| ### macOS notes | |
| The DMG is ad-hoc signed (no Apple notarisation required for dev builds). | |
| If macOS still shows a "damaged or incomplete" warning, run once: | |
| ``` | |
| xattr -cr "/Applications/ACE-Step UI.app" | |
| ``` | |
| ### Linux notes | |
| **AppImage** — make executable then run: | |
| ``` | |
| chmod +x ACE-Step*.AppImage && ./ACE-Step*.AppImage | |
| ``` | |
| **Snap** — install locally (classic confinement required): | |
| ``` | |
| sudo snap install *.snap --dangerous --classic | |
| ``` | |
| files: release-artifacts/**/* | |
| fail_on_unmatched_files: false |