NetDesk 1.5.0 — source release
This commit is contained in:
commit
40318aafdd
227 files changed
+80798
No files matched your search
@@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# NetDesk by Akronlogic -- macOS DESKTOP build script.
|
||||
#
|
||||
# Builds the SAME egui viewer/host-panel binary the Windows editions ship
|
||||
# (crate `netdesk-viewer`) for macOS, as a signed + notarized universal app:
|
||||
#
|
||||
# 1. rustup: add the aarch64-apple-darwin + x86_64-apple-darwin std targets.
|
||||
# 2. cargo build --release -p netdesk-viewer for BOTH targets.
|
||||
# 3. lipo the two binaries into one universal (arm64 + x86_64) executable.
|
||||
# 4. iconutil: turn assets/icon*.png into NetDesk.icns.
|
||||
# 5. Assemble NetDesk.app (Contents/MacOS + Info.plist + Resources/.icns).
|
||||
# 6. codesign with a "Developer ID Application" identity (hardened runtime).
|
||||
# 7. notarize the .app via `xcrun notarytool submit --wait`, then staple it.
|
||||
# 8. Build a .dmg from the stapled .app, sign + notarize + staple the .dmg.
|
||||
#
|
||||
# On macOS the H.264 stream is decoded on the Apple video engine through the
|
||||
# VideoToolbox path (core/src/decode_vt.rs, VTDecompressionSession); it falls
|
||||
# back to the portable openh264 software decoder if the hardware path can't
|
||||
# initialize. Nothing here is Mac-specific beyond packaging -- the viewer code
|
||||
# is identical to Windows. See docs/build-macos.md.
|
||||
#
|
||||
# THIS SCRIPT RUNS ON macOS (the user's M4 Mac), NOT on the Windows dev box.
|
||||
# All outputs land in packaging/dist/.
|
||||
#
|
||||
# Usage (from the repo root or anywhere):
|
||||
# bash packaging/build-macos.sh --notary-profile netdesk
|
||||
# bash packaging/build-macos.sh --skip-build # reuse existing targets
|
||||
# bash packaging/build-macos.sh --no-sign # unsigned .app + .dmg
|
||||
# bash packaging/build-macos.sh --no-notarize # sign but don't notarize
|
||||
# bash packaging/build-macos.sh --identity "Developer ID Application: Akronlogic (TEAMID)" \
|
||||
# --apple-id you@akronlogic.com --team-id TEAMID
|
||||
#
|
||||
# Notarization credentials (pick ONE):
|
||||
# --notary-profile <name> a keychain profile created once with:
|
||||
# xcrun notarytool store-credentials <name> --apple-id <e> --team-id <t> --password <app-pw>
|
||||
# --apple-id <email> --team-id <TEAMID> + env NETDESK_NOTARY_PASSWORD=<app-specific-password>
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# defaults / args
|
||||
# ---------------------------------------------------------------------------
|
||||
BUNDLE_ID="com.akronlogic.netdesk"
|
||||
APP_NAME="NetDesk"
|
||||
MIN_MACOS="11.0" # Big Sur: oldest OS the universal app targets
|
||||
CATEGORY="public.app-category.utilities"
|
||||
COPYRIGHT="© Akronlogic. All rights reserved."
|
||||
|
||||
IDENTITY="" # "Developer ID Application: ... (TEAMID)" (empty = auto-detect)
|
||||
APPLE_ID=""
|
||||
TEAM_ID=""
|
||||
NOTARY_PROFILE=""
|
||||
ENTITLEMENTS="" # optional path to an entitlements .plist
|
||||
ICON_SRC="" # optional path to a master PNG (>= 512px, ideally 1024)
|
||||
VERSION="" # override the Cargo workspace version
|
||||
|
||||
SKIP_BUILD=0
|
||||
NO_SIGN=0
|
||||
NO_NOTARIZE=0
|
||||
STAPLE_APP=1 # also notarize+staple the standalone .app (not just the .dmg)
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--identity) IDENTITY="$2"; shift 2 ;;
|
||||
--apple-id) APPLE_ID="$2"; shift 2 ;;
|
||||
--team-id) TEAM_ID="$2"; shift 2 ;;
|
||||
--notary-profile) NOTARY_PROFILE="$2"; shift 2 ;;
|
||||
--entitlements) ENTITLEMENTS="$2"; shift 2 ;;
|
||||
--icon) ICON_SRC="$2"; shift 2 ;;
|
||||
--version) VERSION="$2"; shift 2 ;;
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
--no-sign) NO_SIGN=1; shift ;;
|
||||
--no-notarize) NO_NOTARIZE=1; shift ;;
|
||||
--no-staple-app) STAPLE_APP=0; shift ;;
|
||||
-h|--help)
|
||||
sed -n '2,46p' "$0"; exit 0 ;;
|
||||
*) echo "unknown option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# pretty output
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ -t 1 ]; then
|
||||
C_CYAN='\033[36m'; C_YEL='\033[33m'; C_GRN='\033[32m'; C_RED='\033[31m'; C_DIM='\033[2m'; C_OFF='\033[0m'
|
||||
else
|
||||
C_CYAN=''; C_YEL=''; C_GRN=''; C_RED=''; C_DIM=''; C_OFF=''
|
||||
fi
|
||||
say() { printf "%b%s%b\n" "$C_CYAN" "$*" "$C_OFF"; }
|
||||
step() { printf "\n%b%s%b\n" "$C_YEL" "$*" "$C_OFF"; }
|
||||
ok() { printf "%b%s%b\n" "$C_GRN" "$*" "$C_OFF"; }
|
||||
dim() { printf "%b %s%b\n" "$C_DIM" "$*" "$C_OFF"; }
|
||||
warn() { printf "%b%s%b\n" "$C_RED" "$*" "$C_OFF"; }
|
||||
die() { printf "%b%s%b\n" "$C_RED" "$*" "$C_OFF" >&2; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# paths
|
||||
# ---------------------------------------------------------------------------
|
||||
PKG_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$PKG_DIR/.." && pwd)"
|
||||
DIST_DIR="$PKG_DIR/dist"
|
||||
ASSETS_DIR="$ROOT_DIR/assets"
|
||||
|
||||
APP_DIR="$DIST_DIR/$APP_NAME.app"
|
||||
MACOS_DIR="$APP_DIR/Contents/MacOS"
|
||||
RES_DIR="$APP_DIR/Contents/Resources"
|
||||
PLIST="$APP_DIR/Contents/Info.plist"
|
||||
EXE_OUT="$MACOS_DIR/$APP_NAME"
|
||||
|
||||
TARGET_ARM="aarch64-apple-darwin"
|
||||
TARGET_X64="x86_64-apple-darwin"
|
||||
BIN_ARM="$ROOT_DIR/target/$TARGET_ARM/release/netdesk-viewer"
|
||||
BIN_X64="$ROOT_DIR/target/$TARGET_X64/release/netdesk-viewer"
|
||||
|
||||
DMG_STAGE="$DIST_DIR/dmg-stage"
|
||||
DMG_OUT="$DIST_DIR/$APP_NAME-macOS-universal.dmg"
|
||||
APP_ZIP="$DIST_DIR/$APP_NAME-macOS-universal.zip"
|
||||
|
||||
say "=== NetDesk macOS build (Akronlogic) ==="
|
||||
dim "root : $ROOT_DIR"
|
||||
dim "dist : $DIST_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# preflight
|
||||
# ---------------------------------------------------------------------------
|
||||
[ "$(uname -s)" = "Darwin" ] || die "This script must run on macOS (uname is '$(uname -s)'). Build on the Mac, not the Windows box."
|
||||
|
||||
need() { command -v "$1" >/dev/null 2>&1 || die "required tool not found on PATH: $1"; }
|
||||
need cargo
|
||||
need rustup
|
||||
need lipo
|
||||
need iconutil
|
||||
need sips
|
||||
need hdiutil
|
||||
need codesign
|
||||
need xcrun
|
||||
|
||||
xcode-select -p >/dev/null 2>&1 || die "Xcode command-line tools missing. Run: xcode-select --install"
|
||||
|
||||
# version from the Cargo workspace unless overridden
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION="$(awk -F'"' '/^\[workspace.package\]/{f=1} f&&/^version[[:space:]]*=/{print $2; exit}' "$ROOT_DIR/Cargo.toml")"
|
||||
[ -n "$VERSION" ] || VERSION="0.0.0"
|
||||
fi
|
||||
dim "version : $VERSION"
|
||||
dim "bundle : $BUNDLE_ID"
|
||||
|
||||
mkdir -p "$DIST_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1 + 2. rust targets + release builds (both arches)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$SKIP_BUILD" -eq 0 ]; then
|
||||
step "[1/8] rustup target add ($TARGET_ARM, $TARGET_X64)"
|
||||
rustup target add "$TARGET_ARM" "$TARGET_X64"
|
||||
|
||||
step "[2/8] cargo build --release -p netdesk-viewer (both arches)"
|
||||
( cd "$ROOT_DIR" && cargo build --release -p netdesk-viewer --target "$TARGET_ARM" )
|
||||
( cd "$ROOT_DIR" && cargo build --release -p netdesk-viewer --target "$TARGET_X64" )
|
||||
else
|
||||
step "[1-2/8] --skip-build: reusing existing per-target binaries"
|
||||
fi
|
||||
[ -f "$BIN_ARM" ] || die "missing arm64 binary: $BIN_ARM"
|
||||
[ -f "$BIN_X64" ] || die "missing x86_64 binary: $BIN_X64"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. lipo -> universal binary
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[3/8] lipo -> universal (arm64 + x86_64)"
|
||||
rm -rf "$APP_DIR"
|
||||
mkdir -p "$MACOS_DIR" "$RES_DIR"
|
||||
lipo -create -output "$EXE_OUT" "$BIN_ARM" "$BIN_X64"
|
||||
chmod +x "$EXE_OUT"
|
||||
dim "$(lipo -info "$EXE_OUT")"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. icns via iconutil
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[4/8] iconutil -> $APP_NAME.icns"
|
||||
# Prefer the largest master we have: an explicit --icon, else a 1024 PNG if it
|
||||
# exists, else the 512, else the 256 (assets/icon.png). iconutil needs a full
|
||||
# .iconset of fixed-named PNGs; sips resizes the master down into each slot.
|
||||
if [ -z "$ICON_SRC" ]; then
|
||||
for c in "$ASSETS_DIR/icon-1024.png" "$ASSETS_DIR/icon-512.png" "$ASSETS_DIR/icon.png"; do
|
||||
if [ -f "$c" ]; then ICON_SRC="$c"; break; fi
|
||||
done
|
||||
fi
|
||||
[ -n "$ICON_SRC" ] && [ -f "$ICON_SRC" ] || die "no icon PNG found (looked for assets/icon-1024.png, icon-512.png, icon.png; or pass --icon)"
|
||||
dim "icon master: $ICON_SRC"
|
||||
|
||||
ICONSET="$DIST_DIR/$APP_NAME.iconset"
|
||||
rm -rf "$ICONSET"; mkdir -p "$ICONSET"
|
||||
# name px
|
||||
gen() { sips -z "$2" "$2" "$ICON_SRC" --out "$ICONSET/$1" >/dev/null; }
|
||||
gen icon_16x16.png 16
|
||||
gen icon_16x16@2x.png 32
|
||||
gen icon_32x32.png 32
|
||||
gen icon_32x32@2x.png 64
|
||||
gen icon_128x128.png 128
|
||||
gen icon_128x128@2x.png 256
|
||||
gen icon_256x256.png 256
|
||||
gen icon_256x256@2x.png 512
|
||||
gen icon_512x512.png 512
|
||||
gen icon_512x512@2x.png 1024
|
||||
iconutil -c icns "$ICONSET" -o "$RES_DIR/$APP_NAME.icns"
|
||||
rm -rf "$ICONSET"
|
||||
dim "wrote $RES_DIR/$APP_NAME.icns"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Info.plist + PkgInfo (assemble the bundle)
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[5/8] assemble $APP_NAME.app"
|
||||
cat > "$PLIST" <<PLIST_EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleDisplayName</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleIdentifier</key> <string>$BUNDLE_ID</string>
|
||||
<key>CFBundleExecutable</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleIconFile</key> <string>$APP_NAME</string>
|
||||
<key>CFBundleVersion</key> <string>$VERSION</string>
|
||||
<key>CFBundleShortVersionString</key> <string>$VERSION</string>
|
||||
<key>CFBundlePackageType</key> <string>APPL</string>
|
||||
<key>CFBundleSignature</key> <string>????</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
|
||||
<key>LSMinimumSystemVersion</key> <string>$MIN_MACOS</string>
|
||||
<key>LSApplicationCategoryType</key> <string>$CATEGORY</string>
|
||||
<key>NSHighResolutionCapable</key> <true/>
|
||||
<key>NSHumanReadableCopyright</key> <string>$COPYRIGHT</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key> <true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST_EOF
|
||||
|
||||
printf 'APPL????' > "$APP_DIR/Contents/PkgInfo"
|
||||
ok "bundle staged: $APP_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# resolve signing identity
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$NO_SIGN" -eq 0 ] && [ -z "$IDENTITY" ]; then
|
||||
# auto-detect a single "Developer ID Application" identity from the keychain
|
||||
IDENTITY="$(security find-identity -v -p codesigning 2>/dev/null \
|
||||
| sed -n 's/.*"\(Developer ID Application: [^"]*\)".*/\1/p' | head -n1 || true)"
|
||||
fi
|
||||
|
||||
CAN_SIGN=0
|
||||
if [ "$NO_SIGN" -eq 0 ] && [ -n "$IDENTITY" ]; then CAN_SIGN=1; fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. codesign (hardened runtime + secure timestamp)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$CAN_SIGN" -eq 1 ]; then
|
||||
step "[6/8] codesign ($IDENTITY)"
|
||||
CS_ARGS="--force --timestamp --options runtime --sign $IDENTITY"
|
||||
if [ -n "$ENTITLEMENTS" ]; then
|
||||
[ -f "$ENTITLEMENTS" ] || die "entitlements file not found: $ENTITLEMENTS"
|
||||
dim "entitlements: $ENTITLEMENTS"
|
||||
fi
|
||||
# single-binary bundle (no nested frameworks/dylibs): sign the executable,
|
||||
# then the bundle. --options runtime is required for notarization.
|
||||
if [ -n "$ENTITLEMENTS" ]; then
|
||||
codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$EXE_OUT"
|
||||
codesign --force --timestamp --options runtime --entitlements "$ENTITLEMENTS" --sign "$IDENTITY" "$APP_DIR"
|
||||
else
|
||||
codesign --force --timestamp --options runtime --sign "$IDENTITY" "$EXE_OUT"
|
||||
codesign --force --timestamp --options runtime --sign "$IDENTITY" "$APP_DIR"
|
||||
fi
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
||||
dim "signed + verified"
|
||||
else
|
||||
step "[6/8] codesign -- SKIPPED"
|
||||
if [ "$NO_SIGN" -eq 1 ]; then
|
||||
warn " --no-sign set: the .app and .dmg are UNSIGNED (Gatekeeper will block them on other Macs)."
|
||||
else
|
||||
warn " No \"Developer ID Application\" identity found in the keychain -- producing an UNSIGNED bundle."
|
||||
warn " Install your Developer ID Application cert, or pass --identity \"Developer ID Application: NAME (TEAMID)\"."
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# notarization helper
|
||||
# ---------------------------------------------------------------------------
|
||||
notary_auth_ok=0
|
||||
notary_args=""
|
||||
if [ -n "$NOTARY_PROFILE" ]; then
|
||||
notary_args="--keychain-profile $NOTARY_PROFILE"; notary_auth_ok=1
|
||||
elif [ -n "$APPLE_ID" ] && [ -n "$TEAM_ID" ] && [ -n "${NETDESK_NOTARY_PASSWORD:-}" ]; then
|
||||
notary_args="--apple-id $APPLE_ID --team-id $TEAM_ID --password $NETDESK_NOTARY_PASSWORD"; notary_auth_ok=1
|
||||
fi
|
||||
|
||||
CAN_NOTARIZE=0
|
||||
if [ "$CAN_SIGN" -eq 1 ] && [ "$NO_NOTARIZE" -eq 0 ] && [ "$notary_auth_ok" -eq 1 ]; then CAN_NOTARIZE=1; fi
|
||||
|
||||
notarize() { # $1 = path to .zip/.dmg to submit
|
||||
# shellcheck disable=SC2086
|
||||
xcrun notarytool submit "$1" $notary_args --wait
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. notarize + staple the standalone .app (via a zip; stapling needs the app)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ "$CAN_NOTARIZE" -eq 1 ] && [ "$STAPLE_APP" -eq 1 ]; then
|
||||
step "[7/8] notarize + staple $APP_NAME.app"
|
||||
rm -f "$APP_ZIP"
|
||||
/usr/bin/ditto -c -k --keepParent "$APP_DIR" "$APP_ZIP"
|
||||
notarize "$APP_ZIP"
|
||||
xcrun stapler staple "$APP_DIR"
|
||||
xcrun stapler validate "$APP_DIR"
|
||||
dim "stapled $APP_NAME.app"
|
||||
else
|
||||
step "[7/8] notarize + staple .app -- SKIPPED"
|
||||
if [ "$CAN_SIGN" -eq 0 ]; then
|
||||
dim "(unsigned bundle cannot be notarized)"
|
||||
elif [ "$NO_NOTARIZE" -eq 1 ]; then
|
||||
dim "(--no-notarize set)"
|
||||
elif [ "$notary_auth_ok" -eq 0 ]; then
|
||||
warn " No notarization credentials. Pass --notary-profile <name>, or"
|
||||
warn " --apple-id <e> --team-id <t> with env NETDESK_NOTARY_PASSWORD=<app-pw>."
|
||||
warn " Create a profile once with: xcrun notarytool store-credentials <name> --apple-id <e> --team-id <t> --password <app-pw>"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 8. build the .dmg, then sign + notarize + staple it
|
||||
# ---------------------------------------------------------------------------
|
||||
step "[8/8] build $APP_NAME-macOS-universal.dmg"
|
||||
rm -rf "$DMG_STAGE"; mkdir -p "$DMG_STAGE"
|
||||
/usr/bin/ditto "$APP_DIR" "$DMG_STAGE/$APP_NAME.app"
|
||||
ln -s /Applications "$DMG_STAGE/Applications"
|
||||
rm -f "$DMG_OUT"
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_STAGE" -fs HFS+ -format UDZO -ov "$DMG_OUT" >/dev/null
|
||||
rm -rf "$DMG_STAGE"
|
||||
dim "wrote $DMG_OUT"
|
||||
|
||||
if [ "$CAN_SIGN" -eq 1 ]; then
|
||||
codesign --force --timestamp --sign "$IDENTITY" "$DMG_OUT"
|
||||
dim "signed the .dmg"
|
||||
fi
|
||||
if [ "$CAN_NOTARIZE" -eq 1 ]; then
|
||||
dim "notarizing the .dmg ..."
|
||||
notarize "$DMG_OUT"
|
||||
xcrun stapler staple "$DMG_OUT"
|
||||
xcrun stapler validate "$DMG_OUT"
|
||||
dim "stapled the .dmg"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# summary
|
||||
# ---------------------------------------------------------------------------
|
||||
printf "\n"
|
||||
ok "macOS build complete."
|
||||
dim "app : $APP_DIR"
|
||||
[ -f "$DMG_OUT" ] && dim "dmg : $DMG_OUT ($(du -h "$DMG_OUT" | cut -f1))"
|
||||
if [ "$CAN_SIGN" -eq 0 ]; then
|
||||
warn "NOTE: artifacts are UNSIGNED -- for distribution, re-run on a Mac with a Developer ID Application cert."
|
||||
elif [ "$CAN_NOTARIZE" -eq 0 ]; then
|
||||
warn "NOTE: signed but NOT notarized -- re-run with notarization credentials (see --help) before distributing."
|
||||
else
|
||||
ok "Signed, notarized, and stapled -- ready to distribute."
|
||||
fi
|
||||
Reference in new issue
Block a user