-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat(lume): restructure release as .app bundle with bridged networking #1122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0263067
db2789b
d12c241
7b44bb9
f5acf43
d0b5ce0
9d9ea83
293f0ae
7b2080b
6316cf2
c167a64
26e2e62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?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>CFBundleIdentifier</key> | ||
| <string>com.trycua.lume</string> | ||
| <key>CFBundleExecutable</key> | ||
| <string>lume</string> | ||
| <key>CFBundleName</key> | ||
| <string>Lume</string> | ||
| <key>CFBundleVersion</key> | ||
| <string>__VERSION__</string> | ||
| <key>CFBundleShortVersionString</key> | ||
| <string>__VERSION__</string> | ||
| <key>CFBundlePackageType</key> | ||
| <string>APPL</string> | ||
| <key>CFBundleInfoDictionaryVersion</key> | ||
| <string>6.0</string> | ||
| <key>LSMinimumSystemVersion</key> | ||
| <string>14.0</string> | ||
| <key>LSUIElement</key> | ||
| <true/> | ||
| </dict> | ||
| </plist> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,23 +64,77 @@ log "normal" "Ensuring .release directory exists and is accessible" | |
| log "essential" "Building release version..." | ||
| swift build -c release --product lume > /dev/null | ||
|
|
||
| # Sign the binary with hardened runtime entitlements | ||
| log "essential" "Signing binary with entitlements..." | ||
| codesign --force --options runtime \ | ||
| --entitlement ./resources/lume.entitlements \ | ||
| # --- Assemble .app bundle --- | ||
| log "essential" "Assembling .app bundle..." | ||
|
|
||
| APP_BUNDLE=".release/lume.app" | ||
| rm -rf "$APP_BUNDLE" | ||
| mkdir -p "$APP_BUNDLE/Contents/MacOS" | ||
| mkdir -p "$APP_BUNDLE/Contents/Resources" | ||
|
|
||
| # Copy the binary into the bundle | ||
| cp -f .build/release/lume "$APP_BUNDLE/Contents/MacOS/lume" | ||
|
|
||
| # Copy resource bundle to Contents/Resources/. | ||
| # It CANNOT go in Contents/MacOS/ (breaks codesign: "bundle format unrecognized") | ||
| # and CANNOT go at the .app root (breaks codesign: "unsealed contents"). | ||
| # The Swift code uses Bundle.lumeResources which checks resourceURL first. | ||
| BUILD_BUNDLE=".build/release/lume_lume.bundle" | ||
| if [ -d "$BUILD_BUNDLE" ]; then | ||
| cp -rf "$BUILD_BUNDLE" "$APP_BUNDLE/Contents/Resources/" | ||
| fi | ||
|
|
||
| # Stamp and copy Info.plist | ||
| sed "s/__VERSION__/$VERSION/g" "./resources/Info.plist" > "$APP_BUNDLE/Contents/Info.plist" | ||
|
|
||
| # Embed the provisioning profile | ||
| PROVISION_PROFILE="./resources/embedded.provisionprofile" | ||
| if [ -f "$PROVISION_PROFILE" ]; then | ||
| cp "$PROVISION_PROFILE" "$APP_BUNDLE/Contents/embedded.provisionprofile" | ||
| else | ||
| log "error" "Error: embedded.provisionprofile not found at $PROVISION_PROFILE" | ||
| log "error" "The provisioning profile is required for the com.apple.vm.networking entitlement." | ||
| log "error" "Obtain one from the Apple Developer portal tied to bundle ID com.trycua.lume." | ||
| exit 1 | ||
| fi | ||
|
|
||
| # --- Sign the .app bundle --- | ||
| log "essential" "Signing .app bundle..." | ||
| log "essential" "Using signing identity: $CERT_APPLICATION_NAME" | ||
|
|
||
| # Ensure build.keychain is in the search list for codesign | ||
| KEYCHAIN_PATH="$HOME/Library/Keychains/build.keychain-db" | ||
| if [ -f "$KEYCHAIN_PATH" ]; then | ||
| log "essential" "Adding build keychain to search list..." | ||
| security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"') | ||
| security list-keychains | ||
| fi | ||
|
|
||
| # Sign the .app bundle | ||
| log "essential" "Signing .app bundle with Developer ID..." | ||
| codesign --force --options runtime --timestamp \ | ||
| --entitlements ./resources/lume.entitlements \ | ||
| --sign "$CERT_APPLICATION_NAME" \ | ||
| .build/release/lume 2> /dev/null | ||
| --keychain "$KEYCHAIN_PATH" \ | ||
| "$APP_BUNDLE" | ||
|
|
||
| # Create a temporary directory for packaging | ||
| TEMP_ROOT=$(mktemp -d) | ||
| mkdir -p "$TEMP_ROOT/usr/local/bin" | ||
| cp -f .build/release/lume "$TEMP_ROOT/usr/local/bin/" | ||
| # Verify the final bundle signature | ||
| log "essential" "Verifying bundle signature..." | ||
| codesign -dvv "$APP_BUNDLE" 2>&1 | ||
| codesign --verify --strict --deep "$APP_BUNDLE" 2>&1 || { log "error" "Bundle signature verification FAILED"; exit 1; } | ||
| log "essential" "Signature verified successfully." | ||
|
|
||
| # Build the installer package | ||
| # --- Package as .pkg installer --- | ||
| log "essential" "Building installer package..." | ||
|
|
||
| TEMP_ROOT=$(mktemp -d) | ||
| mkdir -p "$TEMP_ROOT/usr/local/share/lume" | ||
| # Use ditto to preserve code signatures and extended attributes | ||
| ditto "$APP_BUNDLE" "$TEMP_ROOT/usr/local/share/lume/lume.app" | ||
|
|
||
| if ! pkgbuild --root "$TEMP_ROOT" \ | ||
| --identifier "com.trycua.lume" \ | ||
| --version "1.0" \ | ||
| --version "$VERSION" \ | ||
| --install-location "/" \ | ||
| --sign "$CERT_INSTALLER_NAME" \ | ||
| ./.release/lume.pkg; then | ||
|
|
@@ -96,7 +150,7 @@ fi | |
|
|
||
| log "essential" "Package created successfully" | ||
|
|
||
| # Submit for notarization using stored credentials | ||
| # --- Notarize --- | ||
| log "essential" "Submitting for notarization..." | ||
| if [ "$LOG_LEVEL" = "minimal" ] || [ "$LOG_LEVEL" = "none" ]; then | ||
| # Minimal output - capture ID but hide details | ||
|
|
@@ -113,6 +167,20 @@ if [ "$LOG_LEVEL" = "minimal" ] || [ "$LOG_LEVEL" = "none" ]; then | |
| log "error" "Notarization failed. Please check logs." | ||
| log "error" "Notarization output:" | ||
| echo "$NOTARY_OUTPUT" | ||
| # Extract submission ID and fetch detailed log | ||
| SUBMISSION_ID=$(echo "$NOTARY_OUTPUT" | grep "id:" | head -1 | awk '{print $2}') | ||
| if [ -n "$SUBMISSION_ID" ]; then | ||
| log "error" "Fetching notarization log for submission $SUBMISSION_ID..." | ||
| xcrun notarytool log "$SUBMISSION_ID" \ | ||
| --apple-id "${APPLE_ID}" \ | ||
| --team-id "${TEAM_ID}" \ | ||
| --password "${APP_SPECIFIC_PASSWORD}" \ | ||
| developer_log.json 2>&1 || true | ||
| if [ -f developer_log.json ]; then | ||
| log "error" "Notarization log:" | ||
| cat developer_log.json | ||
| fi | ||
| fi | ||
| exit 1 | ||
| fi | ||
| else | ||
|
|
@@ -123,86 +191,66 @@ else | |
| --password "${APP_SPECIFIC_PASSWORD}" \ | ||
| --wait; then | ||
| log "error" "Notarization failed" | ||
| # Try to fetch the log for the last submission | ||
| LAST_ID=$(xcrun notarytool history \ | ||
| --apple-id "${APPLE_ID}" \ | ||
| --team-id "${TEAM_ID}" \ | ||
| --password "${APP_SPECIFIC_PASSWORD}" 2>&1 | grep "id:" | head -1 | awk '{print $2}') | ||
| if [ -n "$LAST_ID" ]; then | ||
| log "error" "Fetching notarization log for submission $LAST_ID..." | ||
| xcrun notarytool log "$LAST_ID" \ | ||
| --apple-id "${APPLE_ID}" \ | ||
| --team-id "${TEAM_ID}" \ | ||
| --password "${APP_SPECIFIC_PASSWORD}" \ | ||
| developer_log.json 2>&1 || true | ||
| if [ -f developer_log.json ]; then | ||
| log "error" "Notarization log:" | ||
| cat developer_log.json | ||
| fi | ||
| fi | ||
| exit 1 | ||
| fi | ||
| fi | ||
|
|
||
| # Staple the notarization ticket | ||
| log "essential" "Stapling notarization ticket..." | ||
| # Staple the notarization ticket to the .pkg | ||
| log "essential" "Stapling notarization ticket to .pkg..." | ||
| if ! xcrun stapler staple ./.release/lume.pkg > /dev/null 2>&1; then | ||
| log "error" "Failed to staple notarization ticket" | ||
| log "error" "Failed to staple notarization ticket to .pkg" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Create temporary directory for package extraction | ||
| EXTRACT_ROOT=$(mktemp -d) | ||
| PKG_PATH="$(pwd)/.release/lume.pkg" | ||
|
|
||
| # Extract the pkg using xar | ||
| cd "$EXTRACT_ROOT" | ||
| xar -xf "$PKG_PATH" > /dev/null 2>&1 | ||
|
|
||
| # Verify Payload exists before proceeding | ||
| if [ ! -f "Payload" ]; then | ||
| log "error" "Error: Payload file not found after xar extraction" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Create a directory for the extracted contents | ||
| mkdir -p extracted | ||
| cd extracted | ||
|
|
||
| # Extract the Payload | ||
| cat ../Payload | gunzip -dc | cpio -i > /dev/null 2>&1 | ||
|
|
||
| # Verify the binary exists | ||
| if [ ! -f "usr/local/bin/lume" ]; then | ||
| log "error" "Error: lume binary not found in expected location" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Get the release directory absolute path | ||
| RELEASE_DIR="$(realpath "$(dirname "$PKG_PATH")")" | ||
| log "normal" "Using release directory: $RELEASE_DIR" | ||
|
|
||
| # Copy extracted lume to the release directory | ||
| cp -f usr/local/bin/lume "$RELEASE_DIR/lume" | ||
|
|
||
| # Copy the resource bundle (contains unattended presets) from the build directory | ||
| BUILD_BUNDLE="$LUME_DIR/.build/release/lume_lume.bundle" | ||
| if [ -d "$BUILD_BUNDLE" ]; then | ||
| cp -rf "$BUILD_BUNDLE" "$RELEASE_DIR/" | ||
| # Staple the notarization ticket to the .app bundle | ||
| log "essential" "Stapling notarization ticket to .app bundle..." | ||
| if ! xcrun stapler staple "$APP_BUNDLE" > /dev/null 2>&1; then | ||
| log "normal" "Note: Could not staple .app bundle directly (this is expected when notarizing via .pkg)" | ||
| fi | ||
|
|
||
| # Install to user-local bin directory (standard location) | ||
| USER_BIN="$HOME/.local/bin" | ||
| mkdir -p "$USER_BIN" | ||
| cp -f "$RELEASE_DIR/lume" "$USER_BIN/lume" | ||
|
|
||
| # Advise user to add to PATH if not present | ||
| if ! echo "$PATH" | grep -q "$USER_BIN"; then | ||
| log "normal" "[lume build] Note: $USER_BIN is not in your PATH. Add 'export PATH=\"$USER_BIN:\$PATH\"' to your shell profile." | ||
| fi | ||
| # --- Create release archives --- | ||
|
|
||
| # Get architecture and create OS identifier | ||
| ARCH=$(uname -m) | ||
| OS_IDENTIFIER="darwin-${ARCH}" | ||
| RELEASE_DIR="$(cd .release && pwd)" | ||
|
|
||
| # Create versioned archives of the package with OS identifier in the name | ||
| log "essential" "Creating archives in $RELEASE_DIR..." | ||
| cd "$RELEASE_DIR" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for If Proposed fix-cd "$RELEASE_DIR"
+cd "$RELEASE_DIR" || { log "error" "Failed to change to release directory"; exit 1; }🧰 Tools🪛 Shellcheck (0.11.0)[warning] 236-236: Use 'cd ... || exit' or 'cd ... || return' in case cd fails. (SC2164) 🤖 Prompt for AI Agents |
||
|
|
||
| # Clean up any existing artifacts first to avoid conflicts | ||
| rm -f lume-*.tar.gz lume-*.pkg.tar.gz | ||
|
|
||
| # Create a backward-compatible wrapper script at the tarball root | ||
| cat > lume <<'WRAPPER_EOF' | ||
| #!/bin/sh | ||
| exec "$(dirname "$0")/lume.app/Contents/MacOS/lume" "$@" | ||
| WRAPPER_EOF | ||
| chmod +x lume | ||
|
|
||
| # Create version-specific archives | ||
| log "essential" "Creating version-specific archives (${VERSION})..." | ||
| # Package the binary and resource bundle | ||
| if [ -d "lume_lume.bundle" ]; then | ||
| tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" lume lume_lume.bundle > /dev/null 2>&1 | ||
| else | ||
| tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" lume > /dev/null 2>&1 | ||
| fi | ||
|
|
||
| # Package the .app bundle and wrapper script | ||
| tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" lume lume.app > /dev/null 2>&1 | ||
|
|
||
| # Package the installer | ||
| tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" lume.pkg > /dev/null 2>&1 | ||
|
|
||
|
|
@@ -220,6 +268,5 @@ chmod 644 "$RELEASE_DIR"/*.tar.gz "$RELEASE_DIR"/*.pkg.tar.gz "$RELEASE_DIR"/che | |
|
|
||
| # Clean up | ||
| rm -rf "$TEMP_ROOT" | ||
| rm -rf "$EXTRACT_ROOT" | ||
|
|
||
| log "essential" "Build and packaging completed successfully." | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,25 +9,60 @@ LUME_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" | |
| cd "$LUME_DIR" | ||
|
|
||
| swift build -c release --product lume | ||
| codesign --force --entitlement ./resources/lume.entitlements --sign - .build/release/lume | ||
|
|
||
| mkdir -p ./.release | ||
| cp -f .build/release/lume ./.release/lume | ||
| # Assemble .app bundle | ||
| APP_BUNDLE=".release/lume.app" | ||
| mkdir -p "$APP_BUNDLE/Contents/MacOS" | ||
|
|
||
|
Comment on lines
+14
to
16
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset the app bundle before assembly to prevent stale release artifacts. Right now the script reuses Proposed fix APP_BUNDLE=".release/lume.app"
+rm -rf "$APP_BUNDLE"
mkdir -p "$APP_BUNDLE/Contents/MacOS"Also applies to: 30-33 🤖 Prompt for AI Agents |
||
| # Copy the resource bundle (contains unattended presets) | ||
| cp -f .build/release/lume "$APP_BUNDLE/Contents/MacOS/lume" | ||
|
|
||
| # Copy resource bundle to Contents/Resources/. | ||
| # It CANNOT go in Contents/MacOS/ (breaks codesign: "bundle format unrecognized") | ||
| # and CANNOT go at the .app root (breaks codesign: "unsealed contents"). | ||
| # The Swift code uses Bundle.lumeResources which checks resourceURL first. | ||
| mkdir -p "$APP_BUNDLE/Contents/Resources" | ||
| if [ -d ".build/release/lume_lume.bundle" ]; then | ||
| cp -rf .build/release/lume_lume.bundle ./.release/ | ||
| cp -rf .build/release/lume_lume.bundle "$APP_BUNDLE/Contents/Resources/" | ||
| fi | ||
|
|
||
| # Stamp Info.plist with version from VERSION file | ||
| VERSION=$(cat VERSION 2>/dev/null || echo "0.0.0") | ||
| sed "s/__VERSION__/$VERSION/g" "./resources/Info.plist" > "$APP_BUNDLE/Contents/Info.plist" | ||
|
|
||
| # Embed provisioning profile if available | ||
| if [ -f "./resources/embedded.provisionprofile" ]; then | ||
| cp "./resources/embedded.provisionprofile" "$APP_BUNDLE/Contents/embedded.provisionprofile" | ||
| fi | ||
|
|
||
| # Ad-hoc sign the bundle | ||
| codesign --force --entitlements ./resources/lume.entitlements --sign - "$APP_BUNDLE/Contents/MacOS/lume" | ||
| codesign --force --sign - "$APP_BUNDLE" | ||
|
Comment on lines
+37
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check if the file exists and examine the relevant lines
cat -n libs/lume/scripts/build/build-release.sh | sed -n '30,45p'Repository: trycua/cua Length of output: 770 🏁 Script executed: # Search for nested bundle references in the codebase
rg -i "lume_lume\.bundle" --type sh --type mdRepository: trycua/cua Length of output: 1982 🏁 Script executed: # Check the resources directory for bundle files
find . -name "*.bundle" -o -name "*lume*.bundle" 2>/dev/null | head -20Repository: trycua/cua Length of output: 36 🏁 Script executed: # Look for bundle creation patterns in build scripts
rg -i "\.bundle" libs/lume/scripts/ -A 2 -B 2 --type shRepository: trycua/cua Length of output: 3768 🏁 Script executed: # Get full context around the bundle copy and codesign commands
cat -n libs/lume/scripts/build/build-release.sh | sed -n '20,50p'Repository: trycua/cua Length of output: 1475 🏁 Script executed: # Check if there's any signing of nested bundles elsewhere in the script
rg -i "codesign" libs/lume/scripts/build/build-release.sh -B 5 -A 5Repository: trycua/cua Length of output: 964 🏁 Script executed: # Look at the complete build-release.sh file to understand full signing flow
wc -l libs/lume/scripts/build/build-release.shRepository: trycua/cua Length of output: 97 Use The nested # Ad-hoc sign the bundle
codesign --force --entitlements ./resources/lume.entitlements --sign - "$APP_BUNDLE/Contents/MacOS/lume"
-codesign --force --sign - "$APP_BUNDLE"
+codesign --force --deep --sign - "$APP_BUNDLE"
+codesign --verify --deep --strict "$APP_BUNDLE"🤖 Prompt for AI Agents |
||
|
|
||
| # Create wrapper script | ||
| mkdir -p .release | ||
| cat > .release/lume <<'WRAPPER_EOF' | ||
| #!/bin/sh | ||
| exec "$(dirname "$0")/lume.app/Contents/MacOS/lume" "$@" | ||
| WRAPPER_EOF | ||
| chmod +x .release/lume | ||
|
|
||
| # Install to user-local bin directory (standard location) | ||
| USER_BIN="$HOME/.local/bin" | ||
| APP_INSTALL_DIR="$HOME/.local/share/lume" | ||
|
|
||
| mkdir -p "$USER_BIN" | ||
| cp -f ./.release/lume "$USER_BIN/lume" | ||
| mkdir -p "$APP_INSTALL_DIR" | ||
|
|
||
| # Install the resource bundle alongside the binary | ||
| if [ -d "./.release/lume_lume.bundle" ]; then | ||
| cp -rf ./.release/lume_lume.bundle "$USER_BIN/" | ||
| fi | ||
| # Install .app bundle | ||
| rm -rf "$APP_INSTALL_DIR/lume.app" | ||
| cp -R ".release/lume.app" "$APP_INSTALL_DIR/" | ||
|
|
||
| # Create wrapper script in bin directory | ||
| cat > "$USER_BIN/lume" <<WRAPPER_EOF | ||
| #!/bin/sh | ||
| exec "$APP_INSTALL_DIR/lume.app/Contents/MacOS/lume" "\$@" | ||
| WRAPPER_EOF | ||
| chmod +x "$USER_BIN/lume" | ||
|
|
||
| # Advise user to add to PATH if not present | ||
| if ! echo "$PATH" | grep -q "$USER_BIN"; then | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quote command substitution to prevent word splitting.
The
$(security list-keychains ...)output is unquoted, which can cause word splitting issues if keychain paths contain spaces.Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 Shellcheck (0.11.0)
[warning] 109-109: Quote this to prevent word splitting.
(SC2046)
🤖 Prompt for AI Agents