-
Notifications
You must be signed in to change notification settings - Fork 0
323 lines (270 loc) · 12.5 KB
/
release.yml
File metadata and controls
323 lines (270 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
permissions:
contents: write
jobs:
build-sign-notarize:
runs-on: macos-26
timeout-minutes: 60
outputs:
version: ${{ steps.version.outputs.version }}
dmg-name: ${{ steps.version.outputs.dmg-name }}
steps:
- uses: actions/checkout@v5
- name: Extract version from tag
id: version
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "dmg-name=CloudMount-${VERSION}.dmg" >> "$GITHUB_OUTPUT"
- name: Install tools
run: brew install create-dmg xcodegen
- name: Import code signing certificates
env:
DEV_CERTIFICATE_BASE64: ${{ secrets.DEV_CERTIFICATE_BASE64 }}
DEV_P12_PASSWORD: ${{ secrets.DEV_P12_PASSWORD }}
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Import Apple Development cert (used by archive with automatic signing)
echo -n "$DEV_CERTIFICATE_BASE64" | base64 --decode -o "$RUNNER_TEMP/dev_certificate.p12"
security import "$RUNNER_TEMP/dev_certificate.p12" -P "$DEV_P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
# Import Developer ID Application cert (used by exportArchive for distribution)
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o "$RUNNER_TEMP/build_certificate.p12"
security import "$RUNNER_TEMP/build_certificate.p12" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Make keychain visible to xcodebuild for both archive and export
security default-keychain -s "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH" /Library/Keychains/System.keychain
# Verify both certificates are present
echo "Installed codesigning identities:"
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
if ! security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep -q "Developer ID Application"; then
echo "::error::BUILD_CERTIFICATE_BASE64 must contain a Developer ID Application certificate."
exit 1
fi
- name: Install provisioning profiles
env:
APP_PROFILE_BASE64: ${{ secrets.APP_PROVISION_PROFILE_BASE64 }}
EXT_PROFILE_BASE64: ${{ secrets.EXT_PROVISION_PROFILE_BASE64 }}
run: |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo -n "$APP_PROFILE_BASE64" | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/CloudMount.provisionprofile
echo -n "$EXT_PROFILE_BASE64" | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/CloudMountExtension.provisionprofile
- name: Generate Xcode project
run: xcodegen generate
- name: Set version in Info.plist
run: |
VERSION="${{ steps.version.outputs.version }}"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" CloudMount/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION" CloudMountExtension/Info.plist
# Use run number as build number for monotonic increment
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${{ github.run_number }}" CloudMount/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${{ github.run_number }}" CloudMountExtension/Info.plist
- name: Decode API key for Xcode
env:
APP_STORE_CONNECT_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_KEY_BASE64 }}
run: |
echo -n "$APP_STORE_CONNECT_KEY_BASE64" | base64 --decode -o "$RUNNER_TEMP/AuthKey.p8"
- name: Archive
env:
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
API_KEY_ID: ${{ secrets.API_KEY_ID }}
API_ISSUER_ID: ${{ secrets.API_ISSUER_ID }}
run: |
xcodebuild archive \
-project CloudMount.xcodeproj \
-scheme CloudMount \
-archivePath "$RUNNER_TEMP/CloudMount.xcarchive" \
-configuration Release \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/AuthKey.p8" \
-authenticationKeyID "$API_KEY_ID" \
-authenticationKeyIssuerID "$API_ISSUER_ID" \
DEVELOPMENT_TEAM="$TEAM_ID"
- name: Export archive
env:
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
EXPORT_PLIST="$RUNNER_TEMP/export-options.plist"
cat > "$EXPORT_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>${TEAM_ID}</string>
<key>signingStyle</key>
<string>manual</string>
<key>signingCertificate</key>
<string>Developer ID Application</string>
<key>provisioningProfiles</key>
<dict>
<key>com.cloudmount.app</key>
<string>CloudMount Developer ID</string>
<key>com.cloudmount.app.extension</key>
<string>CloudMount Extension Developer ID</string>
</dict>
</dict>
</plist>
EOF
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/CloudMount.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist "$EXPORT_PLIST"
- name: Verify code signature
run: |
codesign --verify --deep --strict --verbose=2 "$RUNNER_TEMP/export/CloudMount.app"
codesign -d --entitlements :- "$RUNNER_TEMP/export/CloudMount.app/Contents/Extensions/CloudMountExtension.appex"
- name: Create DMG
run: |
chmod +x scripts/create-dmg.sh
./scripts/create-dmg.sh \
"$RUNNER_TEMP/export/CloudMount.app" \
"${{ steps.version.outputs.version }}" \
"$RUNNER_TEMP/dist"
- name: Notarize DMG
env:
APP_STORE_CONNECT_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_KEY_BASE64 }}
API_KEY_ID: ${{ secrets.API_KEY_ID }}
API_ISSUER_ID: ${{ secrets.API_ISSUER_ID }}
run: |
echo -n "$APP_STORE_CONNECT_KEY_BASE64" | base64 --decode -o "$RUNNER_TEMP/AuthKey.p8"
SUBMISSION_OUT=$(xcrun notarytool submit \
"$RUNNER_TEMP/dist/${{ steps.version.outputs.dmg-name }}" \
--key "$RUNNER_TEMP/AuthKey.p8" \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" \
--wait \
--timeout 30m 2>&1) || true
echo "$SUBMISSION_OUT"
# Extract submission ID and fetch log if notarization failed
SUBMISSION_ID=$(echo "$SUBMISSION_OUT" | grep "id:" | head -1 | awk '{print $2}')
if echo "$SUBMISSION_OUT" | grep -q "status: Invalid"; then
echo "::error::Notarization failed. Fetching detailed log..."
xcrun notarytool log "$SUBMISSION_ID" \
--key "$RUNNER_TEMP/AuthKey.p8" \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID"
exit 1
fi
xcrun stapler staple "$RUNNER_TEMP/dist/${{ steps.version.outputs.dmg-name }}"
- name: Verify notarization
run: |
xcrun stapler validate "$RUNNER_TEMP/dist/${{ steps.version.outputs.dmg-name }}"
- name: Generate checksum
run: |
cd "$RUNNER_TEMP/dist"
shasum -a 256 "${{ steps.version.outputs.dmg-name }}" > "${{ steps.version.outputs.dmg-name }}.sha256"
- name: Upload release assets
uses: actions/upload-artifact@v4
with:
name: release-assets
path: |
${{ runner.temp }}/dist/${{ steps.version.outputs.dmg-name }}
${{ runner.temp }}/dist/${{ steps.version.outputs.dmg-name }}.sha256
retention-days: 5
- name: Clean up keychain
if: ${{ always() }}
run: |
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true
rm -f "$RUNNER_TEMP/AuthKey.p8" "$RUNNER_TEMP/build_certificate.p12"
rm -rf ~/Library/MobileDevice/Provisioning\ Profiles
publish:
needs: build-sign-notarize
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v5
- name: Download release assets
uses: actions/download-artifact@v4
with:
name: release-assets
path: dist
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: CloudMount ${{ needs.build-sign-notarize.outputs.version }}
files: |
dist/${{ needs.build-sign-notarize.outputs.dmg-name }}
dist/${{ needs.build-sign-notarize.outputs.dmg-name }}.sha256
draft: false
prerelease: false
generate_release_notes: false
body: |
## CloudMount ${{ needs.build-sign-notarize.outputs.version }}
### Installation
**Download:** `${{ needs.build-sign-notarize.outputs.dmg-name }}` - open the DMG and drag CloudMount to Applications.
**Homebrew:** `brew install ebreen/cloudmount/cloudmount`
### Requirements
- macOS 26 (Tahoe) or later
- Enable the FSKit extension in System Settings -> General -> Login Items & Extensions
### Checksums
SHA-256 checksum available in `${{ needs.build-sign-notarize.outputs.dmg-name }}.sha256`
bump-cask:
needs: [build-sign-notarize, publish]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Download checksum
uses: actions/download-artifact@v4
with:
name: release-assets
path: dist
- name: Bump Homebrew Cask
env:
TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
VERSION: ${{ needs.build-sign-notarize.outputs.version }}
DMG_NAME: ${{ needs.build-sign-notarize.outputs.dmg-name }}
run: |
SHA256=$(awk '{print $1}' "dist/${DMG_NAME}.sha256")
git clone "https://x-access-token:${TAP_GITHUB_TOKEN}@github.com/ebreen/homebrew-cloudmount.git" tap
cd tap
cat > Casks/cloudmount.rb << CASK
cask "cloudmount" do
version "${VERSION}"
sha256 "${SHA256}"
url "https://github.com/ebreen/cloudmount/releases/download/v#{version}/CloudMount-#{version}.dmg"
name "CloudMount"
desc "Mount cloud storage as native macOS volumes via FSKit"
homepage "https://github.com/ebreen/cloudmount"
livecheck do
url :url
strategy :github_latest
end
auto_updates true
depends_on macos: ">= :tahoe"
app "CloudMount.app"
zap trash: [
"~/Library/Application Support/com.cloudmount.app",
"~/Library/Caches/com.cloudmount.app",
"~/Library/HTTPStorages/com.cloudmount.app",
"~/Library/Preferences/com.cloudmount.app.plist",
"~/Library/Saved Application State/com.cloudmount.app.savedState",
]
caveats <<~EOS
CloudMount requires macOS 26 (Tahoe) or later.
After installation, enable the FSKit extension:
System Settings -> General -> Login Items & Extensions -> CloudMount
Mount a volume: Use the CloudMount menu bar app, or run: mount -t b2 b2://bucket /mount/point
Unmount: Use the menu bar app, or run: diskutil unmount /mount/point
For help: https://github.com/ebreen/cloudmount
EOS
end
CASK
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Casks/cloudmount.rb
git commit -m "bump cloudmount to ${VERSION}"
git push