Skip to content

Merge pull request #12 from alexmodrono/worktree-release-0.1 #25

Merge pull request #12 from alexmodrono/worktree-release-0.1

Merge pull request #12 from alexmodrono/worktree-release-0.1 #25

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
permissions:
contents: write
env:
APP_NAME: Findle
SCHEME: Foodle
BUNDLE_ID: es.amodrono.foodle
jobs:
build-and-release:
runs-on: macos-26
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
- name: Install XcodeGen
run: brew install xcodegen
- name: Import signing certificate
env:
CERTIFICATE_P12_BASE64: ${{ secrets.CERTIFICATE_P12_BASE64 }}
CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }}
run: |
CERT_PATH="$RUNNER_TEMP/certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain"
echo -n "$CERTIFICATE_P12_BASE64" | base64 --decode -o "$CERT_PATH"
security create-keychain -p "" "$KEYCHAIN_PATH"
security set-keychain-settings "$KEYCHAIN_PATH"
security unlock-keychain -p "" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -k "" "$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain
- name: Install provisioning profiles
env:
APP_PROVISION_PROFILE_BASE64: ${{ secrets.APP_PROVISION_PROFILE_BASE64 }}
FILEPROVIDER_PROVISION_PROFILE_BASE64: ${{ secrets.FILEPROVIDER_PROVISION_PROFILE_BASE64 }}
run: |
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
mkdir -p "$PROFILES_DIR"
echo -n "$APP_PROVISION_PROFILE_BASE64" | base64 --decode -o "$PROFILES_DIR/app.provisionprofile"
echo -n "$FILEPROVIDER_PROVISION_PROFILE_BASE64" | base64 --decode -o "$PROFILES_DIR/fileprovider.provisionprofile"
APP_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< "$(security cms -D -i "$PROFILES_DIR/app.provisionprofile")")
FP_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< "$(security cms -D -i "$PROFILES_DIR/fileprovider.provisionprofile")")
mv "$PROFILES_DIR/app.provisionprofile" "$PROFILES_DIR/$APP_UUID.provisionprofile"
mv "$PROFILES_DIR/fileprovider.provisionprofile" "$PROFILES_DIR/$FP_UUID.provisionprofile"
echo "APP_PROFILE_UUID=$APP_UUID" >> "$GITHUB_ENV"
echo "FP_PROFILE_UUID=$FP_UUID" >> "$GITHUB_ENV"
- name: Write signing xcconfig files
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain"
cat > Config/Release-App.xcconfig <<XCCONFIG
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Developer ID Application
DEVELOPMENT_TEAM = ${{ secrets.TEAM_ID }}
PROVISIONING_PROFILE = $APP_PROFILE_UUID
OTHER_CODE_SIGN_FLAGS = --keychain $KEYCHAIN_PATH
XCCONFIG
cat > Config/Release-FileProvider.xcconfig <<XCCONFIG
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = Developer ID Application
DEVELOPMENT_TEAM = ${{ secrets.TEAM_ID }}
PROVISIONING_PROFILE = $FP_PROFILE_UUID
OTHER_CODE_SIGN_FLAGS = --keychain $KEYCHAIN_PATH
XCCONFIG
cat > Config/Release-Framework.xcconfig <<XCCONFIG
CODE_SIGN_STYLE = Manual
CODE_SIGN_IDENTITY = -
DEVELOPMENT_TEAM = ${{ secrets.TEAM_ID }}
XCCONFIG
- name: Generate Xcode project
run: xcodegen generate
- name: Set version in Info.plist
run: |
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${{ steps.version.outputs.version }}" Resources/Info/App-Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${{ github.run_number }}" Resources/Info/App-Info.plist
- name: Build archive
run: |
xcodebuild archive \
-project Foodle.xcodeproj \
-scheme "$SCHEME" \
-configuration Release \
-archivePath "$RUNNER_TEMP/Foodle.xcarchive"
- name: Export archive
run: |
cat > "$RUNNER_TEMP/ExportOptions.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>method</key>
<string>developer-id</string>
<key>teamID</key>
<string>${{ secrets.TEAM_ID }}</string>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Developer ID Application</string>
<key>provisioningProfiles</key>
<dict>
<key>es.amodrono.foodle</key>
<string>${APP_PROFILE_UUID}</string>
<key>es.amodrono.foodle.file-provider</key>
<string>${FP_PROFILE_UUID}</string>
</dict>
</dict>
</plist>
EOF
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/Foodle.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist"
- name: Notarize app
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: |
APP_PATH="$RUNNER_TEMP/export/$APP_NAME.app"
ditto -c -k --keepParent "$APP_PATH" "$RUNNER_TEMP/notarize.zip"
xcrun notarytool submit "$RUNNER_TEMP/notarize.zip" \
--apple-id "$APPLE_ID" \
--password "$NOTARIZATION_PASSWORD" \
--team-id "$TEAM_ID" \
--wait
xcrun stapler staple "$APP_PATH"
- name: Create DMG
run: |
brew install create-dmg
create-dmg \
--volname "$APP_NAME" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "$APP_NAME.app" 150 185 \
--app-drop-link 450 185 \
--hide-extension "$APP_NAME.app" \
"$RUNNER_TEMP/$APP_NAME-${{ steps.version.outputs.version }}.dmg" \
"$RUNNER_TEMP/export/$APP_NAME.app"
- name: Create ZIP
run: |
cd "$RUNNER_TEMP/export"
ditto -c -k --keepParent "$APP_NAME.app" "$RUNNER_TEMP/$APP_NAME-${{ steps.version.outputs.version }}.zip"
- name: Sign ZIP with Sparkle EdDSA key
id: sparkle
env:
SPARKLE_PRIVATE_ED_KEY: ${{ secrets.SPARKLE_PRIVATE_ED_KEY }}
run: |
ZIP_PATH="$RUNNER_TEMP/$APP_NAME-${{ steps.version.outputs.version }}.zip"
ZIP_SIZE=$(stat -f%z "$ZIP_PATH")
# Find Sparkle's sign_update tool from the SPM build
SIGN_UPDATE=$(find ~/Library/Developer/Xcode/DerivedData -name "sign_update" -path "*/Sparkle-*/bin/*" 2>/dev/null | head -1)
if [ -z "$SIGN_UPDATE" ]; then
# Fallback: build it from the checked-out package
SPARKLE_CHECKOUT=$(find ~/Library/Developer/Xcode/DerivedData -name "Sparkle" -path "*/SourcePackages/checkouts/*" -type d 2>/dev/null | head -1)
if [ -n "$SPARKLE_CHECKOUT" ]; then
pushd "$SPARKLE_CHECKOUT"
xcodebuild -project Sparkle.xcodeproj -scheme sign_update -configuration Release -derivedDataPath "$RUNNER_TEMP/sparkle-build" build 2>/dev/null || true
popd
SIGN_UPDATE=$(find "$RUNNER_TEMP/sparkle-build" -name "sign_update" -type f 2>/dev/null | head -1)
fi
fi
if [ -n "$SIGN_UPDATE" ]; then
ED_SIGNATURE=$("$SIGN_UPDATE" "$ZIP_PATH" --ed-key-file <(echo "$SPARKLE_PRIVATE_ED_KEY") 2>&1 | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
fi
# Fallback: use openssl to compute EdDSA signature manually if sign_update failed
if [ -z "$ED_SIGNATURE" ]; then
echo "Warning: Could not compute EdDSA signature with sign_update, appcast will lack signature"
ED_SIGNATURE=""
fi
echo "ed_signature=$ED_SIGNATURE" >> "$GITHUB_OUTPUT"
echo "zip_size=$ZIP_SIZE" >> "$GITHUB_OUTPUT"
- name: Compute SHA256
id: sha
run: |
SHA=$(shasum -a 256 "$RUNNER_TEMP/$APP_NAME-${{ steps.version.outputs.version }}.zip" | awk '{print $1}')
echo "sha256=$SHA" >> "$GITHUB_OUTPUT"
- name: Generate Sparkle appcast
run: |
VERSION="${{ steps.version.outputs.version }}"
BUILD="${{ github.run_number }}"
ZIP_SIZE="${{ steps.sparkle.outputs.zip_size }}"
ED_SIGNATURE="${{ steps.sparkle.outputs.ed_signature }}"
REPO="${{ github.repository }}"
PUB_DATE=$(date -u +"%a, %d %b %Y %H:%M:%S %z")
cat > "$RUNNER_TEMP/appcast.xml" <<APPCAST
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>Findle Updates</title>
<language>en</language>
<item>
<title>Version ${VERSION}</title>
<pubDate>${PUB_DATE}</pubDate>
<sparkle:version>${BUILD}</sparkle:version>
<sparkle:shortVersionString>${VERSION}</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>
<enclosure
url="https://github.com/${REPO}/releases/download/v${VERSION}/Findle-${VERSION}.zip"
length="${ZIP_SIZE}"
type="application/octet-stream"
sparkle:edSignature="${ED_SIGNATURE}"
/>
</item>
</channel>
</rss>
APPCAST
- name: Create GitHub Release and upload artifacts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$APP_NAME ${{ steps.version.outputs.version }}" \
--generate-notes \
"$RUNNER_TEMP/$APP_NAME-${{ steps.version.outputs.version }}.dmg" \
"$RUNNER_TEMP/$APP_NAME-${{ steps.version.outputs.version }}.zip" \
"$RUNNER_TEMP/appcast.xml"
- name: Update Homebrew cask
env:
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
VERSION="${{ steps.version.outputs.version }}"
SHA="${{ steps.sha.outputs.sha256 }}"
REPO="${{ github.repository }}"
OWNER="${REPO%%/*}"
git clone "https://x-access-token:${HOMEBREW_TAP_TOKEN}@github.com/${OWNER}/homebrew-tap.git" "$RUNNER_TEMP/homebrew-tap"
mkdir -p "$RUNNER_TEMP/homebrew-tap/Casks"
cat > "$RUNNER_TEMP/homebrew-tap/Casks/findle.rb" <<CASK
cask "findle" do
version "$VERSION"
sha256 "$SHA"
url "https://github.com/${REPO}/releases/download/v#{version}/Findle-#{version}.zip"
name "Findle"
desc "Sync Moodle course content to your Mac via Finder"
homepage "https://github.com/${REPO}"
depends_on macos: ">= :sonoma"
app "Findle.app"
zap trash: [
"~/Library/Group Containers/group.es.amodrono.foodle",
"~/Library/Containers/es.amodrono.foodle",
"~/Library/Containers/es.amodrono.foodle.file-provider",
]
end
CASK
cd "$RUNNER_TEMP/homebrew-tap"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Casks/findle.rb
git commit -m "Update findle to $VERSION"
git push
- name: Clean up keychain
if: always()
run: security delete-keychain "$RUNNER_TEMP/build.keychain" 2>/dev/null || true