8686 retention-days : 14
8787
8888 macos :
89- name : macOS arm64 unsigned package
89+ name : macOS arm64 package
9090 runs-on : macos-14
9191
9292 steps :
@@ -126,6 +126,114 @@ jobs:
126126 chmod +x scripts/macos/verify-release.sh scripts/macos/verify-signed-app.sh
127127 ./scripts/macos/verify-release.sh
128128
129+ - name : Determine macOS signing mode
130+ id : signing
131+ shell : bash
132+ env :
133+ APPLE_CERTIFICATE : ${{ secrets.APPLE_CERTIFICATE }}
134+ APPLE_CERTIFICATE_PASSWORD : ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
135+ APPLE_API_KEY : ${{ secrets.APPLE_API_KEY }}
136+ APPLE_API_KEY_ID : ${{ secrets.APPLE_API_KEY_ID }}
137+ APPLE_ISSUER_ID : ${{ secrets.APPLE_ISSUER_ID }}
138+ APPLE_TEAM_ID : ${{ secrets.APPLE_TEAM_ID }}
139+ run : |
140+ set -euo pipefail
141+ if [[ -n "${APPLE_CERTIFICATE}" &&
142+ -n "${APPLE_CERTIFICATE_PASSWORD}" &&
143+ -n "${APPLE_API_KEY}" &&
144+ -n "${APPLE_API_KEY_ID}" &&
145+ -n "${APPLE_ISSUER_ID}" &&
146+ -n "${APPLE_TEAM_ID}" ]]; then
147+ echo "mode=signed" >> "${GITHUB_OUTPUT}"
148+ echo "PACKAGE_SUFFIX=signed" >> "${GITHUB_ENV}"
149+ else
150+ echo "mode=unsigned" >> "${GITHUB_OUTPUT}"
151+ echo "PACKAGE_SUFFIX=unsigned" >> "${GITHUB_ENV}"
152+ echo "Apple signing/notarization secrets are incomplete; keeping unsigned package mode."
153+ fi
154+
155+ - name : Import Developer ID certificate
156+ if : steps.signing.outputs.mode == 'signed'
157+ shell : bash
158+ env :
159+ APPLE_CERTIFICATE : ${{ secrets.APPLE_CERTIFICATE }}
160+ APPLE_CERTIFICATE_PASSWORD : ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
161+ run : |
162+ set -euo pipefail
163+ keychain_path="${RUNNER_TEMP}/vpnrouter-signing.keychain-db"
164+ keychain_password="$(openssl rand -hex 24)"
165+ certificate_path="${RUNNER_TEMP}/vpnrouter-certificate.p12"
166+ printf '%s' "${APPLE_CERTIFICATE}" | base64 -D > "${certificate_path}"
167+ security create-keychain -p "${keychain_password}" "${keychain_path}"
168+ security set-keychain-settings -lut 21600 "${keychain_path}"
169+ security unlock-keychain -p "${keychain_password}" "${keychain_path}"
170+ security import "${certificate_path}" \
171+ -P "${APPLE_CERTIFICATE_PASSWORD}" \
172+ -k "${keychain_path}" \
173+ -T /usr/bin/codesign \
174+ -T /usr/bin/security
175+ security list-keychains -d user -s "${keychain_path}"
176+ security default-keychain -s "${keychain_path}"
177+ signing_identity="$(security find-identity -v -p codesigning "${keychain_path}" |
178+ sed -n 's/.*"\(Developer ID Application:.*\)"/\1/p' | head -n 1)"
179+ if [[ -z "${signing_identity}" ]]; then
180+ echo "Developer ID Application identity was not found in the imported certificate." >&2
181+ exit 1
182+ fi
183+ {
184+ echo "SIGNING_KEYCHAIN=${keychain_path}"
185+ echo "SIGNING_IDENTITY=${signing_identity}"
186+ } >> "${GITHUB_ENV}"
187+
188+ - name : Sign macOS app and embedded extensions
189+ if : steps.signing.outputs.mode == 'signed'
190+ shell : bash
191+ env :
192+ DERIVED_DATA_PATH : ${{ runner.temp }}/vpnrouter-derived
193+ run : |
194+ set -euo pipefail
195+ app_path="${DERIVED_DATA_PATH}/Build/Products/Release/VPNRouter.app"
196+ packet_path="${app_path}/Contents/PlugIns/PacketTunnel.appex"
197+ dns_path="${app_path}/Contents/Library/SystemExtensions/com.simple.VPNRouter.DNSProxyExtension.systemextension"
198+ codesign_args=(--force --options runtime --timestamp --keychain "${SIGNING_KEYCHAIN}" --sign "${SIGNING_IDENTITY}")
199+ codesign "${codesign_args[@]}" \
200+ --entitlements macos/VPNRouter/PacketTunnel/PacketTunnel.entitlements \
201+ "${packet_path}"
202+ codesign "${codesign_args[@]}" \
203+ --entitlements macos/VPNRouter/DNSProxyExtension/DNSProxyExtension.entitlements \
204+ "${dns_path}"
205+ codesign "${codesign_args[@]}" \
206+ --entitlements macos/VPNRouter/VPNRouter/VPNRouter.entitlements \
207+ "${app_path}"
208+ VPNROUTER_PRODUCT_VERSION="${VERSION}" \
209+ VPNROUTER_BUILD_NUMBER="${GITHUB_RUN_NUMBER}" \
210+ VPNROUTER_MINIMUM_MACOS_VERSION="15.0" \
211+ scripts/macos/verify-signed-app.sh --mode distribution "${app_path}"
212+
213+ - name : Notarize and staple macOS app
214+ if : steps.signing.outputs.mode == 'signed'
215+ shell : bash
216+ env :
217+ DERIVED_DATA_PATH : ${{ runner.temp }}/vpnrouter-derived
218+ APPLE_API_KEY : ${{ secrets.APPLE_API_KEY }}
219+ APPLE_API_KEY_ID : ${{ secrets.APPLE_API_KEY_ID }}
220+ APPLE_ISSUER_ID : ${{ secrets.APPLE_ISSUER_ID }}
221+ run : |
222+ set -euo pipefail
223+ app_path="${DERIVED_DATA_PATH}/Build/Products/Release/VPNRouter.app"
224+ submit_zip="${RUNNER_TEMP}/VPNRouter-${VERSION}-notarization.zip"
225+ api_key_path="${RUNNER_TEMP}/AuthKey_${APPLE_API_KEY_ID}.p8"
226+ printf '%s' "${APPLE_API_KEY}" | base64 -D > "${api_key_path}"
227+ ditto -c -k --sequesterRsrc --keepParent "${app_path}" "${submit_zip}"
228+ xcrun notarytool submit "${submit_zip}" \
229+ --key "${api_key_path}" \
230+ --key-id "${APPLE_API_KEY_ID}" \
231+ --issuer "${APPLE_ISSUER_ID}" \
232+ --wait
233+ xcrun stapler staple "${app_path}"
234+ xcrun stapler validate "${app_path}"
235+ spctl --assess --type execute "${app_path}"
236+
129237 - name : Package macOS app as ZIP and DMG
130238 shell : bash
131239 env :
@@ -137,8 +245,8 @@ jobs:
137245 mkdir -p "${output_dir}"
138246 test -d "${app_path}"
139247
140- zip_path="${output_dir}/VPNRouter-${VERSION}-macos-arm64-unsigned .zip"
141- dmg_path="${output_dir}/VPNRouter-${VERSION}-macos-arm64-unsigned .dmg"
248+ zip_path="${output_dir}/VPNRouter-${VERSION}-macos-arm64-${PACKAGE_SUFFIX} .zip"
249+ dmg_path="${output_dir}/VPNRouter-${VERSION}-macos-arm64-${PACKAGE_SUFFIX} .dmg"
142250 ditto -c -k --sequesterRsrc --keepParent "${app_path}" "${zip_path}"
143251 hdiutil create \
144252 -volname "VPN Router ${VERSION}" \
@@ -151,11 +259,13 @@ jobs:
151259 shasum -a 256 "${dmg_path}" > "${dmg_path}.sha256"
152260 shasum -a 256 -c "${zip_path}.sha256"
153261 shasum -a 256 -c "${dmg_path}.sha256"
154- printf '%s\n' \
155- "VPN Router ${VERSION} macOS arm64 package" \
156- "This package is unsigned compile/package evidence." \
157- "System Extension activation requires an owner-signed app installed in /Applications." \
158- > "${output_dir}/VPNRouter-${VERSION}-macos-arm64-unsigned.txt"
262+ if [[ "${PACKAGE_SUFFIX}" == "unsigned" ]]; then
263+ printf '%s\n' \
264+ "VPN Router ${VERSION} macOS arm64 package" \
265+ "This package is unsigned compile/package evidence." \
266+ "System Extension activation requires an owner-signed app installed in /Applications." \
267+ > "${output_dir}/VPNRouter-${VERSION}-macos-arm64-unsigned.txt"
268+ fi
159269
160270 - name : Upload macOS release assets
161271 uses : actions/upload-artifact@v4
0 commit comments