Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ env:

jobs:
macos:
name: macOS DMG
name: macOS universal DMG
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin,x86_64-apple-darwin
- uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri
Expand All @@ -49,18 +51,21 @@ jobs:
- id: updater
uses: ./.github/actions/updater-flags

# These three mirror the `build` script in package.json. They are spelled out
# here only so the updater `--config` flag can be injected into the middle
# one; keep them in step with that script.
# Keep this in step with `build:macos-universal`. The updater config must be
# inserted into the Tauri invocation, so CI spells the command out here.
- run: bun run typecheck
- run: bun run tauri build --bundles app ${{ steps.updater.outputs.config }}
- run: bun run dmg
- run: bun run tauri build --bundles app --target universal-apple-darwin ${{ steps.updater.outputs.config }}
- name: Verify universal app binary
run: |
lipo "src-tauri/target/universal-apple-darwin/release/bundle/macos/ÆTHER.app/Contents/MacOS/aether" \
-verify_arch arm64 x86_64
- run: AETHER_TAURI_TARGET=universal-apple-darwin bun run dmg

- uses: actions/upload-artifact@v4
with:
name: aether-macos-dmg
retention-days: 14
path: src-tauri/target/release/bundle/dmg/*.dmg
path: src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg
if-no-files-found: error

# The in-app updater installs this tarball, not the DMG: it replaces the
Expand All @@ -71,8 +76,8 @@ jobs:
name: aether-macos-updater
retention-days: 14
path: |
src-tauri/target/release/bundle/macos/*.app.tar.gz
src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz
src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz.sig
if-no-files-found: error

linux:
Expand Down
67 changes: 34 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@
Download the latest build for your platform from
**[Releases](https://github.com/CanPixel/aether/releases/latest)**:

| Platform | File |
| -------------------------- | -------------------------------------------- |
| macOS (Apple Silicon, 11+) | `AETHER_macOS.dmg` |
| Windows (x86_64) | `AETHER_x64-setup.exe` |
| Linux (x86_64) | `AETHER_amd64.deb` · `AETHER_amd64.AppImage` |
| Linux (ARM64) | `AETHER_arm64.deb` |
| Platform | File |
| ----------------------------------- | -------------------------------------------- |
| macOS (Apple Silicon or Intel, 11+) | `AETHER_macOS.dmg` |
| Windows (x86_64) | `AETHER_x64-setup.exe` |
| Linux (x86_64) | `AETHER_amd64.deb` · `AETHER_amd64.AppImage` |
| Linux (ARM64) | `AETHER_arm64.deb` |

> [!NOTE]
> **Intel Macs are not supported.** Releases are built `arm64` only, and Rosetta
> translates Intel binaries to Apple Silicon — not the other way round — so there is
> no way to run this DMG on an Intel Mac. Building `universal-apple-darwin` would fix
> it at the cost of doubling an already slow llama.cpp compile.
> The macOS DMG contains a universal binary for Apple Silicon and Intel Macs. Intel
> machines with 16 GB RAM, a quad-core CPU, and an SSD are supported for MiST and
> AiON LiTE. AiON WiSE and large contexts are likely to be slow on Intel hardware;
> Apple Silicon is strongly recommended for the full local-AI experience.

> [!IMPORTANT]
> **Releases are not yet code-signed**, so your OS will block the first launch. This
Expand Down Expand Up @@ -719,7 +719,7 @@ LINUX_DOCKER_PLATFORM=linux/amd64 LINUX_TARGET=x86_64-unknown-linux-gnu LINUX_AR

Because Tauri cannot cross-compile desktop targets, and llama.cpp builds natively per-OS, the cross-platform installers are produced by `.github/workflows/build.yml`:

- `macos-latest` → styled `.dmg` installer.
- `macos-latest` → universal Apple Silicon + Intel styled `.dmg` installer.
- `windows-latest` → NSIS `.exe` / MSI installer.
- `ubuntu-latest` → x86_64 `.deb` and AppImage.
- `ubuntu-24.04-arm` → ARM64 `.deb` for devices such as Raspberry Pi / Compute Module boards.
Expand Down Expand Up @@ -757,28 +757,29 @@ bun run build

## Project Scripts

| Script | Purpose |
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
| `bun run dev` | Start the Tauri desktop app in development. |
| `bun run start` | Alias for the Tauri desktop development app. |
| `bun run dev:vite` | Start only the Vite renderer dev server on `127.0.0.1:1420`. |
| `bun run format` | Format the project with Prettier. |
| `bun run typecheck:web` | Run renderer TypeScript checks. |
| `bun run typecheck:tauri` | Run Rust `cargo check` for the Tauri backend. |
| `bun run typecheck` | Run renderer TypeScript checks and Rust `cargo check` for the Tauri backend. |
| `bun run version:bump 1.2.3` | Sync `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` to one app version. |
| `bun run version:check` | Verify the app version manifests match `package.json`. |
| `bun run lint` | Run ESLint. |
| `bun run build:vite` | Build only the Vite renderer assets into `dist/`. |
| `bun run build` | Typecheck and build the Tauri desktop app. |
| `bun run build:desktop-local` | Build local desktop packages for the current Tauri target plus Docker Linux arm64/x64 packages. |
| `bun run android:dev` | Run the Tauri Android app on a connected device or emulator. |
| `bun run android:build:apk` | Build an Android APK. |
| `bun run android:build:aab` | Build an Android App Bundle. |
| `bun run linux:arm64:build` | Build an Ubuntu arm64 Tauri package in Docker. |
| `bun run linux:x64:build` | Build an Ubuntu x86_64 Tauri package in Docker. |
| `bun run linux:arm64:deb` | Build an Ubuntu arm64 `.deb` package in Docker. |
| `bun run linux:x64:deb` | Build an Ubuntu x86_64 `.deb` package in Docker. |
| Script | Purpose |
| ------------------------------- | ------------------------------------------------------------------------------------------------ |
| `bun run dev` | Start the Tauri desktop app in development. |
| `bun run start` | Alias for the Tauri desktop development app. |
| `bun run dev:vite` | Start only the Vite renderer dev server on `127.0.0.1:1420`. |
| `bun run format` | Format the project with Prettier. |
| `bun run typecheck:web` | Run renderer TypeScript checks. |
| `bun run typecheck:tauri` | Run Rust `cargo check` for the Tauri backend. |
| `bun run typecheck` | Run renderer TypeScript checks and Rust `cargo check` for the Tauri backend. |
| `bun run version:bump 1.2.3` | Sync `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json` to one app version. |
| `bun run version:check` | Verify the app version manifests match `package.json`. |
| `bun run lint` | Run ESLint. |
| `bun run build:vite` | Build only the Vite renderer assets into `dist/`. |
| `bun run build` | Typecheck and build the Tauri desktop app. |
| `bun run build:macos-universal` | Typecheck and build the universal Apple Silicon + Intel macOS app and DMG. |
| `bun run build:desktop-local` | Build local desktop packages for the current Tauri target plus Docker Linux arm64/x64 packages. |
| `bun run android:dev` | Run the Tauri Android app on a connected device or emulator. |
| `bun run android:build:apk` | Build an Android APK. |
| `bun run android:build:aab` | Build an Android App Bundle. |
| `bun run linux:arm64:build` | Build an Ubuntu arm64 Tauri package in Docker. |
| `bun run linux:x64:build` | Build an Ubuntu x86_64 Tauri package in Docker. |
| `bun run linux:arm64:deb` | Build an Ubuntu arm64 `.deb` package in Docker. |
| `bun run linux:x64:deb` | Build an Ubuntu x86_64 `.deb` package in Docker. |

## Build Outputs

Expand Down
5 changes: 3 additions & 2 deletions docs/SIGNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,9 @@ Install button reports `unconfigured` — _"This build has no update signing key
```bash
curl -sL https://github.com/CanPixel/aether/releases/latest/download/latest.json | jq .
```
It should list `darwin-aarch64`, `windows-x86_64`, and `linux-x86_64`, each with a
non-empty `signature` and a URL pinned to the release tag.
It should list `darwin-aarch64`, `darwin-x86_64`, `windows-x86_64`, and
`linux-x86_64`, each with a non-empty `signature` and a URL pinned to the release
tag. The two macOS entries intentionally share the universal updater archive.

### What updates, and what does not

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"build:vite": "vite build",
"prepare:dmg-background": "bash scripts/prepare-dmg-background.sh",
"build": "bun run typecheck && tauri build --bundles app && bun run dmg",
"build:macos-universal": "bun run typecheck && tauri build --bundles app --target universal-apple-darwin && AETHER_TAURI_TARGET=universal-apple-darwin bun run dmg",
"dmg": "bash scripts/make-styled-dmg.sh",
"android:init": "bash scripts/android.sh init",
"android:dev": "bash scripts/android.sh dev",
Expand Down
29 changes: 20 additions & 9 deletions scripts/make-styled-dmg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,31 @@
# background actually landed before declaring success.
#
# Usage: bun run dmg (expects `tauri build --bundles app` to have produced the .app)
# AETHER_TAURI_TARGET=universal-apple-darwin bun run dmg
# (expects `tauri build --target universal-apple-darwin`)
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

VOLNAME="ÆTHER"
APP="src-tauri/target/release/bundle/macos/${VOLNAME}.app"
OUT_DIR="src-tauri/target/release/bundle/dmg"
if [[ -n "${AETHER_TAURI_TARGET:-}" ]]; then
BUNDLE_DIR="src-tauri/target/${AETHER_TAURI_TARGET}/release/bundle"
else
BUNDLE_DIR="src-tauri/target/release/bundle"
fi
APP="${BUNDLE_DIR}/macos/${VOLNAME}.app"
OUT_DIR="${BUNDLE_DIR}/dmg"
VERSION="$(bun -e 'console.log(require("./src-tauri/tauri.conf.json").version)')"
case "$(uname -m)" in
arm64) ARCH="aarch64" ;;
x86_64) ARCH="x64" ;;
*) ARCH="$(uname -m)" ;;
esac
if [[ "${AETHER_TAURI_TARGET:-}" == "universal-apple-darwin" ]]; then
ARCH="universal"
else
case "$(uname -m)" in
arm64) ARCH="aarch64" ;;
x86_64) ARCH="x64" ;;
*) ARCH="$(uname -m)" ;;
esac
fi
OUT="${OUT_DIR}/${VOLNAME}_${VERSION}_${ARCH}.dmg"
BG_PNG="build/dmg-background.png"
BG_TIFF="build/dmg-background.tiff"
Expand All @@ -43,7 +54,7 @@ bash scripts/prepare-dmg-background.sh
# Derive everything from tauri.conf.json so the DMG never drifts from config.
SPEC="$(mktemp -u -t aether-appdmg).json"
trap 'rm -f "$SPEC" 2>/dev/null || true' EXIT
SPEC="$SPEC" bun -e '
BUNDLE_DIR="$BUNDLE_DIR" SPEC="$SPEC" bun -e '
const fs = require("fs");
const path = require("path");
const conf = require("./src-tauri/tauri.conf.json");
Expand All @@ -60,7 +71,7 @@ SPEC="$SPEC" bun -e '
window: { size: { width: d.windowSize.width, height: d.windowSize.height } },
contents: [
{ x: d.appPosition.x, y: d.appPosition.y, type: "file",
path: path.resolve("src-tauri/target/release/bundle/macos/" + conf.productName + ".app") },
path: path.resolve(process.env.BUNDLE_DIR + "/macos/" + conf.productName + ".app") },
{ x: d.applicationFolderPosition.x, y: d.applicationFolderPosition.y, type: "link", path: "/Applications" },
],
};
Expand Down
6 changes: 2 additions & 4 deletions scripts/updater-manifest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,8 @@ if [[ -z "$mac_sig$win_sig$linux_sig" ]]; then
exit 0
fi

# Both macOS arches map to the same tarball: the bundle is built on Apple Silicon,
# and an Intel Mac running it under Rosetta still reports darwin-x86_64 to the
# updater. Drop darwin-x86_64 here if a real Intel or universal build is ever
# published as a separate asset.
# The macOS updater archive contains a universal app binary, so both native
# architectures correctly resolve to the same signed tarball.
jq -n \
--arg version "$version" \
--arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
Expand Down
35 changes: 30 additions & 5 deletions src-tauri/src/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,22 +321,47 @@ impl NativeModelRuntime {
} else {
backend.supports_mmap()
};
let mut params = LlamaModelParams::default().with_use_mmap(use_mmap);
let use_gpu = match kind {
NativeModelKind::Chat => local_gpu_enabled(),
NativeModelKind::Embedding => embedding_gpu_enabled(),
};
if use_gpu && backend.supports_gpu_offload() {
let using_gpu = use_gpu && backend.supports_gpu_offload();
let mut params = LlamaModelParams::default().with_use_mmap(use_mmap);
if using_gpu {
params = params.with_n_gpu_layers(999);
} else {
params = params
.with_n_gpu_layers(0)
.with_devices(&[])
.map_err(|error| format!("Failed to select CPU model backend: {error}"))?;
}
let model = LlamaModel::load_from_file(backend, &path, &params).map_err(|error| {
format!("Failed to load local model {}: {error}", model_label(&path))
})?;
let model = match LlamaModel::load_from_file(backend, &path, &params) {
Ok(model) => model,
// Intel Macs can expose a Metal device with too little VRAM for a full
// model offload. Keep local intelligence usable instead of turning an
// otherwise recoverable GPU allocation failure into a dead model picker.
Err(gpu_error) if using_gpu => {
let cpu_params = LlamaModelParams::default()
.with_use_mmap(use_mmap)
.with_n_gpu_layers(0)
.with_devices(&[])
.map_err(|error| format!("Failed to select CPU model backend: {error}"))?;
LlamaModel::load_from_file(backend, &path, &cpu_params).map_err(
|cpu_error| {
format!(
"Failed to load local model {} with GPU ({gpu_error}) or CPU fallback ({cpu_error})",
model_label(&path)
)
},
)?
}
Err(error) => {
return Err(format!(
"Failed to load local model {}: {error}",
model_label(&path)
));
}
};
*slot = Some(LoadedNativeModel { path, model });
Ok(())
}
Expand Down
60 changes: 11 additions & 49 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,21 @@
"csp": {
"default-src": "'self'",
"script-src": "'self'",
"style-src": [
"'self'",
"'unsafe-inline'"
],
"img-src": [
"'self'",
"data:",
"blob:"
],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:", "blob:"],
"font-src": "'self'",
"connect-src": [
"'self'",
"ipc:",
"http://ipc.localhost"
],
"media-src": [
"'self'",
"data:",
"blob:"
],
"connect-src": ["'self'", "ipc:", "http://ipc.localhost"],
"media-src": ["'self'", "data:", "blob:"],
"object-src": "'none'",
"base-uri": "'self'",
"form-action": "'none'",
"frame-ancestors": "'none'"
},
"devCsp": {
"default-src": "'self'",
"script-src": [
"'self'",
"'unsafe-inline'",
"'unsafe-eval'"
],
"style-src": [
"'self'",
"'unsafe-inline'"
],
"img-src": [
"'self'",
"data:",
"blob:"
],
"script-src": ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:", "blob:"],
"font-src": "'self'",
"connect-src": [
"'self'",
Expand All @@ -76,11 +50,7 @@
"ws://localhost:1420",
"http://127.0.0.1:1420"
],
"media-src": [
"'self'",
"data:",
"blob:"
],
"media-src": ["'self'", "data:", "blob:"],
"object-src": "'none'",
"base-uri": "'self'",
"form-action": "'none'"
Expand All @@ -89,9 +59,7 @@
},
"plugins": {
"updater": {
"endpoints": [
"https://github.com/CanPixel/aether/releases/latest/download/latest.json"
],
"endpoints": ["https://github.com/CanPixel/aether/releases/latest/download/latest.json"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ5QzUwQTk2MkJFQjA4MEIKUldRTENPc3JsZ3JGU1FWamQrMkUrb0hmSDR2MktHZjltZVJyb0RCcHQzNFdEelIxNlYzdmlvOFgK",
"windows": {
"installMode": "passive"
Expand All @@ -100,14 +68,8 @@
},
"bundle": {
"active": true,
"targets": [
"app"
],
"icon": [
"../resources/icon.png",
"../build/icon.icns",
"../build/icon.ico"
],
"targets": ["app"],
"icon": ["../resources/icon.png", "../build/icon.icns", "../build/icon.ico"],
"macOS": {
"minimumSystemVersion": "10.15",
"dmg": {
Expand Down
Loading
Loading