222 lines
6.7 KiB
Bash
Executable File
222 lines
6.7 KiB
Bash
Executable File
#!/bin/zsh
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
APP_DIR="$ROOT_DIR/dist/boss-agent.app"
|
|
CONTENTS_DIR="$APP_DIR/Contents"
|
|
MACOS_DIR="$CONTENTS_DIR/MacOS"
|
|
RESOURCES_DIR="$CONTENTS_DIR/Resources"
|
|
SOURCE_FILE="$ROOT_DIR/apps/boss-agent-mac/Sources/BossAgentApp.swift"
|
|
BINARY_PATH="$MACOS_DIR/boss-agent"
|
|
ICONSET_DIR="$RESOURCES_DIR/BossAgent.iconset"
|
|
ICON_PATH="$RESOURCES_DIR/BossAgent.icns"
|
|
SIGNING_IDENTITY="${BOSS_AGENT_CODESIGN_IDENTITY:-}"
|
|
NOTARIZE="${BOSS_AGENT_NOTARIZE:-0}"
|
|
NOTARY_PROFILE="${BOSS_AGENT_NOTARY_PROFILE:-}"
|
|
NOTARY_APPLE_ID="${BOSS_AGENT_NOTARY_APPLE_ID:-}"
|
|
NOTARY_TEAM_ID="${BOSS_AGENT_NOTARY_TEAM_ID:-}"
|
|
NOTARY_PASSWORD="${BOSS_AGENT_NOTARY_PASSWORD:-}"
|
|
|
|
if ! command -v swiftc >/dev/null 2>&1; then
|
|
echo "swiftc not found. Install Xcode Command Line Tools first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v iconutil >/dev/null 2>&1; then
|
|
echo "iconutil not found. Install Xcode Command Line Tools first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$SIGNING_IDENTITY" ]] && command -v security >/dev/null 2>&1; then
|
|
if [[ "$NOTARIZE" == "1" ]]; then
|
|
SIGNING_IDENTITY="$(
|
|
security find-identity -v -p codesigning 2>/dev/null \
|
|
| awk -F'"' '/"Developer ID Application:/ { print $2; exit }'
|
|
)"
|
|
else
|
|
SIGNING_IDENTITY="$(
|
|
security find-identity -v -p codesigning 2>/dev/null \
|
|
| awk -F'"' '/"Apple Development:|Developer ID Application:|Mac Developer:|Boss Agent/ { print $2; exit }'
|
|
)"
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$SIGNING_IDENTITY" ]]; then
|
|
if [[ "$NOTARIZE" == "1" ]]; then
|
|
echo "boss-agent: BOSS_AGENT_NOTARIZE=1 requires a Developer ID Application signing identity." >&2
|
|
exit 1
|
|
fi
|
|
SIGNING_IDENTITY="-"
|
|
echo "boss-agent: no stable code signing identity found; falling back to ad-hoc signing." >&2
|
|
else
|
|
echo "boss-agent: signing with identity: $SIGNING_IDENTITY" >&2
|
|
fi
|
|
|
|
rm -rf "$APP_DIR"
|
|
mkdir -p "$MACOS_DIR" "$RESOURCES_DIR"
|
|
|
|
swiftc "$SOURCE_FILE" \
|
|
-o "$BINARY_PATH" \
|
|
-framework Cocoa \
|
|
-framework WebKit \
|
|
-framework ApplicationServices
|
|
|
|
chmod +x "$BINARY_PATH"
|
|
|
|
python3 - "$ICONSET_DIR" <<'PY'
|
|
import os
|
|
import struct
|
|
import sys
|
|
import zlib
|
|
|
|
iconset_dir = sys.argv[1]
|
|
os.makedirs(iconset_dir, exist_ok=True)
|
|
|
|
targets = {
|
|
"icon_16x16.png": 16,
|
|
"icon_16x16@2x.png": 32,
|
|
"icon_32x32.png": 32,
|
|
"icon_32x32@2x.png": 64,
|
|
"icon_128x128.png": 128,
|
|
"icon_128x128@2x.png": 256,
|
|
"icon_256x256.png": 256,
|
|
"icon_256x256@2x.png": 512,
|
|
"icon_512x512.png": 512,
|
|
"icon_512x512@2x.png": 1024,
|
|
}
|
|
|
|
def rounded_rect(px, py, x0, y0, x1, y1, radius):
|
|
cx = min(max(px, x0 + radius), x1 - radius)
|
|
cy = min(max(py, y0 + radius), y1 - radius)
|
|
return (px - cx) * (px - cx) + (py - cy) * (py - cy) <= radius * radius
|
|
|
|
def rgba_at(px, py):
|
|
if rounded_rect(px, py, 82, 82, 942, 942, 210):
|
|
white = (
|
|
rounded_rect(px, py, 278, 245, 406, 779, 64)
|
|
or rounded_rect(px, py, 350, 245, 745, 518, 132)
|
|
or rounded_rect(px, py, 350, 506, 745, 779, 132)
|
|
)
|
|
cutout = (
|
|
rounded_rect(px, py, 462, 332, 619, 438, 54)
|
|
or rounded_rect(px, py, 462, 592, 635, 698, 54)
|
|
)
|
|
if white and not cutout:
|
|
return (255, 255, 255, 255)
|
|
return (7, 193, 96, 255)
|
|
|
|
if rounded_rect(px, py - 18, 82, 82, 942, 942, 210):
|
|
return (7, 24, 16, 28)
|
|
return (0, 0, 0, 0)
|
|
|
|
def write_png(path, size):
|
|
rows = []
|
|
for y in range(size):
|
|
row = bytearray([0])
|
|
for x in range(size):
|
|
px = (x + 0.5) * 1024 / size
|
|
py = (y + 0.5) * 1024 / size
|
|
row.extend(rgba_at(px, py))
|
|
rows.append(bytes(row))
|
|
|
|
raw = b"".join(rows)
|
|
def chunk(kind, data):
|
|
return (
|
|
struct.pack(">I", len(data))
|
|
+ kind
|
|
+ data
|
|
+ struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF)
|
|
)
|
|
|
|
with open(path, "wb") as fp:
|
|
fp.write(b"\x89PNG\r\n\x1a\n")
|
|
fp.write(chunk(b"IHDR", struct.pack(">IIBBBBB", size, size, 8, 6, 0, 0, 0)))
|
|
fp.write(chunk(b"IDAT", zlib.compress(raw, 9)))
|
|
fp.write(chunk(b"IEND", b""))
|
|
|
|
for name, size in targets.items():
|
|
write_png(os.path.join(iconset_dir, name), size)
|
|
PY
|
|
|
|
iconutil -c icns "$ICONSET_DIR" -o "$ICON_PATH"
|
|
rm -rf "$ICONSET_DIR"
|
|
|
|
cat > "$CONTENTS_DIR/Info.plist" <<'PLIST'
|
|
<?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>CFBundleDevelopmentRegion</key>
|
|
<string>zh_CN</string>
|
|
<key>CFBundleExecutable</key>
|
|
<string>boss-agent</string>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>com.hyzq.boss.agent</string>
|
|
<key>CFBundleInfoDictionaryVersion</key>
|
|
<string>6.0</string>
|
|
<key>CFBundleName</key>
|
|
<string>boss-agent</string>
|
|
<key>CFBundleDisplayName</key>
|
|
<string>boss-agent</string>
|
|
<key>CFBundleIconFile</key>
|
|
<string>BossAgent.icns</string>
|
|
<key>CFBundleURLTypes</key>
|
|
<array>
|
|
<dict>
|
|
<key>CFBundleURLName</key>
|
|
<string>com.hyzq.boss.agent</string>
|
|
<key>CFBundleURLSchemes</key>
|
|
<array>
|
|
<string>boss-agent</string>
|
|
</array>
|
|
</dict>
|
|
</array>
|
|
<key>CFBundlePackageType</key>
|
|
<string>APPL</string>
|
|
<key>CFBundleShortVersionString</key>
|
|
<string>0.1.0</string>
|
|
<key>CFBundleVersion</key>
|
|
<string>1</string>
|
|
<key>LSMinimumSystemVersion</key>
|
|
<string>13.0</string>
|
|
<key>NSHighResolutionCapable</key>
|
|
<true/>
|
|
<key>NSScreenCaptureUsageDescription</key>
|
|
<string>boss-agent 需要读取屏幕画面,用于识别桌面状态、系统弹窗和远程控制结果。</string>
|
|
</dict>
|
|
</plist>
|
|
PLIST
|
|
|
|
plutil -lint "$CONTENTS_DIR/Info.plist" >/dev/null
|
|
if [[ "$NOTARIZE" == "1" ]]; then
|
|
if ! command -v xcrun >/dev/null 2>&1; then
|
|
echo "boss-agent: xcrun is required for notarization." >&2
|
|
exit 1
|
|
fi
|
|
codesign --force --deep --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$APP_DIR" >/dev/null
|
|
|
|
NOTARY_ZIP="$ROOT_DIR/dist/boss-agent-notary.zip"
|
|
rm -f "$NOTARY_ZIP"
|
|
(
|
|
cd "$ROOT_DIR/dist"
|
|
ditto -c -k --keepParent "boss-agent.app" "$NOTARY_ZIP"
|
|
)
|
|
|
|
NOTARY_ARGS=()
|
|
if [[ -n "$NOTARY_PROFILE" ]]; then
|
|
NOTARY_ARGS=(--keychain-profile "$NOTARY_PROFILE")
|
|
elif [[ -n "$NOTARY_APPLE_ID" && -n "$NOTARY_TEAM_ID" && -n "$NOTARY_PASSWORD" ]]; then
|
|
NOTARY_ARGS=(--apple-id "$NOTARY_APPLE_ID" --team-id "$NOTARY_TEAM_ID" --password "$NOTARY_PASSWORD")
|
|
else
|
|
echo "boss-agent: notarization requires BOSS_AGENT_NOTARY_PROFILE or Apple ID/team/password env vars." >&2
|
|
exit 1
|
|
fi
|
|
|
|
xcrun notarytool submit "$NOTARY_ZIP" "${NOTARY_ARGS[@]}" --wait >/dev/null
|
|
xcrun stapler staple "$APP_DIR" >/dev/null
|
|
rm -f "$NOTARY_ZIP"
|
|
else
|
|
codesign --force --deep --timestamp=none --sign "$SIGNING_IDENTITY" "$APP_DIR" >/dev/null
|
|
fi
|
|
echo "$APP_DIR"
|