|
| 1 | +name: Electron Release |
| 2 | + |
| 3 | +# Triggered by a version tag push (v*) or a manual run. |
| 4 | +on: |
| 5 | + push: |
| 6 | + tags: |
| 7 | + - "v*" |
| 8 | + workflow_dispatch: |
| 9 | + inputs: |
| 10 | + tag: |
| 11 | + description: "Release tag (e.g. v1.0.0)" |
| 12 | + required: true |
| 13 | + default: "v0.0.1-electron" |
| 14 | + binary_version: |
| 15 | + description: "acestep.cpp release tag to bundle (e.g. v0.0.1)" |
| 16 | + required: false |
| 17 | + default: "v0.0.1" |
| 18 | + |
| 19 | +concurrency: |
| 20 | + group: electron-release-${{ github.ref }} |
| 21 | + cancel-in-progress: true |
| 22 | + |
| 23 | +env: |
| 24 | + NODE_VERSION: "20" |
| 25 | + # acestep.cpp binary release to bundle; override via workflow_dispatch input. |
| 26 | + BINARY_VERSION: ${{ github.event.inputs.binary_version || 'v0.0.1' }} |
| 27 | + |
| 28 | +# ────────────────────────────────────────────────────────────────────────────── |
| 29 | +# Shared setup steps are defined as a reusable composite action inline via |
| 30 | +# `run` steps repeated in each job. Each job is self-contained so the CI |
| 31 | +# log is easy to read and debug per platform. |
| 32 | +# |
| 33 | +# Archive layout (flat tarball — all files at ./): |
| 34 | +# bin/<binary> ace-qwen3, dit-vae, neural-codec, … |
| 35 | +# bin/lib<name>.so Linux shared libraries (unversioned names) |
| 36 | +# bin/lib<name>.dylib macOS dylibs (versioned + symlink chain) |
| 37 | +# ────────────────────────────────────────────────────────────────────────────── |
| 38 | + |
| 39 | +jobs: |
| 40 | + |
| 41 | + # ──────────────────────────────────────────────────────────────────────────── |
| 42 | + # macOS — Apple Silicon → ACE-Step UI-*.dmg |
| 43 | + # ──────────────────────────────────────────────────────────────────────────── |
| 44 | + build-mac: |
| 45 | + name: Build — macOS arm64 |
| 46 | + runs-on: macos-14 |
| 47 | + permissions: |
| 48 | + contents: read |
| 49 | + |
| 50 | + steps: |
| 51 | + - uses: actions/checkout@v4 |
| 52 | + |
| 53 | + - uses: actions/setup-node@v4 |
| 54 | + with: |
| 55 | + node-version: ${{ env.NODE_VERSION }} |
| 56 | + cache: "npm" |
| 57 | + |
| 58 | + - name: Install root dependencies |
| 59 | + run: npm ci |
| 60 | + |
| 61 | + - name: Install server dependencies |
| 62 | + run: npm ci |
| 63 | + working-directory: server |
| 64 | + |
| 65 | + - name: Build frontend |
| 66 | + run: npm run build |
| 67 | + |
| 68 | + - name: Build server |
| 69 | + run: npm run build |
| 70 | + working-directory: server |
| 71 | + |
| 72 | + - name: Download & extract acestep.cpp binaries |
| 73 | + shell: bash |
| 74 | + run: | |
| 75 | + ARCHIVE="acestep-macos-arm64-metal.tar.gz" |
| 76 | + URL="https://github.com/audiohacking/acestep.cpp/releases/download/${BINARY_VERSION}/${ARCHIVE}" |
| 77 | + echo "Downloading ${ARCHIVE} from ${URL} …" |
| 78 | + curl -fsSL --retry 3 "${URL}" -o "${ARCHIVE}" |
| 79 | + mkdir -p bin |
| 80 | + tar -xzf "${ARCHIVE}" -C bin/ |
| 81 | + echo "bin/ contents:" |
| 82 | + ls -lh bin/ |
| 83 | +
|
| 84 | + - name: Verify macOS dylibs |
| 85 | + shell: bash |
| 86 | + run: | |
| 87 | + # macOS dylibs ship as versioned files + two-level symlink chain: |
| 88 | + # lib<name>.0.9.7.dylib → lib<name>.0.dylib → lib<name>.dylib |
| 89 | + GGML_VER="0.9.7" |
| 90 | + warn=0 |
| 91 | + for base in libggml libggml-base libggml-metal libggml-cpu libggml-blas; do |
| 92 | + for name in "${base}.${GGML_VER}.dylib" "${base}.0.dylib" "${base}.dylib"; do |
| 93 | + if [ -e "bin/${name}" ]; then echo "✅ bin/${name}" |
| 94 | + else echo "⚠️ bin/${name} — missing"; warn=1; fi |
| 95 | + done |
| 96 | + done |
| 97 | + for bin in ace-qwen3 dit-vae neural-codec; do |
| 98 | + if [ -f "bin/${bin}" ] && [ -x "bin/${bin}" ]; then echo "✅ bin/${bin}" |
| 99 | + else echo "⚠️ bin/${bin} — not found or not executable"; warn=1; fi |
| 100 | + done |
| 101 | + [ "$warn" = "0" ] || echo "⚠️ Some files missing — verify BINARY_VERSION=${BINARY_VERSION}" |
| 102 | +
|
| 103 | + - name: Rebuild native modules for Electron |
| 104 | + run: npx @electron/rebuild --module-dir server --only better-sqlite3 |
| 105 | + |
| 106 | + - name: Build Electron app bundle (unpacked) |
| 107 | + # Build the unpacked .app directory so we can sign it before packaging. |
| 108 | + # electron-builder --dir skips DMG creation; we create the DMG ourselves |
| 109 | + # after signing so the delivered image contains a properly signed bundle. |
| 110 | + run: npm run electron:build:mac -- --dir |
| 111 | + env: |
| 112 | + CSC_IDENTITY_AUTO_DISCOVERY: false # we sign manually below |
| 113 | + |
| 114 | + - name: Code sign the app bundle |
| 115 | + shell: bash |
| 116 | + run: | |
| 117 | + APP="$(find release/mac-arm64 -maxdepth 1 -name '*.app' | head -1)" |
| 118 | + if [ -z "$APP" ]; then |
| 119 | + echo "❌ No .app bundle found in release/mac-arm64/" |
| 120 | + ls -lh release/mac-arm64/ || true |
| 121 | + exit 1 |
| 122 | + fi |
| 123 | + echo "App bundle: $APP" |
| 124 | + chmod +x build/macos/codesign.sh |
| 125 | + ./build/macos/codesign.sh "$APP" |
| 126 | + env: |
| 127 | + MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY || '-' }} |
| 128 | + |
| 129 | + - name: Create DMG |
| 130 | + shell: bash |
| 131 | + run: | |
| 132 | + APP="$(find release/mac-arm64 -maxdepth 1 -name '*.app' | head -1)" |
| 133 | + APP_NAME="$(basename "$APP" .app)" |
| 134 | + VERSION="$(node -p "require('./package.json').version")" |
| 135 | + DMG_NAME="${APP_NAME}-${VERSION}-arm64.dmg" |
| 136 | +
|
| 137 | + echo "Creating DMG: ${DMG_NAME}" |
| 138 | + mkdir -p dist_dmg |
| 139 | + cp -R "$APP" dist_dmg/ |
| 140 | + # Add Applications symlink for drag-and-drop install |
| 141 | + ln -sf /Applications dist_dmg/Applications |
| 142 | +
|
| 143 | + hdiutil create \ |
| 144 | + -volname "${APP_NAME}" \ |
| 145 | + -srcfolder dist_dmg \ |
| 146 | + -ov \ |
| 147 | + -format UDZO \ |
| 148 | + "release/${DMG_NAME}" |
| 149 | +
|
| 150 | + echo "✅ DMG: release/${DMG_NAME}" |
| 151 | + ls -lh "release/${DMG_NAME}" |
| 152 | +
|
| 153 | + - name: Upload macOS artifact |
| 154 | + uses: actions/upload-artifact@v4 |
| 155 | + with: |
| 156 | + name: electron-macos-arm64 |
| 157 | + path: release/**/*.dmg |
| 158 | + if-no-files-found: warn |
| 159 | + retention-days: 7 |
| 160 | + |
| 161 | + # ──────────────────────────────────────────────────────────────────────────── |
| 162 | + # Linux — x86_64 → ACE-Step UI-*.AppImage + ACE-Step UI-*.snap |
| 163 | + # |
| 164 | + # The Linux ELFs have a hardcoded RUNPATH pointing to the CI build tree. |
| 165 | + # At runtime electron/main.js prepends BIN_DIR to LD_LIBRARY_PATH so the |
| 166 | + # bundled shared libraries are found regardless. |
| 167 | + # |
| 168 | + # The archive ships unversioned .so names (libggml.so) but ELFs link against |
| 169 | + # versioned sonames (libggml.so.0). We create the missing symlinks before |
| 170 | + # packaging so electron-builder includes them in extraResources. |
| 171 | + # ──────────────────────────────────────────────────────────────────────────── |
| 172 | + build-linux: |
| 173 | + name: Build — Linux x64 |
| 174 | + runs-on: ubuntu-22.04 |
| 175 | + permissions: |
| 176 | + contents: read |
| 177 | + |
| 178 | + steps: |
| 179 | + - uses: actions/checkout@v4 |
| 180 | + |
| 181 | + - uses: actions/setup-node@v4 |
| 182 | + with: |
| 183 | + node-version: ${{ env.NODE_VERSION }} |
| 184 | + cache: "npm" |
| 185 | + |
| 186 | + - name: Install root dependencies |
| 187 | + run: npm ci |
| 188 | + |
| 189 | + - name: Install server dependencies |
| 190 | + run: npm ci |
| 191 | + working-directory: server |
| 192 | + |
| 193 | + - name: Build frontend |
| 194 | + run: npm run build |
| 195 | + |
| 196 | + - name: Build server |
| 197 | + run: npm run build |
| 198 | + working-directory: server |
| 199 | + |
| 200 | + - name: Download & extract acestep.cpp binaries |
| 201 | + shell: bash |
| 202 | + run: | |
| 203 | + ARCHIVE="acestep-linux-x64.tar.gz" |
| 204 | + URL="https://github.com/audiohacking/acestep.cpp/releases/download/${BINARY_VERSION}/${ARCHIVE}" |
| 205 | + echo "Downloading ${ARCHIVE} from ${URL} …" |
| 206 | + curl -fsSL --retry 3 "${URL}" -o "${ARCHIVE}" |
| 207 | + mkdir -p bin |
| 208 | + tar -xzf "${ARCHIVE}" -C bin/ |
| 209 | + echo "bin/ contents:" |
| 210 | + ls -lh bin/ |
| 211 | +
|
| 212 | + - name: Create versioned soname symlinks |
| 213 | + shell: bash |
| 214 | + run: | |
| 215 | + # ELFs link against libggml.so.0 / libggml-base.so.0 (sonames) but |
| 216 | + # the archive ships the unversioned names. Create the missing links. |
| 217 | + cd bin |
| 218 | + for pair in "libggml.so:libggml.so.0" "libggml-base.so:libggml-base.so.0"; do |
| 219 | + real="${pair%%:*}" |
| 220 | + soname="${pair##*:}" |
| 221 | + if [ -f "$real" ] && [ ! -e "$soname" ]; then |
| 222 | + ln -sv "$real" "$soname" |
| 223 | + fi |
| 224 | + done |
| 225 | + echo "Symlinks:" |
| 226 | + ls -la | grep " -> " || echo "(none)" |
| 227 | +
|
| 228 | + - name: Verify Linux binaries & libraries |
| 229 | + shell: bash |
| 230 | + run: | |
| 231 | + warn=0 |
| 232 | + for bin in ace-qwen3 dit-vae neural-codec; do |
| 233 | + if [ -f "bin/${bin}" ] && [ -x "bin/${bin}" ]; then echo "✅ bin/${bin}" |
| 234 | + else echo "⚠️ bin/${bin} — not found or not executable"; warn=1; fi |
| 235 | + done |
| 236 | + for lib in libggml.so libggml-base.so libggml.so.0 libggml-base.so.0; do |
| 237 | + if [ -e "bin/${lib}" ]; then echo "✅ bin/${lib}" |
| 238 | + else echo "⚠️ bin/${lib} — missing"; warn=1; fi |
| 239 | + done |
| 240 | + [ "$warn" = "0" ] || echo "⚠️ Some files missing — verify BINARY_VERSION=${BINARY_VERSION}" |
| 241 | +
|
| 242 | + - name: Rebuild native modules for Electron |
| 243 | + run: npx @electron/rebuild --module-dir server --only better-sqlite3 |
| 244 | + |
| 245 | + - name: Install snapcraft |
| 246 | + run: sudo snap install snapcraft --classic |
| 247 | + |
| 248 | + - name: Build Electron package (Linux) |
| 249 | + run: npm run electron:build:linux |
| 250 | + env: |
| 251 | + SNAPCRAFT_STORE_CREDENTIALS: "" # offline / no store upload |
| 252 | + |
| 253 | + - name: Upload Linux artifacts |
| 254 | + uses: actions/upload-artifact@v4 |
| 255 | + with: |
| 256 | + name: electron-linux-x64 |
| 257 | + path: | |
| 258 | + release/**/*.AppImage |
| 259 | + release/**/*.snap |
| 260 | + if-no-files-found: warn |
| 261 | + retention-days: 7 |
| 262 | + |
| 263 | + # ──────────────────────────────────────────────────────────────────────────── |
| 264 | + # Publish a GitHub Release with all platform artifacts attached. |
| 265 | + # ──────────────────────────────────────────────────────────────────────────── |
| 266 | + publish: |
| 267 | + name: Publish GitHub Release |
| 268 | + needs: [build-mac, build-linux] |
| 269 | + runs-on: ubuntu-latest |
| 270 | + permissions: |
| 271 | + contents: write |
| 272 | + |
| 273 | + steps: |
| 274 | + - uses: actions/checkout@v4 |
| 275 | + with: |
| 276 | + fetch-depth: 0 |
| 277 | + |
| 278 | + - uses: actions/download-artifact@v4 |
| 279 | + with: |
| 280 | + pattern: electron-* |
| 281 | + path: release-artifacts |
| 282 | + merge-multiple: true |
| 283 | + |
| 284 | + - name: List release artifacts |
| 285 | + run: find release-artifacts -type f | sort |
| 286 | + |
| 287 | + - name: Resolve release tag |
| 288 | + id: tag |
| 289 | + shell: bash |
| 290 | + run: | |
| 291 | + TAG="${GITHUB_REF_NAME:-}" |
| 292 | + [[ "$TAG" == v* ]] || TAG="${{ github.event.inputs.tag || 'v0.0.1-electron' }}" |
| 293 | + echo "tag=$TAG" >> "$GITHUB_OUTPUT" |
| 294 | +
|
| 295 | + - name: Publish GitHub Release |
| 296 | + uses: softprops/action-gh-release@v2 |
| 297 | + with: |
| 298 | + tag_name: ${{ steps.tag.outputs.tag }} |
| 299 | + name: "ACE-Step UI ${{ steps.tag.outputs.tag }} — Desktop App" |
| 300 | + draft: false |
| 301 | + prerelease: ${{ contains(steps.tag.outputs.tag, '-') }} |
| 302 | + body: | |
| 303 | + ## ACE-Step UI ${{ steps.tag.outputs.tag }} — Electron Desktop App |
| 304 | +
|
| 305 | + Native desktop application with embedded server and precompiled |
| 306 | + [acestep.cpp](https://github.com/audiohacking/acestep.cpp) binaries bundled — no compiler or build step required. |
| 307 | +
|
| 308 | + ### Downloads |
| 309 | +
|
| 310 | + | File | Platform | |
| 311 | + |------|----------| |
| 312 | + | `*.dmg` | macOS — Apple Silicon (arm64) | |
| 313 | + | `*.AppImage` | Linux — x86_64 (portable, any distro) | |
| 314 | + | `*.snap` | Linux — x86_64 (Snap Store / snapd) | |
| 315 | +
|
| 316 | + ### First run |
| 317 | +
|
| 318 | + On first launch the app will offer to download the default Q8_0 model set |
| 319 | + (~8 GB total) from HuggingFace automatically. You can also skip and |
| 320 | + copy models manually to: |
| 321 | + - **macOS** `~/Library/Application Support/ACE-Step UI/models/` |
| 322 | + - **Linux** `~/.config/ACE-Step UI/models/` |
| 323 | +
|
| 324 | + Generated audio is saved to `~/Music/ACEStep/`. |
| 325 | +
|
| 326 | + ### macOS notes |
| 327 | +
|
| 328 | + The DMG is ad-hoc signed (no Apple notarisation required for dev builds). |
| 329 | + If macOS still shows a "damaged or incomplete" warning, run once: |
| 330 | + ``` |
| 331 | + xattr -cr "/Applications/ACE-Step UI.app" |
| 332 | + ``` |
| 333 | +
|
| 334 | + ### Linux notes |
| 335 | +
|
| 336 | + **AppImage** — make executable then run: |
| 337 | + ``` |
| 338 | + chmod +x ACE-Step*.AppImage && ./ACE-Step*.AppImage |
| 339 | + ``` |
| 340 | +
|
| 341 | + **Snap** — install locally (classic confinement required): |
| 342 | + ``` |
| 343 | + sudo snap install *.snap --dangerous --classic |
| 344 | + ``` |
| 345 | + files: release-artifacts/**/* |
| 346 | + fail_on_unmatched_files: false |
0 commit comments