Skip to content

Commit 34aa4dc

Browse files
authored
Merge pull request #19 from audiohacking/copilot/add-electron-desktop-shell
Electron App Builders
2 parents 6e1d10b + 111971d commit 34aa4dc

13 files changed

Lines changed: 1584 additions & 2 deletions

File tree

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ DATABASE_PATH=./data/acestep.db
1313
# Run ./models.sh to download the default Q8_0 essential set (~8 GB).
1414
MODELS_DIR=./models
1515

16+
# Electron desktop app: MODELS_DIR is resolved in this priority order:
17+
# 1. MODELS_DIR env var set before launching (e.g. export MODELS_DIR=/my/models)
18+
# 2. Path chosen via the "Browse for existing models…" dialog (saved in prefs.json)
19+
# 3. Default: <userData>/models (macOS: ~/Library/Application Support/ACE-Step UI/models)
20+
# (Linux: ~/.config/ACE-Step UI/models)
21+
# Setting MODELS_DIR in the environment lets you reuse a shared models folder
22+
# across multiple tools without copying or re-downloading the ~8 GB model set.
23+
1624
# ── acestep-cpp: two modes, pick ONE ─────────────────────────────────────────
1725
#
1826
# Mode 1 (recommended, zero-config): binaries are auto-discovered in ./bin/

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
cache: "npm"
2727

2828
- name: Install dependencies
29-
run: npm ci
29+
run: npm install
3030

3131
- name: TypeScript check
3232
run: ./node_modules/.bin/tsc --noEmit
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
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

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,8 @@ wavacity-editor/
9797

9898
# Script logs
9999
logs/
100+
101+
# Electron build output
102+
release/
103+
dist-electron/
104+

0 commit comments

Comments
 (0)