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
73 changes: 73 additions & 0 deletions .github/scripts/inject-macos-dmg-installer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Inject Gatekeeper installer files into built macOS DMGs.
# Usage: inject-macos-dmg-installer.sh [dmg-path...]
# With no args, finds DMGs under src-tauri/target/**/bundle/dmg/
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
INSTALLER_SRC="$ROOT/src-tauri/macos/Install Grimoire.command"
HOWTO_SRC="$ROOT/src-tauri/macos/How to install.txt"

if [[ ! -f "$INSTALLER_SRC" ]]; then
echo "missing installer: $INSTALLER_SRC" >&2
exit 1
fi
if [[ ! -f "$HOWTO_SRC" ]]; then
echo "missing howto: $HOWTO_SRC" >&2
exit 1
fi

if [[ $# -gt 0 ]]; then
DMGS=("$@")
else
# shellcheck disable=SC2207
DMGS=($(find "$ROOT/src-tauri/target" -type f -path '*/bundle/dmg/*.dmg' ! -name 'rw.*.dmg' 2>/dev/null | sort -u))
fi

if [[ ${#DMGS[@]} -eq 0 ]]; then
echo "No DMGs found to patch." >&2
exit 1
fi

inject_one() {
local dmg="$1"
local work rw mount staging out

echo "Injecting installer into $(basename "$dmg")…"

work="$(mktemp -d "${TMPDIR:-/tmp}/grimoire-dmg.XXXXXX")"
rw="$work/rw.dmg"
staging="$work/mount"
out="$work/out.dmg"
mkdir -p "$staging"

# Writable copy with headroom for installer files.
hdiutil convert "$dmg" -format UDRW -o "$rw" >/dev/null
# Resize: current size + 2MB (hdiutil wants sector counts; -size accepts m).
local size_mb
size_mb="$(du -sm "$rw" | awk '{print $1 + 2}')"
hdiutil resize -size "${size_mb}m" "$rw" >/dev/null

hdiutil attach "$rw" -readwrite -nobrowse -mountpoint "$staging" >/dev/null

cp "$INSTALLER_SRC" "$staging/Install Grimoire.command"
chmod +x "$staging/Install Grimoire.command"
cp "$HOWTO_SRC" "$staging/How to install.txt"

# Flush before detach.
sync
hdiutil detach "$staging" >/dev/null

hdiutil convert "$rw" -format UDZO -imagekey zlib-level=9 -o "$out" >/dev/null
mv -f "$out" "$dmg"
rm -rf "$work"
echo " ok: $dmg"
}

for dmg in "${DMGS[@]}"; do
if [[ ! -f "$dmg" ]]; then
echo "skip missing: $dmg" >&2
continue
fi
inject_one "$dmg"
done
38 changes: 38 additions & 0 deletions .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Build and upload to draft release
if: matrix.platform != 'macos-latest'
uses: tauri-apps/tauri-action@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
Expand All @@ -283,6 +284,43 @@ jobs:
prerelease: false
args: ${{ matrix.args }}

# macOS: build first, inject Gatekeeper installer into the DMG, then upload.
- name: Build macOS bundles
id: build_mac
if: matrix.platform == 'macos-latest'
uses: tauri-apps/tauri-action@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
with:
args: ${{ matrix.args }}

- name: Add Gatekeeper installer to macOS DMG
if: matrix.platform == 'macos-latest'
run: |
chmod +x .github/scripts/inject-macos-dmg-installer.sh \
"src-tauri/macos/Install Grimoire.command"
.github/scripts/inject-macos-dmg-installer.sh

- name: Upload macOS release assets
if: matrix.platform == 'macos-latest'
env:
GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TAG: ${{ needs.version-and-release.outputs.tag }}
run: |
set -euo pipefail
mapfile -t ASSETS < <(find src-tauri/target -type f \( \
-path '*/bundle/dmg/*.dmg' -o \
-path '*/bundle/macos/*.tar.gz' \
\) ! -name 'rw.*.dmg' | sort -u)
if [[ ${#ASSETS[@]} -eq 0 ]]; then
echo "No macOS bundle artifacts found to upload" >&2
exit 1
fi
printf 'Uploading:\n'
printf ' %s\n' "${ASSETS[@]}"
gh release upload "${TAG}" "${ASSETS[@]}" --clobber

publish-release:
name: Publish release
needs: [version-and-release, build]
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,16 @@ endif
# Native production build for the machine you are on.
build: ensure-frontend clean-dmg-mounts
$(TAURI) build $(TAURI_BUILD_FLAGS)
ifeq ($(HOST_OS),Darwin)
.github/scripts/inject-macos-dmg-installer.sh
endif

# --- macOS -------------------------------------------------------------------

build-mac: ensure-frontend ensure-mac-targets clean-dmg-mounts
ifeq ($(HOST_OS),Darwin)
$(TAURI) build $(TAURI_BUILD_FLAGS) --target $(MAC_TARGET)
.github/scripts/inject-macos-dmg-installer.sh
else
$(error macOS builds require a Darwin host (got $(HOST_OS)))
endif
Expand All @@ -125,6 +129,7 @@ build-mac-arm: ensure-frontend clean-dmg-mounts
ifeq ($(HOST_OS),Darwin)
@$(RUSTUP) target add $(MAC_ARM_TARGET)
$(TAURI) build $(TAURI_BUILD_FLAGS) --target $(MAC_ARM_TARGET)
.github/scripts/inject-macos-dmg-installer.sh
else
$(error macOS builds require a Darwin host (got $(HOST_OS)))
endif
Expand All @@ -133,6 +138,7 @@ build-mac-intel: ensure-frontend clean-dmg-mounts
ifeq ($(HOST_OS),Darwin)
@$(RUSTUP) target add $(MAC_INTEL_TARGET)
$(TAURI) build $(TAURI_BUILD_FLAGS) --target $(MAC_INTEL_TARGET)
.github/scripts/inject-macos-dmg-installer.sh
else
$(error macOS builds require a Darwin host (got $(HOST_OS)))
endif
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ Windows releases include both:

Both write `%APPDATA%\grimoire\config.json` only when that file does not already exist, so upgrades keep an existing library path.

### macOS install (unsigned builds)

Release DMGs are not Apple-notarized yet. Gatekeeper may say Grimoire is “damaged.” Open the DMG and double-click **Install Grimoire.command** (right-click → Open if needed). That clears quarantine, installs to Applications, and launches the app.
## Library layout

When you choose a library folder, Grimoire writes:
Expand Down
18 changes: 18 additions & 0 deletions src-tauri/macos/How to install.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
How to install Grimoire (macOS)
================================

If macOS says Grimoire is “damaged and can’t be opened”, that is Gatekeeper
blocking an unsigned download. It is not a corrupt file.

Recommended:
1. Open this disk image
2. Double-click “Install Grimoire.command”
(If macOS blocks the script: right-click it → Open → Open)
3. The installer clears Gatekeeper quarantine, copies Grimoire to
Applications, and launches it

Manual alternative (Terminal):
xattr -cr /Applications/Grimoire.app

After you have an Apple Developer account, signed + notarized builds will not
need this step.
53 changes: 53 additions & 0 deletions src-tauri/macos/Install Grimoire.command
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
# Install Grimoire for macOS without an Apple Developer certificate.
# Clears Gatekeeper quarantine (the "damaged and can't be opened" message),
# copies the app to /Applications, and launches it.
set -euo pipefail

clear_quarantine() {
local path="$1"
if [[ -e "$path" ]]; then
xattr -cr "$path" 2>/dev/null || true
fi
}

here="$(cd "$(dirname "$0")" && pwd)"
# Allow Terminal to run this .command even when the DMG is quarantined.
clear_quarantine "$0"
clear_quarantine "$here"

app_src=""
if [[ -d "$here/Grimoire.app" ]]; then
app_src="$here/Grimoire.app"
else
echo "Could not find Grimoire.app next to this installer." >&2
echo "Open the Grimoire .dmg, then double-click \"Install Grimoire.command\" inside it." >&2
read -r -p "Press Return to close…" _
exit 1
fi

dest="/Applications/Grimoire.app"

echo "Installing Grimoire…"
echo " from: $app_src"
echo " to: $dest"
echo

clear_quarantine "$app_src"

if [[ -d "$dest" ]]; then
echo "Removing previous install…"
rm -rf "$dest"
fi

# ditto preserves bundle metadata better than cp -R on macOS.
ditto "$app_src" "$dest"
clear_quarantine "$dest"

echo
echo "Installed. Opening Grimoire…"
open "$dest"

echo
echo "Done. You can eject the disk image."
read -r -p "Press Return to close…" _
Loading