Merge origin/rc175 (macOS signing and notarization) into rc175
Build Packages / Create release (push) Successful in 8s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m53s
Build Packages / build:portable:macos-arm64:nocuda (push) Failing after 10m37s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 14m38s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 19m15s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 19m49s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 21m41s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 23m5s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 23m47s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 25m48s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m39s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 28m52s
Build Packages / Generate python client (push) Successful in 26s
Build Packages / Build documentation (push) Successful in 1m0s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 20m25s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m44s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 21m46s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 23m32s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 22m48s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m46s
Build Packages / Unit tests (push) Successful in 1h37m57s
Build Packages / Validation Battery (push) Successful in 37m52s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-14 18:47:07 +02:00
co-authored by Claude Opus 5.5
8 changed files with 135 additions and 38 deletions
+29 -13
View File
@@ -244,11 +244,25 @@ jobs:
- name: Clean previous build
shell: bash
run: rm -rf build-macos # host runners keep the workspace between runs
# The Developer ID in the runner's login keychain signs the app (hardened runtime, secure
# timestamp) so the .dmg can be notarized below; without it the build must not go on, as an
# ad-hoc signed download is refused by Gatekeeper. Passed on as the certificate's SHA-1 (see
# JFJOCH_MACOS_SIGNING_IDENTITY in CMakeLists.txt). The notarytool credentials sit in the same
# keychain under the profile jfjoch-notary (xcrun notarytool store-credentials --keychain
# ~/Library/Keychains/login.keychain-db: that file never locks here, the default data-protection
# keychain does with the screen).
- name: Signing identity
shell: bash
run: |
set -euo pipefail
identity=$(security find-identity -v -p codesigning | sed -n 's/^ *[0-9]*) \([0-9A-F]*\) "Developer ID Application: .*/\1/p' | head -1)
if [ -z "$identity" ]; then echo "no Developer ID Application identity in the runner's keychain"; exit 1; fi
echo "MACOS_SIGNING_IDENTITY=$identity" >> "$GITHUB_ENV"
- name: Configure
shell: bash
# JFJOCH_VIEWER_ONLY is forced on macOS. No -mcpu: the arm64 baseline is what every Apple
# Silicon Mac runs, and NEON is part of it.
run: cmake -S . -B build-macos -DCMAKE_BUILD_TYPE=Release -DJFJOCH_USE_CUDA=OFF -DCMAKE_PREFIX_PATH="$QT_PREFIX"
run: cmake -S . -B build-macos -DCMAKE_BUILD_TYPE=Release -DJFJOCH_USE_CUDA=OFF -DCMAKE_PREFIX_PATH="$QT_PREFIX" -DJFJOCH_MACOS_SIGNING_IDENTITY="$MACOS_SIGNING_IDENTITY"
# The quick cross-platform checks (tests/PortableTest.cpp and the [portable] cases), before
# anything is packaged.
- name: Portable tests
@@ -274,11 +288,13 @@ jobs:
# ... and nothing in the bundle may point at this machine's Qt (or anything else under /Users)
find "$app" -type f \( -perm +111 -o -name '*.dylib' \) -exec otool -L {} + > /tmp/jfjv.libs 2>/dev/null || true
if grep -q "/Users/" /tmp/jfjv.libs; then grep "/Users/" /tmp/jfjv.libs; echo "absolute path into the build machine"; exit 1; fi
# the (ad-hoc) signature must survive everything installed into the bundle
# the Developer ID signature must survive everything installed into the bundle, and carry
# the hardened runtime that notarization demands
codesign --verify --deep --strict "$app"
# rugnux is in an archive of its own, not in the app
find "$app" -name rugnux > /tmp/jfjv.extra
if [ -s /tmp/jfjv.extra ]; then cat /tmp/jfjv.extra; echo "the bundle carries another program"; exit 1; fi
codesign -d --verbose=2 "$app" 2>&1 | grep -q "flags=.*runtime" || { echo "no hardened runtime"; exit 1; }
echo "self-contained and validly signed"
- name: Verify rugnux is self-contained
shell: bash
@@ -293,17 +309,17 @@ jobs:
# and it runs: with no arguments rugnux prints its usage
"$bin" > /tmp/rgxm.out 2>&1 || true
if ! grep -qi "usage" /tmp/rgxm.out; then cat /tmp/rgxm.out; echo "rugnux did not start"; exit 1; fi
echo "self-contained and runs"
# Notarization - needs an Apple Developer ID, which the project does not have yet. Once it
# does, the app has to be signed with it (hardened runtime, secure timestamp) BEFORE cpack
# packs it, e.g. by passing to macdeployqt, through qt_generate_deploy_app_script's
# DEPLOY_TOOL_OPTIONS in viewer/CMakeLists.txt:
# -codesign="Developer ID Application: <NAME> (<TEAMID>)" -hardened-runtime -timestamp
# and the DMG then notarized and stapled here, with an app-specific password stored once on
# the runner (xcrun notarytool store-credentials jfjoch-notary ...):
# xcrun notarytool submit build-macos/jfjoch-viewer-*.dmg --keychain-profile jfjoch-notary --wait
# xcrun stapler staple build-macos/jfjoch-viewer-*.dmg
# Until then a downloaded DMG is blocked by Gatekeeper on first open (right-click > Open).
# and carries the Developer ID signature with the hardened runtime that notarization demands
codesign --verify --strict "$bin"
codesign -d --verbose=2 "$bin" 2>&1 | grep -q "flags=.*runtime" || { echo "no hardened runtime"; exit 1; }
echo "self-contained, runs and is validly signed"
# Every push, not only a release: a rejection by Apple (a library macdeployqt left unsigned, an
# entitlement the hardened runtime needs) should show up when the change is made, not when the
# release is cut. Signs the image, waits for Apple's verdict, staples the ticket to the image;
# the rugnux binary only gets its hash registered, a bare executable has nowhere to staple.
- name: Notarize
shell: bash
run: tools/notarize_macos.sh build-macos/jfjoch-viewer-*.dmg build-macos/rugnux-*.tar.gz
- name: Upload DMG and tgz to release
if: github.event_name == 'workflow_dispatch' && (github.event.inputs.create_release == 'true' || github.event.inputs.create_release == true)
shell: bash
+9
View File
@@ -79,6 +79,15 @@ SET(JFJOCH_VIEWER_BUILD OFF CACHE BOOL "Compile Jungfraujoch viewer")
# artifact has to ship a runtime library beside its executable.
SET(JFJOCH_RUGNUX_ONLY OFF CACHE BOOL "Compile only rugnux, the offline analysis CLI (no Qt)")
# macOS only: the Developer ID Application certificate to sign the shipped program with at install
# time - hardened runtime and a secure timestamp, which is what Apple's notarization demands. Given
# as the certificate's SHA-1 hash, the 40 hex digits `security find-identity -v -p codesigning`
# prints in front of the name: the name itself ("Developer ID Application: <name> (<team>)") has
# spaces, and Qt's generated deploy script would split it into separate macdeployqt arguments. Empty
# (the default) keeps the ad-hoc signature, which runs locally but is refused by Gatekeeper after a
# download. Notarizing the packed artefact is a separate step after cpack: tools/notarize_macos.sh.
SET(JFJOCH_MACOS_SIGNING_IDENTITY "" CACHE STRING "macOS: SHA-1 of the Developer ID Application certificate to codesign with (empty: ad-hoc)")
# Only the viewer (and its portable dependency tree) is supported on Windows and macOS -- the
# broker/receiver/FPGA/writer server stack is Linux-only. Force viewer-only on those platforms so a
# plain configure builds the right subset; on Linux it remains a user-togglable option. A rugnux-only
+9 -2
View File
@@ -10,8 +10,7 @@ Gitea release page, and for Linux also in the Jungfraujoch RPM/APT repositories
[Release contents](RELEASE_CONTENTS.md) for what each package contains and what it requires, and
[Deployment](DEPLOYMENT.md) for how to install it. The macOS build needs **macOS 13 (Ventura) or
newer on an Apple Silicon Mac** (M1 and newer; Intel Macs are not supported) and is CPU-only — see
[Release contents ▸ macOS](RELEASE_CONTENTS.md#macos), which also covers opening it for the first
time, as the release is not yet notarized by Apple.
[Release contents ▸ macOS](RELEASE_CONTENTS.md#macos).
## Where it fits among the three analysis tools
@@ -332,3 +331,11 @@ As on Windows, `CMAKE_PREFIX_PATH` (Qt) is the only required flag. To produce th
Qt frameworks and the license notices copied into the app with `macdeployqt`, packed into
`jfjoch-viewer-<version>-macos-arm64.dmg` — run `cpack` in the build directory. What comes out is
described in [Release contents ▸ macOS](RELEASE_CONTENTS.md#macos).
The released image is signed with the project's Apple Developer ID and notarized by Apple. A build of
your own is signed ad hoc, which runs on the machine that built it but is refused by Gatekeeper once
downloaded. To sign with a Developer ID from your own keychain, configure with
`-DJFJOCH_MACOS_SIGNING_IDENTITY=<hash>` before `cpack`, where the hash is the 40 hex digits
`security find-identity -v -p codesigning` prints in front of the certificate's name, then notarize
and staple the image with `tools/notarize_macos.sh build-mac/jfjoch-viewer-*.dmg` (it needs
notarytool credentials stored once on the machine; the script's header says how).
+4 -11
View File
@@ -173,17 +173,10 @@ app onto the *Applications* shortcut next to it. The app is self-contained: the
inside it, and so are the license notices (`Contents/Resources`), which is where a macOS app keeps
them.
**The release is not notarized yet** (that needs an Apple Developer account), so macOS refuses to
open a downloaded copy the first time. On macOS 15 and newer: try to open it once, then allow it in
*System Settings ▸ Privacy & Security* with *Open Anyway*. On macOS 13 and 14, Control-click the app
and choose *Open*. Alternatively, clear the download flag in Terminal:
```
xattr -dr com.apple.quarantine /Applications/jfjoch_viewer.app
```
The same applies to the `rugnux` binary from its `.tgz` — see
[Installing Rugnux](RUGNUX_INSTALL.md#from-the-release-archive).
Both artefacts are signed with the project's Apple Developer ID and **notarized by Apple**, so a
downloaded copy opens like any other Mac application. The disk image carries its notarization ticket;
the `rugnux` binary in the `.tgz` cannot (a bare executable has nowhere to staple one), so the first
run of a freshly downloaded `rugnux` looks the ticket up online and needs an internet connection.
The toolchain of the released macOS artefacts is Xcode (Apple Clang) and Qt 6.11 for macOS,
including Qt Charts; to build the viewer yourself see
+4 -11
View File
@@ -62,17 +62,10 @@ Nothing is written outside that directory, nothing needs root, and several versi
side. To remove it, delete the directory. Put `bin/` on your `PATH` if you want to type `rugnux`
rather than the full path.
> **macOS: the first run is blocked.** The release is not yet notarized by Apple, and a `.tgz`
> downloaded with a browser hands its download flag on to everything `tar` extracts from it, so
> macOS refuses to run `rugnux` ("cannot be opened because the developer cannot be verified"). Clear
> the flag once for the whole directory:
>
> ```
> xattr -dr com.apple.quarantine ~/rugnux-1.0.0
> ```
>
> An archive fetched with `curl` carries no such flag. Unpacking into a directory under your home
> directory (`~/rugnux-<version>`) rather than `/opt` avoids needing `sudo` on a Mac.
> **macOS:** the binary is signed with the project's Apple Developer ID and notarized, so a `.tgz`
> downloaded with a browser runs without further steps; the first run of a freshly downloaded copy
> checks the notarization online. Unpacking into a directory under your home directory
> (`~/rugnux-<version>`) rather than `/opt` avoids needing `sudo` on a Mac.
> **Mixing the two.** If a `rugnux` package is also installed, `/usr/bin/rugnux` will normally win
> on `PATH`. Put the archive's `bin/` first, or call it by its full path, to be sure which one you
+10
View File
@@ -37,6 +37,16 @@ TARGET_LINK_LIBRARIES(JFJochRugnux JFJochReader JFJochImageAnalysis JFJochWriter
ADD_EXECUTABLE(rugnux rugnux_cli.cpp RugnuxConsole.cpp RugnuxConsole.h)
TARGET_LINK_LIBRARIES(rugnux JFJochRugnux JFJochReader JFJochImageAnalysis JFJochWriter)
INSTALL(TARGETS rugnux RUNTIME COMPONENT rugnux)
# macOS: sign the installed binary with the Developer ID (hardened runtime, secure timestamp) so the
# .tgz can be notarized - see JFJOCH_MACOS_SIGNING_IDENTITY in the top-level CMakeLists.txt. The
# binary links only system libraries, so no entitlements are needed.
IF(APPLE AND JFJOCH_MACOS_SIGNING_IDENTITY)
INSTALL(CODE "execute_process(COMMAND codesign --force --options runtime --timestamp
--sign \"${JFJOCH_MACOS_SIGNING_IDENTITY}\"
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/rugnux\"
COMMAND_ERROR_IS_FATAL ANY)"
COMPONENT rugnux)
ENDIF()
# libcufft_static.a carries a relocatable-device-code object (separate_callback.o), so an executable
# linking it needs a CUDA device-link step -- without it the host link fails on an undefined
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# Notarize the macOS release artefacts with Apple and staple the ticket where one can be stapled.
#
# tools/notarize_macos.sh <jfjoch-viewer-*.dmg | rugnux-*.tar.gz> ...
#
# The programs inside must already carry a Developer ID signature with hardened runtime and secure
# timestamp: configure with -DJFJOCH_MACOS_SIGNING_IDENTITY=<certificate SHA-1> and let cpack produce
# the artefact (see the top-level CMakeLists.txt). The same certificate signs the .dmg here: the
# one JFJOCH_MACOS_SIGNING_IDENTITY names, else the Developer ID Application in the keychain. Needs
# notarytool credentials stored once on the machine, under the profile named by JFJOCH_NOTARY_PROFILE
# (default jfjoch-notary). On an unattended machine store them in the login keychain file: without
# --keychain they go into the data-protection keychain, which is unreadable while the screen is
# locked, and the step then fails with "No Keychain password item found".
#
# xcrun notarytool store-credentials jfjoch-notary --apple-id <apple id> --team-id <team id> \
# --keychain ~/Library/Keychains/login.keychain-db
#
# .dmg signed with the same Developer ID, submitted, the ticket stapled to the image.
# .tar.gz the rugnux binary inside is zipped and submitted; Apple then knows its hash, which is
# all Gatekeeper needs (a bare executable has nowhere to staple a ticket). The archive is
# shipped unchanged.
set -euo pipefail
profile=${JFJOCH_NOTARY_PROFILE:-jfjoch-notary}
identity=${JFJOCH_MACOS_SIGNING_IDENTITY:-$(security find-identity -v -p codesigning | sed -n 's/^ *[0-9]*) \([0-9A-F]*\) "Developer ID Application: .*/\1/p' | head -1)}
if [ -z "$identity" ]; then echo "no Developer ID Application identity in the keychain"; exit 1; fi
submit() { # submit <file>: wait for Apple's verdict, print the log on rejection
local out id status
out=$(xcrun notarytool submit "$1" --keychain-profile "$profile" --wait --output-format plist)
id=$(echo "$out" | plutil -extract id raw -)
status=$(echo "$out" | plutil -extract status raw -)
echo "notarization of $(basename "$1"): $status (submission $id)"
if [ "$status" != "Accepted" ]; then
xcrun notarytool log "$id" --keychain-profile "$profile"
exit 1
fi
}
for file in "$@"; do
case "$file" in
*.dmg)
codesign --force --timestamp --sign "$identity" "$file"
submit "$file"
xcrun stapler staple "$file"
spctl -a -t open --context context:primary-signature -v "$file"
;;
*.tar.gz|*.tgz)
tmp=$(mktemp -d)
tar xf "$file" -C "$tmp"
bin=$(find "$tmp" -name rugnux -type f)
ditto -c -k "$bin" "$tmp/rugnux.zip"
submit "$tmp/rugnux.zip"
rm -rf "$tmp"
;;
*)
echo "don't know how to notarize $file"; exit 1
;;
esac
done
+10 -1
View File
@@ -246,11 +246,20 @@ qt_import_plugins(jfjoch_viewer EXCLUDE Qt::QJpegPlugin Qt::QTiffPlugin)
# which is when the OUTPUT_SCRIPT signature and the Linux deploy path landed (6.3/6.4 used the older
# FILENAME_VARIABLE and had no Linux support); skipped on older Qt. NO_UNSUPPORTED_PLATFORM_ERROR
# keeps it from hard-failing on configurations it does not handle.
# On macOS macdeployqt also signs the bundle (ad-hoc by default); with JFJOCH_MACOS_SIGNING_IDENTITY
# set it signs with that Developer ID instead, hardened runtime and secure timestamp included, which
# is what the .dmg needs to be notarized afterwards (DEPLOY_TOOL_OPTIONS needs Qt >= 6.7).
IF(Qt6_VERSION VERSION_GREATER_EQUAL "6.5")
SET(_viewer_deploy_tool_options)
IF(APPLE AND JFJOCH_MACOS_SIGNING_IDENTITY)
SET(_viewer_deploy_tool_options
DEPLOY_TOOL_OPTIONS "-sign-for-notarization=${JFJOCH_MACOS_SIGNING_IDENTITY}")
ENDIF()
qt_generate_deploy_app_script(
TARGET jfjoch_viewer
OUTPUT_SCRIPT jfjoch_viewer_deploy_script
NO_UNSUPPORTED_PLATFORM_ERROR)
NO_UNSUPPORTED_PLATFORM_ERROR
${_viewer_deploy_tool_options})
INSTALL(SCRIPT ${jfjoch_viewer_deploy_script} COMPONENT viewer)
ENDIF()