Ariver
2026-06-28 fc6d61e41b1b0e0686d1346694445dc119b97872
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
#!/usr/bin/env bash
#
# build-macappstore-pkg-macos.sh - Build a Mac App Store upload package.
#
# This script uses the App Store sandbox build path, signs the app with an
# Apple-issued distribution identity, embeds the Mac App Store provisioning
# profile, then packages the app as a signed .pkg for App Store Connect upload.
#
# Required environment:
#   PRIVATEVOICE_APPSTORE_APP_IDENTITY
#     Example: "Apple Distribution: Example Team (TEAMID)"
#     Legacy:  "3rd Party Mac Developer Application: Example Team (TEAMID)"
#
#   PRIVATEVOICE_APPSTORE_INSTALLER_IDENTITY
#     Example: "3rd Party Mac Developer Installer: Example Team (TEAMID)"
#
#   PRIVATEVOICE_APPSTORE_PROVISIONING_PROFILE
#     Path to the Mac App Store provisioning profile for com.shanghai3168.privatevoicedictation.
#
# Optional environment:
#   PRIVATEVOICE_SIGNING_KEYCHAIN
#   PRIVATEVOICE_APPSTORE_MIN_SYSTEM_VERSION (default: 13.4)
#   PRIVATEVOICE_MACAPPSTORE_BUILD_ROOT (default: build/macappstore)
#
# Usage:
#   cd privatevoice.src
#   PRIVATEVOICE_APPSTORE_APP_IDENTITY="Apple Distribution: ..." \
#   PRIVATEVOICE_APPSTORE_INSTALLER_IDENTITY="3rd Party Mac Developer Installer: ..." \
#   PRIVATEVOICE_APPSTORE_PROVISIONING_PROFILE="/path/to/profile.provisionprofile" \
#     ./scripts/build-macappstore-pkg-macos.sh arm64
#
set -euo pipefail
 
APP_NAME="PrivateVoice Dictation"
APPSTORE_APP_IDENTITY="${PRIVATEVOICE_APPSTORE_APP_IDENTITY:-}"
APPSTORE_INSTALLER_IDENTITY="${PRIVATEVOICE_APPSTORE_INSTALLER_IDENTITY:-}"
APPSTORE_PROVISIONING_PROFILE="${PRIVATEVOICE_APPSTORE_PROVISIONING_PROFILE:-}"
SIGNING_KEYCHAIN="${PRIVATEVOICE_SIGNING_KEYCHAIN:-}"
APPSTORE_MIN_SYSTEM_VERSION="${PRIVATEVOICE_APPSTORE_MIN_SYSTEM_VERSION:-13.4}"
 
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
MACAPPSTORE_BUILD_ROOT="${PRIVATEVOICE_MACAPPSTORE_BUILD_ROOT:-$PROJECT_DIR/build/macappstore}"
 
INFO_PLIST="$PROJECT_DIR/build/darwin/Info.plist"
BASE_ENTITLEMENTS="$PROJECT_DIR/build/darwin/entitlements-appstore.plist"
GOMODCACHE="$(go env GOMODCACHE)"
SHERPA_MACOS_MODULE_VERSION="$(cd "$PROJECT_DIR" && go list -m -f '{{.Version}}' github.com/k2-fsa/sherpa-onnx-go-macos)"
 
VERSION="$(defaults read "$INFO_PLIST" CFBundleShortVersionString 2>/dev/null || echo "2.1.0")"
BUILD_ID="$(defaults read "$INFO_PLIST" CFBundleVersion 2>/dev/null || date +%Y%m%d.%H%M)"
BUNDLE_ID="$(defaults read "$INFO_PLIST" CFBundleIdentifier 2>/dev/null || echo "")"
 
step() {
  echo ""
  echo "==> $1"
}
 
fail() {
  echo "ERROR: $1" >&2
  exit 1
}
 
security_find_code_identities() {
  if [[ -n "$SIGNING_KEYCHAIN" ]]; then
    security find-identity -v -p codesigning "$SIGNING_KEYCHAIN"
  else
    security find-identity -v -p codesigning
  fi
}
 
security_find_installer_identities() {
  # Installer identities are not codesigning identities; do not filter with -p.
  if [[ -n "$SIGNING_KEYCHAIN" ]]; then
    security find-identity -v "$SIGNING_KEYCHAIN"
  else
    security find-identity -v
  fi
}
 
resolve_arch() {
  local target="$1"
  case "$target" in
    arm64)
      GOARCH="arm64"
      SHERPA_ARCH="aarch64-apple-darwin"
      OUT_ARCH="arm64"
      ;;
    x86_64|amd64)
      GOARCH="amd64"
      SHERPA_ARCH="x86_64-apple-darwin"
      OUT_ARCH="x86_64"
      ;;
    *)
      fail "Unknown architecture: $target (use arm64 or x86_64)"
      ;;
  esac
 
  BUILD_DIR="$MACAPPSTORE_BUILD_ROOT/$OUT_ARCH"
  APP_BUNDLE="$BUILD_DIR/$APP_NAME.app"
  PKG_PATH="$BUILD_DIR/PrivateVoice-Dictation-$VERSION-build$BUILD_ID-$OUT_ARCH-macappstore.pkg"
  SHERPA_LIB_DIR="$GOMODCACHE/github.com/k2-fsa/sherpa-onnx-go-macos@$SHERPA_MACOS_MODULE_VERSION/lib/$SHERPA_ARCH"
  ONNXRUNTIME_DYLIB=""
  ONNXRUNTIME_DYLIB_NAME=""
}
 
sign_plain() {
  local item="$1"
  codesign --force \
    --options runtime \
    --timestamp \
    --sign "$APPSTORE_APP_IDENTITY" \
    "$item"
}
 
sign_sandboxed() {
  local item="$1"
  codesign --force \
    --options runtime \
    --timestamp \
    --entitlements "$SIGNING_ENTITLEMENTS" \
    --sign "$APPSTORE_APP_IDENTITY" \
    "$item"
}
 
plist_set_string() {
  local plist="$1"
  local key="$2"
  local value="$3"
  /usr/libexec/PlistBuddy -c "Delete :$key" "$plist" >/dev/null 2>&1 || true
  /usr/libexec/PlistBuddy -c "Add :$key string $value" "$plist"
}
 
strip_extended_attributes() {
  local item="$1"
  if command -v xattr >/dev/null; then
    chmod -R u+rwX "$item"
    xattr -cr "$item"
  fi
}
 
assert_no_quarantine_attributes() {
  local item="$1"
  if command -v xattr >/dev/null; then
    local found
    found="$(xattr -lr "$item" 2>/dev/null | grep -F "com.apple.quarantine" | head -n 20 || true)"
    [[ -z "$found" ]] || fail "com.apple.quarantine attributes remain under $item:
$found"
  fi
}
 
remove_appledouble_files() {
  local item="$1"
  find "$item" \( -name '._*' -o -name '.DS_Store' \) -delete
}
 
assert_no_appledouble_files() {
  local item="$1"
  local found
  found="$(find "$item" \( -name '._*' -o -name '.DS_Store' \) -print | head -n 20 || true)"
  [[ -z "$found" ]] || fail "AppleDouble or Finder metadata files remain under $item:
$found"
}
 
assert_pkg_no_quarantine_attributes() {
  local pkg="$1"
  local expanded
  local found
  expanded="$(mktemp -d)"
  pkgutil --expand-full "$pkg" "$expanded/pkg" >/dev/null
  found="$(xattr -lr "$expanded/pkg" 2>/dev/null | grep -F "com.apple.quarantine" | head -n 20 || true)"
  rm -rf "$expanded"
  [[ -z "$found" ]] || fail "com.apple.quarantine attributes remain in package:
$found"
}
 
TARGET_ARCH="${1:-$(uname -m)}"
resolve_arch "$TARGET_ARCH"
 
step "Preflight checks"
[[ -n "$APPSTORE_APP_IDENTITY" ]] || fail "PRIVATEVOICE_APPSTORE_APP_IDENTITY is required"
[[ -n "$APPSTORE_INSTALLER_IDENTITY" ]] || fail "PRIVATEVOICE_APPSTORE_INSTALLER_IDENTITY is required"
[[ -n "$APPSTORE_PROVISIONING_PROFILE" ]] || fail "PRIVATEVOICE_APPSTORE_PROVISIONING_PROFILE is required"
[[ -f "$APPSTORE_PROVISIONING_PROFILE" ]] || fail "Provisioning profile not found: $APPSTORE_PROVISIONING_PROFILE"
[[ -f "$INFO_PLIST" ]] || fail "Info.plist not found at $INFO_PLIST"
[[ -n "$BUNDLE_ID" ]] || fail "CFBundleIdentifier is missing from $INFO_PLIST"
[[ -f "$BASE_ENTITLEMENTS" ]] || fail "App Store entitlements not found at $BASE_ENTITLEMENTS"
[[ -f "$PROJECT_DIR/build/darwin/icon.icns" ]] || fail "icon.icns not found"
[[ -d "$SHERPA_LIB_DIR" ]] || fail "sherpa-onnx dylibs not found at $SHERPA_LIB_DIR"
ONNXRUNTIME_DYLIB="$(find "$SHERPA_LIB_DIR" -maxdepth 1 -type f -name 'libonnxruntime.*.dylib' | sort | tail -n 1)"
[[ -f "$ONNXRUNTIME_DYLIB" ]] || fail "onnxruntime dylib not found in $SHERPA_LIB_DIR"
ONNXRUNTIME_DYLIB_NAME="$(basename "$ONNXRUNTIME_DYLIB")"
security_find_code_identities | grep -Fq "$APPSTORE_APP_IDENTITY" ||
  fail "App Store app signing identity not found: $APPSTORE_APP_IDENTITY"
security_find_installer_identities | grep -Fq "$APPSTORE_INSTALLER_IDENTITY" ||
  fail "App Store installer signing identity not found: $APPSTORE_INSTALLER_IDENTITY"
command -v productbuild >/dev/null || fail "productbuild not found"
command -v pkgutil >/dev/null || fail "pkgutil not found"
 
PROFILE_PLIST="$(mktemp)"
SIGNING_ENTITLEMENTS="$(mktemp)"
trap 'rm -f "$PROFILE_PLIST" "$SIGNING_ENTITLEMENTS"' EXIT
security cms -D -i "$APPSTORE_PROVISIONING_PROFILE" >"$PROFILE_PLIST" ||
  fail "Unable to decode provisioning profile: $APPSTORE_PROVISIONING_PROFILE"
PROFILE_APP_IDENTIFIER="$(/usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.application-identifier" "$PROFILE_PLIST" 2>/dev/null || true)"
PROFILE_TEAM_IDENTIFIER="$(/usr/libexec/PlistBuddy -c "Print :Entitlements:com.apple.developer.team-identifier" "$PROFILE_PLIST" 2>/dev/null || true)"
PROFILE_KEYCHAIN_GROUP="$(/usr/libexec/PlistBuddy -c "Print :Entitlements:keychain-access-groups:0" "$PROFILE_PLIST" 2>/dev/null || true)"
PROFILE_BUNDLE_ID="${PROFILE_APP_IDENTIFIER#*.}"
[[ "$PROFILE_BUNDLE_ID" == "$BUNDLE_ID" ]] ||
  fail "Provisioning profile bundle ID mismatch: profile has $PROFILE_BUNDLE_ID, app has $BUNDLE_ID"
[[ -n "$PROFILE_TEAM_IDENTIFIER" ]] || fail "Provisioning profile missing com.apple.developer.team-identifier entitlement"
 
cp "$BASE_ENTITLEMENTS" "$SIGNING_ENTITLEMENTS"
plist_set_string "$SIGNING_ENTITLEMENTS" "com.apple.application-identifier" "$PROFILE_APP_IDENTIFIER"
plist_set_string "$SIGNING_ENTITLEMENTS" "com.apple.developer.team-identifier" "$PROFILE_TEAM_IDENTIFIER"
if [[ -n "$PROFILE_KEYCHAIN_GROUP" ]]; then
  /usr/libexec/PlistBuddy -c "Delete :keychain-access-groups" "$SIGNING_ENTITLEMENTS" >/dev/null 2>&1 || true
  /usr/libexec/PlistBuddy -c "Add :keychain-access-groups array" "$SIGNING_ENTITLEMENTS"
  /usr/libexec/PlistBuddy -c "Add :keychain-access-groups:0 string $PROFILE_KEYCHAIN_GROUP" "$SIGNING_ENTITLEMENTS"
fi
 
step "Cleaning old Mac App Store build"
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR"
 
step "Building frontend"
cd "$PROJECT_DIR/frontend"
if [[ ! -d node_modules ]]; then
  npm ci --prefer-offline
fi
npm run build
 
step "Building Go binary ($OUT_ARCH, appstore tag)"
cd "$PROJECT_DIR"
CGO_ENABLED=1 GOOS=darwin GOARCH="$GOARCH" \
  MACOSX_DEPLOYMENT_TARGET="$APPSTORE_MIN_SYSTEM_VERSION" \
  CGO_CFLAGS="${CGO_CFLAGS:-} -mmacosx-version-min=$APPSTORE_MIN_SYSTEM_VERSION" \
  CGO_LDFLAGS="${CGO_LDFLAGS:-} -mmacosx-version-min=$APPSTORE_MIN_SYSTEM_VERSION" \
  go build -tags appstore -buildvcs=false -gcflags=all="-l" -ldflags="-s -w" \
  -o "$BUILD_DIR/$APP_NAME"
 
step "Assembling $APP_NAME.app"
mkdir -p "$APP_BUNDLE/Contents/MacOS"
mkdir -p "$APP_BUNDLE/Contents/Frameworks"
mkdir -p "$APP_BUNDLE/Contents/Resources"
 
cp "$INFO_PLIST" "$APP_BUNDLE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion $APPSTORE_MIN_SYSTEM_VERSION" \
  "$APP_BUNDLE/Contents/Info.plist"
cp "$APPSTORE_PROVISIONING_PROFILE" "$APP_BUNDLE/Contents/embedded.provisionprofile"
cp "$BUILD_DIR/$APP_NAME" "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
cp "$PROJECT_DIR/build/darwin/icon.icns" "$APP_BUNDLE/Contents/Resources/icon.icns"
find "$PROJECT_DIR/build/darwin" -maxdepth 1 -type d -name "*.lproj" \
  -exec cp -R {} "$APP_BUNDLE/Contents/Resources/" \;
cp "$SHERPA_LIB_DIR/libsherpa-onnx-c-api.dylib" "$APP_BUNDLE/Contents/Frameworks/"
cp "$ONNXRUNTIME_DYLIB" "$APP_BUNDLE/Contents/Frameworks/"
 
step "Removing extended attributes from app bundle"
strip_extended_attributes "$APP_BUNDLE"
remove_appledouble_files "$APP_BUNDLE"
assert_no_quarantine_attributes "$APP_BUNDLE"
assert_no_appledouble_files "$APP_BUNDLE"
 
step "Fixing rpaths"
install_name_tool -add_rpath @executable_path/../Frameworks \
  "$APP_BUNDLE/Contents/MacOS/$APP_NAME" 2>/dev/null || true
install_name_tool -id @rpath/libsherpa-onnx-c-api.dylib \
  "$APP_BUNDLE/Contents/Frameworks/libsherpa-onnx-c-api.dylib"
install_name_tool -id "@rpath/$ONNXRUNTIME_DYLIB_NAME" \
  "$APP_BUNDLE/Contents/Frameworks/$ONNXRUNTIME_DYLIB_NAME"
 
step "Code signing for Mac App Store"
sign_plain "$APP_BUNDLE/Contents/Frameworks/$ONNXRUNTIME_DYLIB_NAME"
sign_plain "$APP_BUNDLE/Contents/Frameworks/libsherpa-onnx-c-api.dylib"
sign_sandboxed "$APP_BUNDLE/Contents/MacOS/$APP_NAME"
sign_sandboxed "$APP_BUNDLE"
 
step "Removing extended attributes after signing"
strip_extended_attributes "$APP_BUNDLE"
remove_appledouble_files "$APP_BUNDLE"
assert_no_quarantine_attributes "$APP_BUNDLE"
assert_no_appledouble_files "$APP_BUNDLE"
 
step "Verifying app signature and entitlements"
codesign -vvv --deep --strict "$APP_BUNDLE"
codesign -d --entitlements :- "$APP_BUNDLE"
SIGNED_ENTITLEMENTS="$(mktemp)"
codesign -d --entitlements :- "$APP_BUNDLE" >"$SIGNED_ENTITLEMENTS" 2>/dev/null
SIGNED_APP_IDENTIFIER="$(/usr/libexec/PlistBuddy -c "Print :com.apple.application-identifier" "$SIGNED_ENTITLEMENTS" 2>/dev/null || true)"
rm -f "$SIGNED_ENTITLEMENTS"
[[ "$SIGNED_APP_IDENTIFIER" == "$PROFILE_APP_IDENTIFIER" ]] ||
  fail "Signed app application-identifier mismatch: signed has $SIGNED_APP_IDENTIFIER, profile has $PROFILE_APP_IDENTIFIER"
 
step "Creating signed installer package"
COPYFILE_DISABLE=1 productbuild \
  --sign "$APPSTORE_INSTALLER_IDENTITY" \
  --component "$APP_BUNDLE" /Applications \
  "$PKG_PATH"
 
step "Verifying installer package signature"
pkgutil --check-signature "$PKG_PATH"
assert_pkg_no_quarantine_attributes "$PKG_PATH"
 
step "Mac App Store package complete"
echo "  App: $APP_BUNDLE"
echo "  PKG: $PKG_PATH"
echo "  App signing identity: $APPSTORE_APP_IDENTITY"
echo "  Installer signing identity: $APPSTORE_INSTALLER_IDENTITY"