Skip to content

Commit cae4e0c

Browse files
feat: build, sign and notarize macOS app bundle in CI
- new build-macos-app job: Lazarus 4.8 install, make build-macos, create-macos-app.sh bundle/sign/notarize, artifact upload - with MACOS_CODESIGN_IDENTITY + APPLE_NOTARY secrets: full Developer ID signing and notarization; without: ad-hoc signed fallback bundle - signing cert imported into ephemeral keychain, cleaned up after run - release job attaches the bundle as HeidiSQL-macos-<tag>.zip - create-macos-app.sh: env-overridable identity/team/notary profile, --adhoc flag, executable auto-detection (out/ vs out/macos/), tolerant openssl@1.1 install Co-authored-by: ansgarbecker <ansgarbecker@users.noreply.github.com>
1 parent 2ad4009 commit cae4e0c

2 files changed

Lines changed: 129 additions & 11 deletions

File tree

‎.github/workflows/lazarus.yaml‎

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,89 @@ jobs:
174174
name: ${{ matrix.operating-system }}
175175
path: out/macos/heidisql
176176

177+
build-macos-app:
178+
name: macOS App Bundle
179+
if: github.repository == 'HeidiSQL/HeidiSQL'
180+
runs-on: macos-15
181+
env:
182+
GITHUB: 1
183+
steps:
184+
- name: Checkout source code
185+
uses: actions/checkout@v4
186+
187+
- name: Install Lazarus 4.8 (macOS)
188+
run: |
189+
wget -q "https://sourceforge.net/projects/lazarus/files/Lazarus%20macOS%20x86-64/Lazarus%204.8/fpc-3.2.4rc1a.intelarm64-macosx.dmg" -O "$RUNNER_TEMP/fpc.dmg"
190+
wget -q "https://sourceforge.net/projects/lazarus/files/Lazarus%20macOS%20aarch64/Lazarus%204.8/lazarus-darwin-aarch64-4.8.zip" -O "$RUNNER_TEMP/lazarus.zip"
191+
sudo hdiutil attach "$RUNNER_TEMP/fpc.dmg"
192+
FPC_VOL=$(ls -d /Volumes/fpc* | head -1)
193+
FPC_PKG=$(find "$FPC_VOL" -maxdepth 1 -name "*.pkg" | head -1)
194+
sudo installer -package "$FPC_PKG" -target /
195+
sudo hdiutil detach "$FPC_VOL"
196+
xattr -cr "$RUNNER_TEMP/lazarus.zip"
197+
unzip -q "$RUNNER_TEMP/lazarus.zip" -d "$RUNNER_TEMP"
198+
sed -i '' 's|"/Developer/lazarus/"|"'$RUNNER_TEMP'/lazarus/"|g' "$RUNNER_TEMP/lazarus/config/environmentoptions.xml" 2>/dev/null || true
199+
sudo ln -sf "$RUNNER_TEMP/lazarus/lazbuild" /usr/local/bin/lazbuild
200+
echo "$RUNNER_TEMP/lazarus" >> $GITHUB_PATH
201+
202+
- name: Import code signing certificate
203+
if: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 != '' }}
204+
env:
205+
MACOS_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
206+
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
207+
run: |
208+
KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db"
209+
KEYCHAIN_PASSWORD=$(uuidgen)
210+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
211+
security set-keychain-settings "$KEYCHAIN_PATH" -lut 21600
212+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
213+
echo "$MACOS_CERTIFICATE_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
214+
security import "$RUNNER_TEMP/certificate.p12" -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
215+
security list-keychain -d user -s "$KEYCHAIN_PATH"
216+
security set-key-partition-list -S apple-tool:,apple: -s "$KEYCHAIN_PATH" -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" >/dev/null
217+
rm -f "$RUNNER_TEMP/certificate.p12"
218+
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> "$GITHUB_ENV"
219+
220+
- name: Build HeidiSQL (macOS)
221+
run: make build-macos
222+
223+
- name: Create, sign and notarize app bundle
224+
env:
225+
MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
226+
MACOS_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }}
227+
APPLE_ID: ${{ secrets.APPLE_NOTARY_APPLE_ID }}
228+
APPLE_TEAM_ID: ${{ secrets.APPLE_NOTARY_TEAM_ID }}
229+
APPLE_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }}
230+
run: |
231+
if [ -n "${MACOS_CODESIGN_IDENTITY}" ] && [ -n "${APPLE_ID}" ]; then
232+
xcrun notarytool store-credentials notarytool-profile-ci \
233+
--apple-id "${APPLE_ID}" \
234+
--team-id "${APPLE_TEAM_ID}" \
235+
--password "${APPLE_PASSWORD}"
236+
export MACOS_NOTARY_PROFILE=notarytool-profile-ci
237+
./create-macos-app.sh --notarize
238+
else
239+
echo "Signing secrets not available, creating ad-hoc signed bundle"
240+
./create-macos-app.sh --adhoc
241+
fi
242+
243+
- name: Zip app bundle
244+
run: |
245+
cd "$(pwd)"
246+
rm -f heidisql-macos-app.zip
247+
/usr/bin/zip -r -y heidisql-macos-app.zip heidisql.app
248+
249+
- name: Upload app bundle artifact
250+
uses: actions/upload-artifact@v4
251+
with:
252+
name: macos-app-bundle
253+
path: heidisql-macos-app.zip
254+
255+
- name: Cleanup keychain
256+
if: always() && env.KEYCHAIN_PATH != ''
257+
run: |
258+
security delete-keychain "$KEYCHAIN_PATH" || true
259+
177260
build-qt6arm:
178261
name: Linux ARM64 QT6
179262
runs-on: ubuntu-24.04-arm
@@ -233,7 +316,7 @@ jobs:
233316

234317
name: Create GitHub Release
235318
runs-on: [ubuntu-latest]
236-
needs: [build, build-qt6arm]
319+
needs: [build, build-qt6arm, build-macos-app]
237320

238321
steps:
239322
- name: Checkout source code
@@ -271,6 +354,13 @@ jobs:
271354
with:
272355
name: ubuntu-24.04-arm-QT6
273356
path: out/qt6arm
357+
- name: Download macOS app bundle
358+
uses: actions/download-artifact@v4
359+
with:
360+
name: macos-app-bundle
361+
path: dist
362+
- name: Rename macOS app bundle for release
363+
run: mv dist/heidisql-macos-app.zip "dist/HeidiSQL-macos-${{ env.tag }}.zip"
274364

275365
- name: List files
276366
run: |

‎create-macos-app.sh‎

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
set -euo pipefail
33

44
DO_NOTARIZE=false
5+
DO_ADHOC=false
56
# parse args
67
for arg in "$@"; do
78
case "$arg" in
89
--notarize) DO_NOTARIZE=true ;;
10+
--adhoc) DO_ADHOC=true ;;
911
esac
1012
done
1113

@@ -18,18 +20,37 @@ APP_DIR="$(pwd)/${BUNDLE_NAME}"
1820
LPI_FILE="$(pwd)/heidisql.lpi"
1921

2022
# Path to the already built Lazarus executable
21-
EXECUTABLE_SRC="$(pwd)/out/heidisql"
23+
if [[ -f "$(pwd)/out/heidisql" ]]; then
24+
EXECUTABLE_SRC="$(pwd)/out/heidisql"
25+
elif [[ -f "$(pwd)/out/macos/heidisql" ]]; then
26+
EXECUTABLE_SRC="$(pwd)/out/macos/heidisql"
27+
else
28+
echo "ERROR: No built executable found in out/ or out/macos/" >&2
29+
exit 1
30+
fi
2231
EXECUTABLE_TRG="${APP_DIR}/Contents/MacOS/${APP_NAME}"
2332

2433
# Homebrew prefix (auto-detected; override if needed)
2534
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo "/opt/homebrew")"
2635

27-
# Your Developer ID identity, as shown by: security find-identity -v -p codesigning
28-
TEAM_ID="QBD4CC6FH3"
29-
CODESIGN_IDENTITY="Developer ID Application: Ansgar Becker (${TEAM_ID})"
36+
# Developer ID identity, overridable via environment (CI), as shown by: security find-identity -v -p codesigning
37+
TEAM_ID="${MACOS_TEAM_ID:-QBD4CC6FH3}"
38+
CODESIGN_IDENTITY="${MACOS_CODESIGN_IDENTITY:-Developer ID Application: Ansgar Becker (${TEAM_ID})}"
39+
40+
# Ad-hoc signing (no Developer ID available, e.g. fork CI builds)
41+
if $DO_ADHOC; then
42+
CODESIGN_IDENTITY="-"
43+
fi
3044

3145
# Name for notarytool keychain profile (store once with notarytool store-credentials)
32-
NOTARY_PROFILE="notarytool-profile"
46+
NOTARY_PROFILE="${MACOS_NOTARY_PROFILE:-notarytool-profile}"
47+
48+
# codesign flags: hardened runtime + secure timestamp only make sense with a real identity
49+
if $DO_ADHOC; then
50+
SIGN_OPTS=()
51+
else
52+
SIGN_OPTS=(--options runtime --timestamp)
53+
fi
3354

3455
LOCALES_ZIP_URL="https://www.heidisql.com/downloads/locale/HeidiSQL-locale.zip"
3556

@@ -74,8 +95,10 @@ brew list sqlite >/dev/null 2>&1 || brew install sqlite
7495
# MariaDB Connector/C (libmariadb*.dylib)
7596
brew list mariadb-connector-c >/dev/null 2>&1 || brew install mariadb-connector-c
7697

77-
# OpenSSL 1.1 for TFPHTTPClient
78-
brew list openssl@1.1 >/dev/null 2>&1 || brew install openssl@1.1
98+
# OpenSSL 1.1 for TFPHTTPClient (deprecated in Homebrew, may be unavailable on CI runners)
99+
if ! brew list openssl@1.1 >/dev/null 2>&1; then
100+
brew install openssl@1.1 2>/dev/null || echo "WARNING: openssl@1.1 not installable, skipping" >&2
101+
fi
79102

80103
MYSQL_LIB_DIR="${BREW_PREFIX}/opt/mysql-client/lib"
81104
PG_LIB_DIR="${BREW_PREFIX}/opt/libpq/lib"
@@ -95,7 +118,7 @@ mkdir -p "${APP_DIR}/Contents/Frameworks" # where we will put .dylib and .so f
95118
# Copy main executable
96119
cp "${EXECUTABLE_SRC}" "${EXECUTABLE_TRG}"
97120
chmod +x "${EXECUTABLE_TRG}"
98-
codesign --force --options runtime --timestamp --sign "${CODESIGN_IDENTITY}" "${EXECUTABLE_TRG}"
121+
codesign --force "${SIGN_OPTS[@]}" --sign "${CODESIGN_IDENTITY}" "${EXECUTABLE_TRG}"
99122

100123
# Minimal Info.plist (adjust identifiers/versions as needed)
101124
cat > "${APP_DIR}/Contents/Info.plist" <<EOF
@@ -320,18 +343,23 @@ echo "Done. Bundled app is at: ${APP_DIR}"
320343
echo "Signing embedded libraries..."
321344
find "${APP_DIR}/Contents" -type f \( -name "*.dylib" -o -name "*.so" \) | while read -r f; do
322345
echo " Signing ${f}"
323-
codesign --force --options runtime --timestamp --sign "${CODESIGN_IDENTITY}" "${f}"
346+
codesign --force "${SIGN_OPTS[@]}" --sign "${CODESIGN_IDENTITY}" "${f}"
324347
done
325348

326349
echo "Signing main app bundle..."
327-
codesign --force --options runtime --timestamp --deep --sign "${CODESIGN_IDENTITY}" "${APP_DIR}"
350+
codesign --force "${SIGN_OPTS[@]}" --deep --sign "${CODESIGN_IDENTITY}" "${APP_DIR}"
328351

329352
echo "Verifying code signature..."
330353
codesign --verify --deep --strict --verbose=2 "${APP_DIR}"
331354

332355

333356
### ZIP, NOTARIZE, AND STAPLE
334357

358+
if $DO_ADHOC; then
359+
echo "Skip notarization (ad-hoc signed bundle cannot be notarized)"
360+
exit 0
361+
fi
362+
335363
if ! $DO_NOTARIZE; then
336364
echo "Skip notarization (--notarize not given)"
337365
exit 0

0 commit comments

Comments
 (0)