Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,42 @@ jobs:
# Cleanup
rm -f /tmp/certificate.p12

- name: Install Provisioning Profiles
env:
PROFILE_HOST_BASE64: ${{ secrets.PROFILE_HOST_BASE64 }}
PROFILE_SHARE_BASE64: ${{ secrets.PROFILE_SHARE_BASE64 }}
PROFILE_FINDERSYNC_BASE64: ${{ secrets.PROFILE_FINDERSYNC_BASE64 }}
run: |
# xcodebuild looks up profiles by UUID in this directory. Each file's
# UUID is read from its plist payload and used as the filename — that's
# what xcodebuild's PROVISIONING_PROFILE_SPECIFIER resolves against.
PP_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PP_DIR"

install_profile() {
local var_name="$1" label="$2"
local payload
payload=$(eval "printf '%s' \"\$$var_name\"")
if [ -z "$payload" ]; then
echo "❌ $var_name is empty — corresponding GitHub secret missing"
return 1
fi
local tmp uuid
tmp=$(mktemp)
printf '%s' "$payload" | base64 --decode > "$tmp"
uuid=$(security cms -D -i "$tmp" 2>/dev/null | python3 -c "import sys,plistlib; print(plistlib.loads(sys.stdin.buffer.read())['UUID'])")
if [ -z "$uuid" ]; then
echo "❌ Could not extract UUID from $label profile"
return 1
fi
mv "$tmp" "$PP_DIR/$uuid.provisionprofile"
echo " ✓ $label profile installed: $uuid.provisionprofile"
}

install_profile PROFILE_HOST_BASE64 "Host"
install_profile PROFILE_SHARE_BASE64 "Share Extension"
install_profile PROFILE_FINDERSYNC_BASE64 "FinderSync Extension"

- name: Build and Package
env:
CODESIGN_IDENTITY: "Developer ID Application: high5 ventures GmbH (VG5X6JCLGF)"
Expand Down
14 changes: 2 additions & 12 deletions ClawsyMac.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,13 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.ai.openclaw.clawsy</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.keychain-access-groups</key>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.ai.openclaw.clawsy</string>
<string>VG5X6JCLGF.ai.clawsy</string>
</array>
</dict>
</plist>
23 changes: 23 additions & 0 deletions ExportOptions.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?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>method</key>
<string>developer-id</string>
<key>teamID</key>
<string>VG5X6JCLGF</string>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Developer ID Application</string>
<key>provisioningProfiles</key>
<dict>
<key>ai.clawsy</key>
<string>Clawsy Direct</string>
<key>ai.clawsy.ShareExtension</key>
<string>Clawsy Share Direct</string>
<key>ai.clawsy.FinderSync</key>
<string>Clawsy FinderSync Direct</string>
</dict>
</dict>
</plist>
14 changes: 0 additions & 14 deletions FinderSync.entitlements

This file was deleted.

4 changes: 0 additions & 4 deletions Sources/ClawsyMacShare/ClawsyMacShare.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,5 @@
<array>
<string>group.ai.openclaw.clawsy</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
188 changes: 104 additions & 84 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@ APP_NAME="Clawsy"
SCHEME="ClawsyMac"
BUILD_DIR=".build"
DERIVED_DATA="$BUILD_DIR/DerivedData"
ARCHIVE_PATH="$BUILD_DIR/$APP_NAME.xcarchive"
EXPORT_PATH="$BUILD_DIR/export"
APP_BUNDLE="$BUILD_DIR/app/$APP_NAME.app"
EXPORT_OPTIONS="ExportOptions.plist"
SIGN_ID="${CODESIGN_IDENTITY:--}"

# Hardened Runtime requires an Apple-issued certificate (Developer ID /
# Apple Development). The `disable-library-validation` entitlement that
# would allow loading self-signed frameworks is a *restricted* entitlement
# — macOS silently ignores it when the cert is not Apple-issued. Result:
# dyld kills the app at launch ("different Team IDs").
#
# Enable HR only when the signing identity is a real Apple cert.
# Distribution build = archive + exportArchive (Apple's required path for
# Developer ID Direct Distribution). Local/dev builds with ad-hoc cert use
# plain `xcodebuild build` because archive requires a real cert in the keychain
# and the productPackagingUtility distribution-mode would reject ad-hoc anyway.
case "$SIGN_ID" in
"Developer ID"*|"Apple Development"*|"Apple Distribution"*|"3rd Party Mac"*)
HARDENED_RUNTIME=YES ;;
DISTRIBUTION_BUILD=YES ;;
*)
HARDENED_RUNTIME=NO ;;
DISTRIBUTION_BUILD=NO ;;
esac

echo "🔑 Signing identity: $SIGN_ID"
echo "🛡️ Hardened Runtime: $HARDENED_RUNTIME"
echo "📦 Distribution build: $DISTRIBUTION_BUILD"

echo "🧹 Cleaning up..."
rm -rf "$BUILD_DIR/app"
rm -rf "$BUILD_DIR/app" "$ARCHIVE_PATH" "$EXPORT_PATH"
mkdir -p "$BUILD_DIR/app"

# ── Step 1: Generate Xcode project from project.yml ─────────────────
Expand All @@ -43,102 +43,122 @@ xcodegen generate --spec project.yml
echo "🎨 Generating icons..."
bash scripts/generate_icons.sh

# ── Step 3: Build with xcodebuild ───────────────────────────────────
echo "🦞 Building $APP_NAME (Release, Universal)..."
xcodebuild \
-project Clawsy.xcodeproj \
-scheme "$SCHEME" \
-configuration Release \
-derivedDataPath "$DERIVED_DATA" \
-arch arm64 -arch x86_64 \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGN_IDENTITY="$SIGN_ID" \
CODE_SIGN_STYLE=Manual \
ENABLE_HARDENED_RUNTIME=$HARDENED_RUNTIME \
build

# ── Step 4: Copy built .app to output directory ─────────────────────
echo "📦 Packaging $APP_NAME.app..."
BUILT_APP=$(find "$DERIVED_DATA" -name "$APP_NAME.app" -type d -path "*/Release/*" | head -n 1)

if [ -z "$BUILT_APP" ]; then
echo "❌ Error: Could not find built app bundle"
exit 1
# ── Step 3: Build ───────────────────────────────────────────────────
if [ "$DISTRIBUTION_BUILD" = "YES" ]; then
# Apple Developer ID Direct Distribution path: archive then exportArchive.
# Plain `xcodebuild build` produces .xcent files that contain ONLY profile
# defaults (application-identifier, team-identifier, get-task-allow=YES)
# because Xcode treats it as a development build — restricted entitlements
# (application-groups, FinderSync.HostBundleIdentifier) get silently
# stripped before codesign even runs. Archive sets ENTITLEMENTS_REQUIRED
# and PROVISIONING_PROFILE_REQUIRED to YES, switching productPackagingUtility
# into distribution mode, which honors the full .entitlements files.
echo "🦞 Archiving $APP_NAME (Release, Universal)..."
xcodebuild \
-project Clawsy.xcodeproj \
-scheme "$SCHEME" \
-configuration Release \
-derivedDataPath "$DERIVED_DATA" \
-archivePath "$ARCHIVE_PATH" \
-arch arm64 -arch x86_64 \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGN_IDENTITY="$SIGN_ID" \
CODE_SIGN_STYLE=Manual \
archive

echo "📤 Exporting archive with developer-id method..."
xcodebuild \
-archivePath "$ARCHIVE_PATH" \
-exportArchive \
-exportPath "$EXPORT_PATH" \
-exportOptionsPlist "$EXPORT_OPTIONS"

if [ ! -d "$EXPORT_PATH/$APP_NAME.app" ]; then
echo "❌ Error: exportArchive did not produce $APP_NAME.app"
ls -la "$EXPORT_PATH" || true
exit 1
fi

cp -R "$EXPORT_PATH/$APP_NAME.app" "$APP_BUNDLE"
else
# Local/dev ad-hoc path — no distribution semantics, just produce a runnable
# bundle. Restricted entitlements will be stripped by codesign, but for
# local dev that's fine; macOS treats ad-hoc-signed unsandboxed apps
# leniently.
echo "🦞 Building $APP_NAME (Release, Universal, ad-hoc)..."
xcodebuild \
-project Clawsy.xcodeproj \
-scheme "$SCHEME" \
-configuration Release \
-derivedDataPath "$DERIVED_DATA" \
-arch arm64 -arch x86_64 \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGN_IDENTITY="$SIGN_ID" \
CODE_SIGN_STYLE=Manual \
ENABLE_HARDENED_RUNTIME=NO \
PROVISIONING_PROFILE_SPECIFIER="" \
build

BUILT_APP=$(find "$DERIVED_DATA" -name "$APP_NAME.app" -type d -path "*/Release/*" | head -n 1)
if [ -z "$BUILT_APP" ]; then
echo "❌ Error: Could not find built app bundle"
exit 1
fi
cp -R "$BUILT_APP" "$APP_BUNDLE"
fi

cp -R "$BUILT_APP" "$APP_BUNDLE"

# ── Step 5: Verify CLAWSY.md ───────────────────────────────────────
# CLAWSY.md is included via project.yml resources — no manual copy
# needed. Copying after xcodebuild would break the code signature seal.
# ── Step 4: Verify CLAWSY.md ───────────────────────────────────────
if [ -f "$APP_BUNDLE/Contents/Resources/CLAWSY.md" ]; then
echo "✅ CLAWSY.md bundled by xcodebuild"
else
echo "⚠️ CLAWSY.md missing from bundle resources"
fi

# ── Step 6: Re-sign bundle for consistent Team ID ─────────────────
# Re-sign all components with the same identity (inside-out) to
# guarantee matching Team IDs.
echo "🔏 Re-signing app bundle (component-level)..."

if [ "$HARDENED_RUNTIME" = "YES" ]; then
CODESIGN_OPTS="--options runtime --timestamp"
else
CODESIGN_OPTS=""
fi

# 6a: Bundles inside frameworks (must be signed before the framework)
for fw in "$APP_BUNDLE"/Contents/Frameworks/*.framework; do
[ -d "$fw" ] || continue
for bundle in "$fw"/Versions/A/Resources/*.bundle; do
[ -d "$bundle" ] && codesign --force --sign "$SIGN_ID" $CODESIGN_OPTS "$bundle"
done
codesign --force --sign "$SIGN_ID" $CODESIGN_OPTS "$fw"
done

# 6b: Extensions — explicit entitlements (avoid preserving get-task-allow)
codesign --force --sign "$SIGN_ID" $CODESIGN_OPTS \
--entitlements Sources/ClawsyMacShare/ClawsyMacShare.entitlements \
"$APP_BUNDLE/Contents/PlugIns/ClawsyShare.appex"
codesign --force --sign "$SIGN_ID" $CODESIGN_OPTS \
--entitlements Sources/ClawsyFinderSync/ClawsyFinderSync.entitlements \
"$APP_BUNDLE/Contents/PlugIns/ClawsyFinderSync.appex"

# 6c: Main app (outermost, signed last) — explicit entitlements
codesign --force --sign "$SIGN_ID" $CODESIGN_OPTS \
--entitlements ClawsyMac.entitlements "$APP_BUNDLE"

# ── Step 7: Verify ──────────────────────────────────────────────────
# ── Step 5: Verify ──────────────────────────────────────────────────
echo "🔍 Verifying bundle structure..."

# Check extensions are embedded
if [ -d "$APP_BUNDLE/Contents/PlugIns/ClawsyShare.appex" ]; then
echo "✅ Share Extension embedded"
else
echo "⚠️ Share Extension missing from PlugIns/"
echo "Share Extension missing from PlugIns/" && exit 1
fi

if [ -d "$APP_BUNDLE/Contents/PlugIns/ClawsyFinderSync.appex" ]; then
echo "✅ FinderSync Extension embedded"
else
echo "⚠️ FinderSync Extension missing from PlugIns/"
echo "FinderSync Extension missing from PlugIns/" && exit 1
fi

# Verify code signature
codesign -vvv --deep --strict "$APP_BUNDLE"

# Verify extension entitlements are preserved
echo "🔐 Verifying extension entitlements..."
if codesign -d --entitlements :- "$APP_BUNDLE/Contents/PlugIns/ClawsyFinderSync.appex" 2>/dev/null | grep -q "FinderSync.HostBundleIdentifier"; then
echo "✅ FinderSync entitlements OK"
else
echo "⚠️ FinderSync missing HostBundleIdentifier entitlement!"
fi
if codesign -d --entitlements :- "$APP_BUNDLE/Contents/PlugIns/ClawsyShare.appex" 2>/dev/null | grep -q "application-groups"; then
echo "✅ Share Extension entitlements OK"
else
echo "⚠️ Share Extension missing app-group entitlement!"
# Verify restricted entitlements survived signing — these are HARD failures
# for distribution builds. In ad-hoc local builds we skip these because
# codesign strips them by design.
if [ "$DISTRIBUTION_BUILD" = "YES" ]; then
echo "🔐 Verifying entitlements survived signing..."
ENT_FAIL=0

verify_ent() {
local bundle="$1" needle="$2" label="$3"
local ent
ent=$(codesign -d --entitlements :- "$bundle" 2>/dev/null)
if echo "$ent" | grep -q "$needle"; then
echo " ✅ $label has $needle"
else
echo " ❌ $label is MISSING $needle"
ENT_FAIL=1
fi
}

verify_ent "$APP_BUNDLE/Contents/PlugIns/ClawsyFinderSync.appex" "application-groups" "FinderSync"
verify_ent "$APP_BUNDLE/Contents/PlugIns/ClawsyShare.appex" "application-groups" "Share Extension"
verify_ent "$APP_BUNDLE" "application-groups" "Host"

if [ "$ENT_FAIL" != "0" ]; then
echo "❌ One or more components are missing required entitlements."
exit 1
fi
fi

# Show signing identity & Team ID for all components
Expand Down
Loading
Loading