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
11 changes: 11 additions & 0 deletions .github/workflows/cd-swift-lume.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ on:
required: true
DEVELOPER_NAME:
required: true
PROVISIONING_PROFILE_BASE64:
required: true

permissions:
contents: write
Expand All @@ -43,6 +45,7 @@ env:
TEAM_ID: ${{ secrets.TEAM_ID }}
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
DEVELOPER_NAME: ${{ secrets.DEVELOPER_NAME }}
PROVISIONING_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}

jobs:
notarize:
Expand Down Expand Up @@ -137,6 +140,14 @@ jobs:
# Clean up certificate files
rm application.p12 installer.p12

- name: Install Provisioning Profile
env:
PROVISIONING_PROFILE_BASE64: ${{ secrets.PROVISIONING_PROFILE_BASE64 }}
run: |
echo "Installing provisioning profile..."
echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode > libs/lume/resources/embedded.provisionprofile
echo "Provisioning profile installed successfully"

- name: Build and Notarize
id: build_notarize
env:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ fastlane/test_output
ignore
# .release
.release/
# Provisioning profiles (generated from CI secrets)
*.provisionprofile
# Shared folder
shared
# Trajectories
Expand Down
25 changes: 25 additions & 0 deletions libs/lume/resources/Info.plist
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>
2 changes: 2 additions & 0 deletions libs/lume/resources/lume.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
<dict>
<key>com.apple.security.virtualization</key>
<true/>
<key>com.apple.vm.networking</key>
<true/>
</dict>
</plist>
187 changes: 117 additions & 70 deletions libs/lume/scripts/build/build-release-notarized.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +109 to +111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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
-  security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
+  security list-keychains -d user -s "$KEYCHAIN_PATH" "$(security list-keychains -d user | tr -d '"')"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
security list-keychains
fi
security list-keychains -d user -s "$KEYCHAIN_PATH" "$(security list-keychains -d user | tr -d '"')"
security list-keychains
fi
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 109-109: Quote this to prevent word splitting.

(SC2046)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/lume/scripts/build/build-release-notarized.sh` around lines 109 - 111,
The shell invocation that rebuilds the user keychain list uses an unquoted
command substitution: security list-keychains -d user -s "$KEYCHAIN_PATH"
$(security list-keychains -d user | tr -d '"'); to avoid word-splitting when
keychain paths contain spaces, wrap the command substitution in quotes (i.e.,
use "$(security list-keychains -d user | tr -d '"')" ) so the entire output is
treated as a single argument; update the call around the existing
security/list-keychains usage and keep the existing KEYCHAIN_PATH variable usage
intact.


# 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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling for cd command.

If cd fails, subsequent commands would execute in the wrong directory, potentially causing incorrect behavior or data loss.

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
Verify each finding against the current code and only fix it if needed.

In `@libs/lume/scripts/build/build-release-notarized.sh` at line 236, The script
currently runs cd "$RELEASE_DIR" without checking its result; update the code
around the cd "$RELEASE_DIR" invocation to detect failure and abort safely—after
attempting to change directory, test the exit status and if cd failed print a
clear error to stderr including the $RELEASE_DIR value and exit with a nonzero
status (or use a safe shell construct that achieves the same), so subsequent
commands won't run in the wrong directory.


# 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

Expand All @@ -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."
55 changes: 45 additions & 10 deletions libs/lume/scripts/build/build-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset the app bundle before assembly to prevent stale release artifacts.

Right now the script reuses .release/lume.app. If a previous run had files (for example embedded.provisionprofile) and the current run doesn’t, stale content can be shipped.

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
Verify each finding against the current code and only fix it if needed.

In `@libs/lume/scripts/build/build-release.sh` around lines 14 - 16, The script
reuses APP_BUNDLE (variable APP_BUNDLE=".release/lume.app") and can carry stale
files between runs; update build-release.sh to remove the existing bundle before
recreating it: when APP_BUNDLE is set, delete or rm -rf the existing
"$APP_BUNDLE" (and any related Contents dir) prior to the mkdir -p
"$APP_BUNDLE/Contents/MacOS" step and apply the same cleanup before the later
assembly steps around the block referenced at lines 30-33 so the release is
built from a clean slate.

# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 md

Repository: 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 -20

Repository: 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 sh

Repository: 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 5

Repository: 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.sh

Repository: trycua/cua

Length of output: 97


Use --deep flag for code signing and add verification to ensure nested bundle is signed.

The nested lume_lume.bundle is placed at the app root (line 25) by design for SPM compatibility. The current signing (lines 38-39) doesn't use the --deep flag, which may leave the nested bundle improperly signed. Add --deep to the final codesign and verify the signature:

# 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
Verify each finding against the current code and only fix it if needed.

In `@libs/lume/scripts/build/build-release.sh` around lines 35 - 37, The final
codesign invocation is missing the --deep flag so the nested lume_lume.bundle
placed at the app root may remain unsigned; update the second codesign call that
signs "$APP_BUNDLE" to include --deep (and keep the entitlements on the main
executable call), then add a verification step after signing that uses codesign
--verify (with --deep/--strict/verbose flags) against "$APP_BUNDLE" to confirm
the nested bundle (lume_lume.bundle) and all nested code are properly signed.


# 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
Expand Down
Loading
Loading