Introduce make lint-shell / fmt-shell with shellcheck and shfmt (-i 2 -ci -bn), normalize first-party scripts, and document the new targets. Keep them out of make lint / fmt. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
473 lines
16 KiB
Bash
Executable File
473 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Build a Probo device posture agent macOS installer (.pkg) from a
|
|
# pre-built fat `probo-agent` binary (arm64 + x86_64).
|
|
#
|
|
# Required arguments:
|
|
# --binary PATH Path to a compiled probo-agent fat binary.
|
|
# --version VER Agent version, e.g. 0.1.0. Defaults to the
|
|
# content of cmd/probo-agent/VERSION.
|
|
# --output PATH Output .pkg path. Defaults to
|
|
# dist/probo-agent_${VER}_darwin.pkg.
|
|
#
|
|
# Required environment variables:
|
|
# CODESIGN_IDENTITY Developer ID Application identity. Signs the
|
|
# agent binary, Probo Agent.app, and embedded helper.
|
|
# APPLE_TEAM_ID Apple Developer Team ID (helper client requirement).
|
|
#
|
|
# Optional (auditor-mode compatible):
|
|
# INSTALLER_IDENTITY Developer ID Installer identity. When
|
|
# set, passes --sign to productbuild.
|
|
# APPLE_ID Apple ID for notarytool store-credentials.
|
|
# APPLE_ID_PASSWORD App-specific password; used only to
|
|
# populate a keychain profile (not passed
|
|
# to long-lived notarytool submit).
|
|
# NOTARYTOOL_KEYCHAIN_PROFILE Keychain profile name for store/submit.
|
|
# Defaults to probo-agent-notary.
|
|
#
|
|
# Notarization is enabled when APPLE_ID and APPLE_ID_PASSWORD are both
|
|
# set. INSTALLER_IDENTITY is then required. The script stores credentials
|
|
# into the keychain profile, then notarizes and staples the .app before
|
|
# packaging and the signed .pkg via --keychain-profile so the password
|
|
# is not on submit argv for the long --wait.
|
|
#
|
|
# Must run on macOS: pkgbuild, productbuild, and swift build are
|
|
# Apple-only tools. The build also compiles Probo Agent.app (the
|
|
# probo:// URL handler + privileged helper) from enroll-ui/.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)"
|
|
ENROLL_UI_DIR="${SCRIPT_DIR}/enroll-ui"
|
|
|
|
BINARY=""
|
|
VERSION=""
|
|
OUTPUT=""
|
|
IDENTIFIER="com.probo.agent"
|
|
APP_NAME="Probo Agent.app"
|
|
URL_HANDLER_NAME="probo-agent-url-handler"
|
|
HELPER_LABEL="com.probo.agent.helper"
|
|
CODESIGN_IDENTITY="${CODESIGN_IDENTITY:-}"
|
|
INSTALLER_IDENTITY="${INSTALLER_IDENTITY:-}"
|
|
APPLE_ID="${APPLE_ID:-}"
|
|
APPLE_ID_PASSWORD="${APPLE_ID_PASSWORD:-}"
|
|
APPLE_TEAM_ID="${APPLE_TEAM_ID:-}"
|
|
NOTARYTOOL_KEYCHAIN_PROFILE="${NOTARYTOOL_KEYCHAIN_PROFILE:-probo-agent-notary}"
|
|
|
|
usage() {
|
|
sed -ne '/^#/!q; s/^# \{0,1\}//; 2,$ p' <"$0"
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--binary)
|
|
BINARY="$2"
|
|
shift 2
|
|
;;
|
|
--version)
|
|
VERSION="$2"
|
|
shift 2
|
|
;;
|
|
--output)
|
|
OUTPUT="$2"
|
|
shift 2
|
|
;;
|
|
--identifier)
|
|
IDENTIFIER="$2"
|
|
shift 2
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "unknown flag: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "${BINARY}" ] || [ ! -x "${BINARY}" ]; then
|
|
echo "error: --binary <path-to-probo-agent> is required and must be executable" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Distribution.xml advertises hostArchitectures=arm64,x86_64. Refuse a
|
|
# binary that lacks either slice so Installer cannot install on a CPU
|
|
# the agent cannot run on.
|
|
if ! command -v lipo >/dev/null 2>&1; then
|
|
echo "error: lipo is required to validate --binary architecture (run on macOS)" >&2
|
|
exit 1
|
|
fi
|
|
BINARY_ARCHS="$(lipo -archs "${BINARY}")"
|
|
has_arm64=false
|
|
has_x86_64=false
|
|
for arch_slice in ${BINARY_ARCHS}; do
|
|
case "${arch_slice}" in
|
|
arm64) has_arm64=true ;;
|
|
x86_64) has_x86_64=true ;;
|
|
esac
|
|
done
|
|
if [ "${has_arm64}" != true ] || [ "${has_x86_64}" != true ]; then
|
|
echo "error: --binary must be a fat binary with arm64 and x86_64 slices (got: ${BINARY_ARCHS}); use lipo -create" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [ -z "${VERSION}" ]; then
|
|
VERSION="$(cat "${REPO_ROOT}/cmd/probo-agent/VERSION")"
|
|
fi
|
|
if [ -z "${OUTPUT}" ]; then
|
|
mkdir -p "${REPO_ROOT}/dist"
|
|
OUTPUT="${REPO_ROOT}/dist/probo-agent_${VERSION}_darwin.pkg"
|
|
fi
|
|
|
|
if ! command -v pkgbuild >/dev/null 2>&1 || ! command -v productbuild >/dev/null 2>&1; then
|
|
echo "error: pkgbuild and productbuild are required (run on macOS)" >&2
|
|
exit 1
|
|
fi
|
|
if ! command -v swift >/dev/null 2>&1; then
|
|
echo "error: swift is required to build Probo Agent.app (run on macOS)" >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "${CODESIGN_IDENTITY}" ]; then
|
|
echo "error: CODESIGN_IDENTITY is required (privileged helper must be signed)" >&2
|
|
exit 2
|
|
fi
|
|
if [ -z "${APPLE_TEAM_ID}" ]; then
|
|
echo "error: APPLE_TEAM_ID is required (SMAuthorizedClients team requirement)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
notarize_enabled=false
|
|
if [ -n "${APPLE_ID}" ] && [ -n "${APPLE_ID_PASSWORD}" ]; then
|
|
notarize_enabled=true
|
|
fi
|
|
if [ "${notarize_enabled}" = true ] && [ -z "${INSTALLER_IDENTITY}" ]; then
|
|
echo "error: notarization requires INSTALLER_IDENTITY" >&2
|
|
exit 2
|
|
fi
|
|
|
|
codesign_runtime() {
|
|
local path="$1"
|
|
codesign \
|
|
--force \
|
|
--options runtime \
|
|
--timestamp \
|
|
--sign "${CODESIGN_IDENTITY}" \
|
|
"${path}"
|
|
codesign --verify --verbose=2 "${path}"
|
|
}
|
|
|
|
client_requirement() {
|
|
printf 'anchor apple generic and identifier "com.probo.agent.url-handler" and certificate leaf[subject.OU] = "%s"' "${APPLE_TEAM_ID}"
|
|
}
|
|
|
|
team_id_option() {
|
|
printf '"%s"' "${APPLE_TEAM_ID}"
|
|
}
|
|
|
|
# Build AppIcon.icns from the single committed master PNG
|
|
# (enroll-ui/Resources/icon-original.png), matching auditor-mode's
|
|
# pad-then-resize pipeline. Writes under STAGE; does not touch the
|
|
# source tree.
|
|
generate_app_icon_icns() {
|
|
local icon_original="$1"
|
|
local icns_out="$2"
|
|
local icon_dir padded tmp iconset
|
|
|
|
if [ ! -f "${icon_original}" ]; then
|
|
echo "error: missing app icon source ${icon_original}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
icon_dir="${STAGE}/app-icon"
|
|
padded="${icon_dir}/icon-padded.png"
|
|
tmp="${icon_dir}/icon-padded.tmp.png"
|
|
iconset="${icon_dir}/AppIcon.iconset"
|
|
rm -rf "${icon_dir}"
|
|
mkdir -p "${iconset}"
|
|
|
|
# 10% padding on all sides (960 content inside 1200 canvas).
|
|
sips -z 960 960 "${icon_original}" --out "${tmp}" >/dev/null
|
|
sips --padToHeightWidth 1200 1200 "${tmp}" --out "${padded}" >/dev/null
|
|
rm -f "${tmp}"
|
|
|
|
# Write through temp names: sips mishandles @2x suffixes in --out paths.
|
|
sips -z 16 16 "${padded}" --out "${icon_dir}/16.png" >/dev/null
|
|
sips -z 32 32 "${padded}" --out "${icon_dir}/32.png" >/dev/null
|
|
sips -z 64 64 "${padded}" --out "${icon_dir}/64.png" >/dev/null
|
|
sips -z 128 128 "${padded}" --out "${icon_dir}/128.png" >/dev/null
|
|
sips -z 256 256 "${padded}" --out "${icon_dir}/256.png" >/dev/null
|
|
sips -z 512 512 "${padded}" --out "${icon_dir}/512.png" >/dev/null
|
|
sips -z 1024 1024 "${padded}" --out "${icon_dir}/1024.png" >/dev/null
|
|
|
|
# Build @2x names via concatenation so the shell never treats @ as a
|
|
# glob qualifier (and sips is never asked to write those paths).
|
|
local at2x
|
|
at2x='@2x.png'
|
|
cp "${icon_dir}/16.png" "${iconset}/icon_16x16.png"
|
|
cp "${icon_dir}/32.png" "${iconset}/icon_16x16${at2x}"
|
|
cp "${icon_dir}/32.png" "${iconset}/icon_32x32.png"
|
|
cp "${icon_dir}/64.png" "${iconset}/icon_32x32${at2x}"
|
|
cp "${icon_dir}/128.png" "${iconset}/icon_128x128.png"
|
|
cp "${icon_dir}/256.png" "${iconset}/icon_128x128${at2x}"
|
|
cp "${icon_dir}/256.png" "${iconset}/icon_256x256.png"
|
|
cp "${icon_dir}/512.png" "${iconset}/icon_256x256${at2x}"
|
|
cp "${icon_dir}/512.png" "${iconset}/icon_512x512.png"
|
|
cp "${icon_dir}/1024.png" "${iconset}/icon_512x512${at2x}"
|
|
|
|
iconutil -c icns "${iconset}" -o "${icns_out}"
|
|
}
|
|
|
|
# Build Probo Agent.app (URL handler + embedded privileged helper) into
|
|
# parent_dir. Signs nested Mach-Os then the .app bundle (bottom-up).
|
|
build_probo_agent_app() {
|
|
local parent_dir="$1"
|
|
local build_dir render_dir
|
|
local helper_info_plist helper_launchd_plist
|
|
local helper_binary url_handler_binary bin_dir
|
|
local app_root contents macos resources launch_services launch_daemons
|
|
local plist embedded_helper embedded_launchd
|
|
local helper_requirement app_icon_original app_icon_icns
|
|
local -a helper_linker_flags swift_arch_args
|
|
|
|
build_dir="${STAGE}/enroll-ui-build"
|
|
render_dir="${build_dir}/rendered"
|
|
mkdir -p "${render_dir}"
|
|
|
|
swift_arch_args=(--arch arm64 --arch x86_64)
|
|
|
|
sed \
|
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
|
"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift.tmpl" \
|
|
>"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift"
|
|
|
|
sed \
|
|
-e "s|@@TEAM_ID_OPTION@@|$(team_id_option)|g" \
|
|
"${ENROLL_UI_DIR}/Shared/SigningConstants.generated.swift.tmpl" \
|
|
>"${ENROLL_UI_DIR}/Shared/SigningConstants.generated.swift"
|
|
|
|
helper_info_plist="${render_dir}/helper-info.plist"
|
|
helper_launchd_plist="${render_dir}/helper-launchd.plist"
|
|
|
|
sed \
|
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
|
-e "s|@@CLIENT_DESIGNATED_REQUIREMENT@@|$(client_requirement)|g" \
|
|
"${ENROLL_UI_DIR}/HelperTool/Info.plist.tmpl" >"${helper_info_plist}"
|
|
|
|
cp "${ENROLL_UI_DIR}/HelperTool/Launchd.plist.tmpl" "${helper_launchd_plist}"
|
|
|
|
helper_linker_flags=(
|
|
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist
|
|
-Xlinker "${helper_info_plist}"
|
|
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __launchd_plist
|
|
-Xlinker "${helper_launchd_plist}"
|
|
)
|
|
|
|
pushd "${ENROLL_UI_DIR}" >/dev/null
|
|
swift build -c release "${swift_arch_args[@]}" \
|
|
--scratch-path "${build_dir}/swift" \
|
|
--product "${HELPER_LABEL}" \
|
|
"${helper_linker_flags[@]}"
|
|
|
|
swift build -c release "${swift_arch_args[@]}" \
|
|
--scratch-path "${build_dir}/swift" \
|
|
--product "${URL_HANDLER_NAME}"
|
|
|
|
bin_dir="$(swift build -c release "${swift_arch_args[@]}" \
|
|
--scratch-path "${build_dir}/swift" --show-bin-path)"
|
|
helper_binary="${bin_dir}/${HELPER_LABEL}"
|
|
url_handler_binary="${bin_dir}/${URL_HANDLER_NAME}"
|
|
popd >/dev/null
|
|
|
|
if [ ! -x "${helper_binary}" ] || [ ! -x "${url_handler_binary}" ]; then
|
|
echo "error: expected release binaries were not produced" >&2
|
|
exit 1
|
|
fi
|
|
|
|
app_root="${parent_dir}/${APP_NAME}"
|
|
contents="${app_root}/Contents"
|
|
macos="${contents}/MacOS"
|
|
resources="${contents}/Resources"
|
|
launch_services="${contents}/Library/LaunchServices"
|
|
launch_daemons="${contents}/Library/LaunchDaemons"
|
|
plist="${contents}/Info.plist"
|
|
embedded_helper="${launch_services}/${HELPER_LABEL}"
|
|
embedded_launchd="${launch_daemons}/${HELPER_LABEL}.plist"
|
|
app_icon_original="${ENROLL_UI_DIR}/Resources/icon-original.png"
|
|
app_icon_icns="${STAGE}/AppIcon.icns"
|
|
|
|
rm -rf "${app_root}"
|
|
mkdir -p "${macos}" "${resources}" "${launch_services}" "${launch_daemons}"
|
|
|
|
generate_app_icon_icns "${app_icon_original}" "${app_icon_icns}"
|
|
|
|
install -m 0755 "${url_handler_binary}" "${macos}/${URL_HANDLER_NAME}"
|
|
install -m 0755 "${helper_binary}" "${embedded_helper}"
|
|
install -m 0644 "${helper_launchd_plist}" "${embedded_launchd}"
|
|
ditto --norsrc --noextattr "${app_icon_icns}" "${resources}/AppIcon.icns"
|
|
|
|
# Sign helper before writing Info.plist so SMPrivilegedExecutables
|
|
# can embed the helper's designated requirement.
|
|
codesign_runtime "${embedded_helper}"
|
|
# codesign prints "Executable=…" on stderr and either
|
|
# "# designated => …" (modern) or "designated => …" (older) on stdout.
|
|
helper_requirement="$(
|
|
codesign -d -r- "${embedded_helper}" 2>&1 \
|
|
| sed -n -e 's/^# designated => //p' -e 's/^designated => //p'
|
|
)"
|
|
if [ -z "${helper_requirement}" ]; then
|
|
echo "error: cannot extract designated requirement from signed helper" >&2
|
|
codesign -d -r- "${embedded_helper}" 2>&1 >&2 || true
|
|
exit 1
|
|
fi
|
|
echo "Helper designated requirement: ${helper_requirement}"
|
|
|
|
sed \
|
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
|
-e "s|@@HELPER_DESIGNATED_REQUIREMENT@@|${helper_requirement}|g" \
|
|
"${ENROLL_UI_DIR}/Info.plist.tmpl" >"${plist}"
|
|
|
|
if ! plutil -lint "${plist}" >/dev/null; then
|
|
echo "error: rendered Info.plist failed plutil -lint" >&2
|
|
exit 1
|
|
fi
|
|
if ! grep -q '<string>probo</string>' "${plist}"; then
|
|
echo "error: Info.plist is missing probo URL scheme" >&2
|
|
exit 1
|
|
fi
|
|
|
|
codesign_runtime "${macos}/${URL_HANDLER_NAME}"
|
|
codesign_runtime "${app_root}"
|
|
|
|
echo "Built ${app_root}"
|
|
}
|
|
|
|
notarytool_submit() {
|
|
local path="$1"
|
|
xcrun notarytool submit "${path}" \
|
|
--keychain-profile "${NOTARYTOOL_KEYCHAIN_PROFILE}" \
|
|
--wait
|
|
}
|
|
|
|
# pkgbuild records protected com.apple.provenance xattrs as empty
|
|
# AppleDouble (._*) Bom entries. Rewrite the Bom with mkbom so the
|
|
# installer does not lay down those stubs next to real files.
|
|
rewrite_component_bom() {
|
|
local pkg="$1"
|
|
local expand_dir root_dir flat_pkg
|
|
|
|
expand_dir="${STAGE}/component-expand"
|
|
root_dir="${STAGE}/component-root"
|
|
flat_pkg="${STAGE}/probo-agent-component-clean.pkg"
|
|
rm -rf "${expand_dir}" "${root_dir}" "${flat_pkg}"
|
|
# pkgutil --expand creates the destination directory itself.
|
|
pkgutil --expand "${pkg}" "${expand_dir}"
|
|
find "${expand_dir}/Scripts" -name '._*' -delete 2>/dev/null || true
|
|
|
|
mkdir -p "${root_dir}"
|
|
(
|
|
cd "${root_dir}"
|
|
gzip -dc "${expand_dir}/Payload" | cpio -idmu 2>/dev/null
|
|
)
|
|
find "${root_dir}" -name '._*' -delete 2>/dev/null || true
|
|
mkbom "${root_dir}" "${expand_dir}/Bom"
|
|
if lsbom "${expand_dir}/Bom" | grep -q '/\._'; then
|
|
echo "error: rewritten Bom still contains AppleDouble entries" >&2
|
|
return 1
|
|
fi
|
|
pkgutil --flatten "${expand_dir}" "${flat_pkg}"
|
|
mv "${flat_pkg}" "${pkg}"
|
|
}
|
|
|
|
STAGE="$(mktemp -d -t probo-agent-pkg)"
|
|
trap 'rm -rf "${STAGE}"' EXIT
|
|
|
|
PAYLOAD="${STAGE}/payload"
|
|
SCRIPTS="${STAGE}/scripts"
|
|
RESOURCES="${STAGE}/Resources"
|
|
mkdir -p "${PAYLOAD}/usr/local/bin" "${SCRIPTS}" "${RESOURCES}"
|
|
|
|
install -m 0755 "${BINARY}" "${PAYLOAD}/usr/local/bin/probo-agent"
|
|
codesign_runtime "${PAYLOAD}/usr/local/bin/probo-agent"
|
|
|
|
mkdir -p "${PAYLOAD}/Applications"
|
|
build_probo_agent_app "${PAYLOAD}/Applications"
|
|
APP_PATH="${PAYLOAD}/Applications/${APP_NAME}"
|
|
|
|
if [ "${notarize_enabled}" = true ]; then
|
|
# Password appears on argv only for this short-lived store. Submits
|
|
# use --keychain-profile so concurrent processes cannot read it.
|
|
xcrun notarytool store-credentials "${NOTARYTOOL_KEYCHAIN_PROFILE}" \
|
|
--apple-id "${APPLE_ID}" \
|
|
--password "${APPLE_ID_PASSWORD}" \
|
|
--team-id "${APPLE_TEAM_ID}"
|
|
echo "Notarizing Probo Agent.app before packaging..."
|
|
zip_path="${STAGE}/probo-agent-app.zip"
|
|
ditto -c -k --keepParent "${APP_PATH}" "${zip_path}"
|
|
notarytool_submit "${zip_path}"
|
|
rm -f "${zip_path}"
|
|
xcrun stapler staple "${APP_PATH}"
|
|
fi
|
|
|
|
# AppleDouble / xattr hygiene: COPYFILE_DISABLE + ditto --norsrc/--noextattr
|
|
# avoid forks on copy; xattr -cr / find '._*' strip anything codesign
|
|
# reattached; rewrite_component_bom drops provenance stubs from the Bom.
|
|
export COPYFILE_DISABLE=1
|
|
|
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/scripts/preinstall" "${SCRIPTS}/preinstall"
|
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/scripts/postinstall" "${SCRIPTS}/postinstall"
|
|
ditto --norsrc --noextattr \
|
|
"${REPO_ROOT}/pkg/deviceagent/tray/launchagent.plist.tmpl" \
|
|
"${SCRIPTS}/launchagent.plist.tmpl"
|
|
chmod 0755 "${SCRIPTS}/preinstall" "${SCRIPTS}/postinstall"
|
|
chmod 0644 "${SCRIPTS}/launchagent.plist.tmpl"
|
|
|
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/Resources/welcome.html" "${RESOURCES}/welcome.html"
|
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/Resources/conclusion.html" "${RESOURCES}/conclusion.html"
|
|
ditto --norsrc --noextattr "${REPO_ROOT}/LICENSE" "${RESOURCES}/license.txt"
|
|
|
|
xattr -cr "${PAYLOAD}" "${SCRIPTS}" "${RESOURCES}" 2>/dev/null || true
|
|
find "${PAYLOAD}" "${SCRIPTS}" "${RESOURCES}" -name '._*' -delete 2>/dev/null || true
|
|
|
|
COMPONENT_PKG="${STAGE}/probo-agent-component.pkg"
|
|
pkgbuild \
|
|
--root "${PAYLOAD}" \
|
|
--scripts "${SCRIPTS}" \
|
|
--identifier "${IDENTIFIER}" \
|
|
--version "${VERSION}" \
|
|
--install-location "/" \
|
|
"${COMPONENT_PKG}"
|
|
|
|
rewrite_component_bom "${COMPONENT_PKG}"
|
|
|
|
DISTRIBUTION="${STAGE}/Distribution.xml"
|
|
sed \
|
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
|
-e "s|@@IDENTIFIER@@|${IDENTIFIER}|g" \
|
|
"${SCRIPT_DIR}/Distribution.xml.tmpl" >"${DISTRIBUTION}"
|
|
|
|
mkdir -p "$(dirname "${OUTPUT}")"
|
|
|
|
PRODUCTBUILD_ARGS=(
|
|
--distribution "${DISTRIBUTION}"
|
|
--package-path "${STAGE}"
|
|
--resources "${RESOURCES}"
|
|
)
|
|
if [ -n "${INSTALLER_IDENTITY}" ]; then
|
|
PRODUCTBUILD_ARGS+=(--sign "${INSTALLER_IDENTITY}")
|
|
fi
|
|
PRODUCTBUILD_ARGS+=("${OUTPUT}")
|
|
|
|
productbuild "${PRODUCTBUILD_ARGS[@]}"
|
|
|
|
if [ "${notarize_enabled}" = true ]; then
|
|
echo "Notarizing ${OUTPUT}..."
|
|
notarytool_submit "${OUTPUT}"
|
|
xcrun stapler staple "${OUTPUT}"
|
|
fi
|
|
|
|
echo "Built ${OUTPUT}"
|