-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathbuild.sh
More file actions
executable file
·363 lines (316 loc) · 11 KB
/
Copy pathbuild.sh
File metadata and controls
executable file
·363 lines (316 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/bin/bash
set -euo pipefail
APP_NAME="${APP_NAME:-AgentBro}"
BUNDLE_ID="${BUNDLE_ID:-com.agentbro.desktop}"
BUILD_DIR="src-tauri/target"
DIST_DIR="dist"
DMG_NAME="${DMG_NAME:-AgentBro.dmg}"
APP_VERSION="${APP_VERSION:-$(node -e "console.log(JSON.parse(require('fs').readFileSync('src-tauri/tauri.conf.json', 'utf8')).version)" 2>/dev/null || echo "0.0.0")}"
UPDATE_ARCHIVE_NAME="${UPDATE_ARCHIVE_NAME:-$APP_NAME.app.tar.gz}"
UPDATE_MANIFEST_NAME="${UPDATE_MANIFEST_NAME:-latest.json}"
NOTARIZE=false
SKIP_SIGN="${SKIP_SIGN:-0}"
SKIP_NOTARIZE="${SKIP_NOTARIZE:-0}"
usage() {
cat <<'EOF'
Usage: ./build.sh [--notarize] [--help]
--notarize Sign and notarize the app bundle and DMG
--help Show this help
Environment variables:
SKIP_SIGN=1 Skip code signing
SKIP_NOTARIZE=1 Skip notarization (signing still happens)
CODESIGN_IDENTITY Code signing identity (Developer ID Application: ...)
APPLE_ID Apple ID for notarization
APPLE_PASSWORD App-specific password for notarization
APPLE_TEAM_ID Apple Team ID
UPDATE_BASE_URL Base URL for updater archive links in latest.json
EOF
}
for arg in "$@"; do
case "$arg" in
--notarize)
NOTARIZE=true
;;
--help|-h)
usage
exit 0
;;
*)
echo "Unknown option: $arg" >&2
usage >&2
exit 1
;;
esac
done
check_deps() {
local missing=()
for cmd in cargo pnpm node tar shasum create-dmg lipo ditto; do
if ! command -v "$cmd" >/dev/null 2>&1; then
missing+=("$cmd")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
echo "Missing dependencies: ${missing[*]}" >&2
echo "Install with: brew install create-dmg && cargo --version && pnpm --version && node --version" >&2
exit 1
fi
}
build_app_bundle() {
echo "==> Building Tauri universal app bundle..."
local tauri_app="$BUILD_DIR/universal-apple-darwin/release/bundle/macos/$APP_NAME.app"
local bridge_out="$BUILD_DIR/universal-apple-darwin/release/agentbro-bridge"
echo "==> Building universal bridge helper..."
mkdir -p "$BUILD_DIR/agentbro-bridge-resource"
touch "$BUILD_DIR/agentbro-bridge-resource/agentbro-bridge"
cargo build --release --target aarch64-apple-darwin --bin agentbro-bridge --manifest-path src-tauri/Cargo.toml
cargo build --release --target x86_64-apple-darwin --bin agentbro-bridge --manifest-path src-tauri/Cargo.toml
mkdir -p "$(dirname "$bridge_out")"
lipo -create \
"$BUILD_DIR/aarch64-apple-darwin/release/agentbro-bridge" \
"$BUILD_DIR/x86_64-apple-darwin/release/agentbro-bridge" \
-output "$bridge_out"
cp "$bridge_out" "$BUILD_DIR/agentbro-bridge-resource/agentbro-bridge"
chmod +x "$BUILD_DIR/agentbro-bridge-resource/agentbro-bridge"
cargo tauri build \
--target universal-apple-darwin \
--bundles app \
--no-sign \
--ci \
--config '{"bundle":{"createUpdaterArtifacts":false}}'
if [ ! -d "$tauri_app" ]; then
echo "Tauri app bundle not found: $tauri_app" >&2
exit 1
fi
rm -rf "$DIST_DIR/$APP_NAME.app"
mkdir -p "$DIST_DIR"
cp -R "$tauri_app" "$DIST_DIR/$APP_NAME.app"
}
sign_app() {
if [ "$SKIP_SIGN" = "1" ]; then
echo "==> Skipping code signing (SKIP_SIGN=1)"
return
fi
local identity="${CODESIGN_IDENTITY:-}"
if [ -z "$identity" ]; then
echo "==> No CODESIGN_IDENTITY set, skipping signing"
return
fi
echo "==> Signing app bundle..."
local entitlements="src-tauri/Entitlements.plist"
local app_path="$DIST_DIR/$APP_NAME.app"
local resource_bridge="$app_path/Contents/Resources/agentbro-bridge"
while IFS= read -r binary; do
if [ ! -x "$binary" ]; then
continue
fi
echo "==> Signing executable: $binary"
codesign --force --timestamp --options runtime \
--entitlements "$entitlements" \
--sign "$identity" \
"$binary"
done < <(find "$app_path/Contents/MacOS" -type f)
if [ -f "$resource_bridge" ]; then
chmod +x "$resource_bridge"
echo "==> Signing executable resource: $resource_bridge"
codesign --force --timestamp --options runtime \
--entitlements "$entitlements" \
--sign "$identity" \
"$resource_bridge"
fi
echo "==> Signing app container..."
codesign --force --timestamp --options runtime \
--entitlements "$entitlements" \
--sign "$identity" \
"$app_path"
echo "==> Verifying signature..."
codesign --verify --strict --verbose=2 "$app_path"
codesign -dv --verbose=4 "$app_path"
spctl --assess --type exec "$app_path" 2>/dev/null || echo "Note: spctl assessment skipped (no Gatekeeper on CI)"
}
create_updater_archive() {
echo "==> Creating updater archive..."
local archive_path="$DIST_DIR/$UPDATE_ARCHIVE_NAME"
rm -f "$archive_path" "$archive_path.sig"
tar -czf "$archive_path" -C "$DIST_DIR" "$APP_NAME.app"
if [ -n "${TAURI_SIGNING_PRIVATE_KEY:-}" ] || [ -n "${TAURI_SIGNING_PRIVATE_KEY_PATH:-}" ]; then
echo "==> Signing updater archive..."
cargo tauri signer sign -p "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" "$archive_path" >/dev/null
else
echo "==> Skipping updater archive signing (TAURI_SIGNING_PRIVATE_KEY not set)"
fi
if [ -n "${UPDATE_BASE_URL:-}" ] && [ -f "$archive_path.sig" ]; then
echo "==> Writing updater manifest..."
VERSION="$APP_VERSION" \
UPDATE_ARCHIVE_PATH="$archive_path" \
UPDATE_ARCHIVE_URL="${UPDATE_BASE_URL%/}/$UPDATE_ARCHIVE_NAME" \
UPDATE_SIGNATURE_PATH="$archive_path.sig" \
UPDATE_MANIFEST_PATH="$DIST_DIR/$UPDATE_MANIFEST_NAME" \
node scripts/create-updater-manifest.mjs
fi
}
create_dmg() {
echo "==> Creating DMG..."
mkdir -p "$DIST_DIR"
local app_path="$DIST_DIR/$APP_NAME.app"
local dmg_path="$DIST_DIR/$DMG_NAME"
rm -f "$dmg_path"
create-dmg \
--volname "$APP_NAME" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "$APP_NAME.app" 175 190 \
--hide-extension "$APP_NAME.app" \
--app-drop-link 425 190 \
"$dmg_path" \
"$app_path"
echo "DMG created: $dmg_path"
}
sign_dmg() {
if [ "$SKIP_SIGN" = "1" ]; then
return
fi
local identity="${CODESIGN_IDENTITY:-}"
if [ -z "$identity" ]; then
return
fi
echo "==> Signing DMG..."
codesign --force --sign "$identity" "$DIST_DIR/$DMG_NAME"
}
json_field() {
node -e '
const field = process.argv[1];
let input = "";
process.stdin.on("data", chunk => input += chunk);
process.stdin.on("end", () => {
const data = JSON.parse(input);
const value = field.split(".").reduce((acc, key) => acc && acc[key], data);
if (value !== undefined && value !== null) process.stdout.write(String(value));
});
' "$1"
}
notary_submit_and_wait() {
local target_path="$1"
local apple_id="${APPLE_ID:-}"
local apple_password="${APPLE_PASSWORD:-}"
local team_id="${APPLE_TEAM_ID:-}"
if [ -z "$apple_id" ] || [ -z "$apple_password" ] || [ -z "$team_id" ]; then
echo "==> Notarization credentials not set, skipping"
return 1
fi
local timeout_seconds="${NOTARY_TIMEOUT_SECONDS:-5400}"
local poll_seconds="${NOTARY_POLL_SECONDS:-60}"
local submitted_at
local submission_output
local submission_id
submitted_at="$(date +%s)"
print_notary_log() {
local id="$1"
echo "==> Notarization log for $id"
xcrun notarytool log "$id" \
--apple-id "$apple_id" \
--password "$apple_password" \
--team-id "$team_id" || true
}
echo "==> Submitting for notarization: $target_path"
submission_output="$(xcrun notarytool submit "$target_path" \
--apple-id "$apple_id" \
--password "$apple_password" \
--team-id "$team_id" \
--output-format json)"
echo "$submission_output"
submission_id="$(printf '%s' "$submission_output" | json_field id)"
if [ -z "$submission_id" ]; then
echo "Unable to read notarization submission id" >&2
exit 1
fi
echo "==> Notarization submission id: $submission_id"
while true; do
local info_output
local status
info_output="$(xcrun notarytool info "$submission_id" \
--apple-id "$apple_id" \
--password "$apple_password" \
--team-id "$team_id" \
--output-format json)"
echo "$info_output"
status="$(printf '%s' "$info_output" | json_field status)"
case "$status" in
Accepted)
break
;;
Invalid|Rejected)
print_notary_log "$submission_id"
exit 1
;;
*)
local now
now="$(date +%s)"
if [ $((now - submitted_at)) -ge "$timeout_seconds" ]; then
echo "Timed out waiting for notarization after ${timeout_seconds}s" >&2
print_notary_log "$submission_id"
exit 124
fi
echo "Current notarization status: ${status:-unknown}; waiting ${poll_seconds}s..."
sleep "$poll_seconds"
;;
esac
done
}
notarize_app_bundle() {
if [ "$SKIP_NOTARIZE" = "1" ] || [ "$NOTARIZE" = "false" ]; then
echo "==> Skipping notarization"
return
fi
local app_path="$DIST_DIR/$APP_NAME.app"
local zip_path="$DIST_DIR/$APP_NAME.app.zip"
if [ ! -d "$app_path" ]; then
echo "App bundle not found: $app_path" >&2
exit 1
fi
echo "==> Creating app notarization archive..."
rm -f "$zip_path"
ditto -c -k --keepParent "$app_path" "$zip_path"
if ! notary_submit_and_wait "$zip_path"; then
rm -f "$zip_path"
return
fi
rm -f "$zip_path"
echo "==> Stapling notarization ticket to app..."
xcrun stapler staple "$app_path"
xcrun stapler validate "$app_path"
}
notarize_dmg() {
if [ "$SKIP_NOTARIZE" = "1" ] || [ "$NOTARIZE" = "false" ]; then
echo "==> Skipping notarization"
return
fi
local dmg_path="$DIST_DIR/$DMG_NAME"
if ! notary_submit_and_wait "$dmg_path"; then
return
fi
echo "==> Stapling notarization ticket..."
xcrun stapler staple "$dmg_path"
xcrun stapler validate "$dmg_path"
}
create_checksums() {
echo "==> Creating checksums..."
find "$DIST_DIR" -maxdepth 1 -type f \
\( -name "*.dmg" -o -name "*.tar.gz" -o -name "*.sig" -o -name "*.json" \) \
-print0 |
sort -z |
xargs -0 shasum -a 256 > "$DIST_DIR/checksums.txt"
}
main() {
check_deps
build_app_bundle
sign_app
notarize_app_bundle
create_updater_archive
create_dmg
sign_dmg
notarize_dmg
create_checksums
echo "==> Build complete: $DIST_DIR/$DMG_NAME"
}
main