Skip to content

Commit 490668d

Browse files
Merge pull request #126 from datum-cloud/improve-macos-install-dmg
Improve macOS install DMG
2 parents 99171b1 + 15dffc0 commit 490668d

14 files changed

+792
-2
lines changed

.DS_Store

6 KB
Binary file not shown.

.github/workflows/bundle.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ jobs:
5151
env:
5252
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5353

54+
- uses: actions/setup-node@v4
55+
if: runner.os == 'macOS'
56+
with:
57+
node-version: "22"
58+
cache: npm
59+
cache-dependency-path: ui/packaging/dmg/package-lock.json
60+
61+
- name: Install appdmg (npm ci)
62+
if: runner.os == 'macOS'
63+
working-directory: ui/packaging/dmg
64+
run: npm ci
65+
5466
- name: Import Apple Certificate
5567
if: runner.os == 'macOS'
5668
env:
@@ -139,7 +151,34 @@ jobs:
139151
- name: Bundle (macOS)
140152
if: runner.os == 'macOS'
141153
working-directory: ./ui
142-
run: dx bundle --locked --desktop --release --package-types dmg
154+
run: dx bundle --locked --desktop --release --package-types macos
155+
156+
- name: Build DMG (appdmg)
157+
if: runner.os == 'macOS'
158+
env:
159+
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
160+
run: |
161+
mkdir -p ui/dist
162+
if [ ! -d ui/dist/Datum.app ] && [ -d ui/target/dx/Datum/release/macos/Datum.app ]; then
163+
cp -R ui/target/dx/Datum/release/macos/Datum.app ui/dist/
164+
fi
165+
test -d ui/dist/Datum.app
166+
./ui/packaging/dmg/mk-dmg-icns-from-linux-png.sh
167+
if [ -f ui/packaging/dmg/dmg-background@2x.png ]; then
168+
./ui/packaging/dmg/sync-dmg-background-1x.sh
169+
fi
170+
rm -f ui/dist/*.dmg
171+
./ui/packaging/dmg/node_modules/.bin/appdmg \
172+
ui/packaging/dmg/appdmg.json \
173+
ui/dist/Datum.dmg
174+
./ui/packaging/dmg/apply-dmg-finder-icon.sh \
175+
ui/dist/Datum.dmg \
176+
ui/packaging/dmg/Datum-dmg.icns
177+
if [ -z "${APPLE_SIGNING_IDENTITY:-}" ]; then
178+
echo "APPLE_SIGNING_IDENTITY is empty"
179+
exit 1
180+
fi
181+
codesign --force --sign "$APPLE_SIGNING_IDENTITY" --timestamp ui/dist/Datum.dmg
143182
144183
- name: Upload Debug Symbols to Sentry (macOS)
145184
if: runner.os == 'macOS' && startsWith(github.ref, 'refs/tags/v')

.github/workflows/manual-release.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ jobs:
5555
env:
5656
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5757

58+
- uses: actions/setup-node@v4
59+
if: runner.os == 'macOS'
60+
with:
61+
node-version: "22"
62+
cache: npm
63+
cache-dependency-path: ui/packaging/dmg/package-lock.json
64+
65+
- name: Install appdmg (npm ci)
66+
if: runner.os == 'macOS'
67+
working-directory: ui/packaging/dmg
68+
run: npm ci
69+
5870
- name: Import Apple Certificate
5971
if: runner.os == 'macOS'
6072
env:
@@ -125,7 +137,34 @@ jobs:
125137
- name: Bundle (macOS)
126138
if: runner.os == 'macOS'
127139
working-directory: ./ui
128-
run: dx bundle --locked --desktop --release --package-types dmg
140+
run: dx bundle --locked --desktop --release --package-types macos
141+
142+
- name: Build DMG (appdmg)
143+
if: runner.os == 'macOS'
144+
env:
145+
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
146+
run: |
147+
mkdir -p ui/dist
148+
if [ ! -d ui/dist/Datum.app ] && [ -d ui/target/dx/Datum/release/macos/Datum.app ]; then
149+
cp -R ui/target/dx/Datum/release/macos/Datum.app ui/dist/
150+
fi
151+
test -d ui/dist/Datum.app
152+
./ui/packaging/dmg/mk-dmg-icns-from-linux-png.sh
153+
if [ -f ui/packaging/dmg/dmg-background@2x.png ]; then
154+
./ui/packaging/dmg/sync-dmg-background-1x.sh
155+
fi
156+
rm -f ui/dist/*.dmg
157+
./ui/packaging/dmg/node_modules/.bin/appdmg \
158+
ui/packaging/dmg/appdmg.json \
159+
ui/dist/Datum.dmg
160+
./ui/packaging/dmg/apply-dmg-finder-icon.sh \
161+
ui/dist/Datum.dmg \
162+
ui/packaging/dmg/Datum-dmg.icns
163+
if [ -z "${APPLE_SIGNING_IDENTITY:-}" ]; then
164+
echo "APPLE_SIGNING_IDENTITY is empty"
165+
exit 1
166+
fi
167+
codesign --force --sign "$APPLE_SIGNING_IDENTITY" --timestamp ui/dist/Datum.dmg
129168
130169
- name: Notarize macOS App
131170
if: runner.os == 'macOS'

ui/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
**/*.rs.bk
88

99
/node_modules
10+
/packaging/dmg/node_modules
11+
/packaging/dmg/appdmg.build.json
12+
/packaging/dmg/Datum-dmg.iconset

ui/packaging/dmg/Datum-dmg.icns

1.85 MB
Binary file not shown.

ui/packaging/dmg/appdmg.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"title": "Install Datum",
3+
"icon": "Datum-dmg.icns",
4+
"background": "dmg-background.png",
5+
"icon-size": 80,
6+
"window": {
7+
"position": {
8+
"x": 400,
9+
"y": 400
10+
}
11+
},
12+
"contents": [
13+
{ "x": 165, "y": 200, "type": "file", "path": "../../dist/Datum.app" },
14+
{ "x": 370, "y": 200, "type": "link", "path": "/Applications" },
15+
{ "x": 1200, "y": 800, "type": "position", "path": ".background" },
16+
{ "x": 1200, "y": 800, "type": "position", "path": ".DS_Store" },
17+
{ "x": 1200, "y": 800, "type": "position", "path": ".Trashes" }
18+
]
19+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env bash
2+
# Embed a custom Finder icon on the .dmg file itself (not the mounted volume).
3+
# appdmg's "icon" only sets the volume window / mount icon; the downloaded file
4+
# stays generic until icon data is written via NSWorkspace (resource fork + FinderInfo).
5+
#
6+
# Usage: apply-dmg-finder-icon.sh <path-to.dmg> <path-to.icns>
7+
# Run AFTER appdmg creates the DMG and BEFORE codesigning the DMG.
8+
#
9+
set -euo pipefail
10+
11+
if [[ "$(uname -s)" != "Darwin" ]]; then
12+
echo "This script only runs on macOS." >&2
13+
exit 1
14+
fi
15+
16+
if [[ $# -ne 2 ]]; then
17+
echo "Usage: $0 <file.dmg> <icon.icns>" >&2
18+
exit 2
19+
fi
20+
21+
DMG=$(cd "$(dirname "$1")" && pwd)/$(basename "$1")
22+
ICNS=$(cd "$(dirname "$2")" && pwd)/$(basename "$2")
23+
24+
if [[ ! -f "$DMG" ]]; then
25+
echo "DMG not found: $DMG" >&2
26+
exit 1
27+
fi
28+
if [[ ! -f "$ICNS" ]]; then
29+
echo ".icns not found: $ICNS" >&2
30+
exit 1
31+
fi
32+
33+
export APPLY_DMG_TARGET="$DMG"
34+
export APPLY_DMG_ICNS="$ICNS"
35+
36+
# Swift is present on GitHub Actions macOS images and developer Macs.
37+
swift - <<'SWIFT'
38+
import AppKit
39+
40+
guard let icns = ProcessInfo.processInfo.environment["APPLY_DMG_ICNS"],
41+
let dmg = ProcessInfo.processInfo.environment["APPLY_DMG_TARGET"] else {
42+
fputs("Missing APPLY_DMG_ICNS or APPLY_DMG_TARGET\n", stderr)
43+
exit(1)
44+
}
45+
46+
guard let image = NSImage(contentsOfFile: icns) else {
47+
fputs("Failed to load .icns at \(icns)\n", stderr)
48+
exit(1)
49+
}
50+
51+
if !NSWorkspace.shared.setIcon(image, forFile: dmg, options: []) {
52+
fputs("NSWorkspace.setIcon returned false for \(dmg)\n", stderr)
53+
exit(1)
54+
}
55+
SWIFT

ui/packaging/dmg/build-dmg.sh

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env bash
2+
# Build Datum.dmg locally using the same appdmg.json as CI.
3+
# Run from anywhere; paths are resolved from this script's location.
4+
#
5+
# Usage:
6+
# export APPLE_SIGNING_IDENTITY="Developer ID Application: … (TEAMID)"
7+
# ./build-dmg.sh # expects ui/dist/Datum.app (or copies from target/dx)
8+
# ./build-dmg.sh --bundle # runs dx bundle --package-types macos first
9+
# ./build-dmg.sh --unsigned # omit codesign on the .dmg (layout / quick test only)
10+
#
11+
set -euo pipefail
12+
13+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14+
UI_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
15+
REPO_ROOT="$(cd "$UI_ROOT/.." && pwd)"
16+
DMG_DIR="$SCRIPT_DIR"
17+
export DMG_DIR
18+
APPDMG_SPEC="$DMG_DIR/appdmg.json"
19+
ICNS_FOR_DMG="$DMG_DIR/Datum-dmg.icns"
20+
OUT_DMG="${OUT_DMG:-$UI_ROOT/dist/Datum.dmg}"
21+
22+
DO_BUNDLE=false
23+
UNSIGNED=false
24+
25+
for arg in "$@"; do
26+
case "$arg" in
27+
--bundle) DO_BUNDLE=true ;;
28+
--unsigned) UNSIGNED=true ;;
29+
-h|--help)
30+
cat <<'EOF'
31+
Build Datum.dmg locally using the same appdmg.json as CI.
32+
33+
Usage:
34+
export APPLE_SIGNING_IDENTITY="Developer ID Application: … (TEAMID)"
35+
ui/packaging/dmg/build-dmg.sh # needs ui/dist/Datum.app (or target/dx copy)
36+
ui/packaging/dmg/build-dmg.sh --bundle # dx bundle --package-types macos first
37+
ui/packaging/dmg/build-dmg.sh --unsigned # no codesign on .dmg (layout test only)
38+
39+
Override output path: OUT_DMG=/path/out.dmg ui/packaging/dmg/build-dmg.sh
40+
EOF
41+
exit 0
42+
;;
43+
*)
44+
echo "Unknown option: $arg" >&2
45+
exit 1
46+
;;
47+
esac
48+
done
49+
50+
if [[ "$(uname -s)" != "Darwin" ]]; then
51+
echo "appdmg only runs on macOS." >&2
52+
exit 1
53+
fi
54+
55+
if [[ "$DO_BUNDLE" == true ]]; then
56+
(cd "$UI_ROOT" && dx bundle --locked --desktop --release --package-types macos)
57+
fi
58+
59+
mkdir -p "$UI_ROOT/dist"
60+
if [[ ! -d "$UI_ROOT/dist/Datum.app" ]] && [[ -d "$UI_ROOT/target/dx/Datum/release/macos/Datum.app" ]]; then
61+
echo "Copying Datum.app from target/dx → dist/"
62+
cp -R "$UI_ROOT/target/dx/Datum/release/macos/Datum.app" "$UI_ROOT/dist/"
63+
fi
64+
65+
if [[ ! -d "$UI_ROOT/dist/Datum.app" ]]; then
66+
echo "Datum.app not found. Run from repo with a built app, e.g.:" >&2
67+
echo " (cd ui && dx bundle --locked --desktop --release --package-types macos)" >&2
68+
echo "Or pass --bundle to this script." >&2
69+
exit 1
70+
fi
71+
72+
if [[ ! -d "$DMG_DIR/node_modules" ]]; then
73+
echo "Installing npm dependencies (appdmg)…"
74+
(cd "$DMG_DIR" && npm ci)
75+
fi
76+
77+
if [[ "$UNSIGNED" != true ]] && [[ -z "${APPLE_SIGNING_IDENTITY:-}" ]]; then
78+
echo "APPLE_SIGNING_IDENTITY is not set. Export it (same as CI), or use --unsigned." >&2
79+
exit 1
80+
fi
81+
82+
echo "Building DMG .icns from Linux bundle art (rounded)…"
83+
"$DMG_DIR/mk-dmg-icns-from-linux-png.sh"
84+
85+
if [[ -f "$DMG_DIR/dmg-background@2x.png" ]]; then
86+
echo "Syncing 1× DMG background from @2x…"
87+
"$DMG_DIR/sync-dmg-background-1x.sh"
88+
fi
89+
90+
rm -f "$UI_ROOT/dist"/*.dmg
91+
echo "Writing $OUT_DMG"
92+
(cd "$REPO_ROOT" && "$DMG_DIR/node_modules/.bin/appdmg" "$APPDMG_SPEC" "$OUT_DMG")
93+
94+
echo "Applying Finder icon to .dmg file…"
95+
"$DMG_DIR/apply-dmg-finder-icon.sh" "$OUT_DMG" "$ICNS_FOR_DMG"
96+
97+
if [[ "$UNSIGNED" != true ]]; then
98+
echo "Codesigning DMG…"
99+
codesign --force --sign "$APPLE_SIGNING_IDENTITY" --timestamp "$OUT_DMG"
100+
fi
101+
102+
echo "Done: $OUT_DMG"
34.6 KB
Loading
70.8 KB
Loading

0 commit comments

Comments
 (0)