diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6bc4b11..11754f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -328,6 +328,57 @@ jobs: releaseId: ${{ needs.draft.outputs.id }} args: --target ${{ matrix.target }} --config src-tauri/tauri.release.conf.json + # Ad-hoc signing the executable is not the same as sealing the bundle + # around it. Without a `_CodeSignature` covering the sidecars, macOS + # calls the app damaged and offers only the Trash — and that is invisible + # here, where the app is never opened, until somebody downloads it. + # + # The sidecars are required beside the executable rather than anywhere + # inside the bundle, because that is the only place the app looks: + # tools::locate resolves them against the directory holding the running + # binary and otherwise falls back to a bare name on the PATH. A copy + # sealed somewhere else would pass a laxer check and ship an app that + # tells its user ffmpeg is missing. + - name: Check the bundle is sealed around its sidecars + if: runner.os == 'macOS' + shell: bash + run: | + set -euo pipefail + + # Searched under the folder the upload step already reads, not all + # of target: a stray Transcrate.app anywhere else is whichever one + # find reaches first, and the wrong bundle would be the one checked. + # + # `-print -quit` rather than piping into `head`, because under + # pipefail head leaving early sends find SIGPIPE and the step fails + # on a bundle that is fine. `|| true` so an absent folder is reported + # by the line below rather than by find. + bundle="target/${{ matrix.target }}/release/bundle" + app="$(find "$bundle" -maxdepth 3 -name 'Transcrate.app' -print -quit 2>/dev/null || true)" + if [ -z "$app" ]; then + echo "no Transcrate.app was built" >&2 + exit 1 + fi + + # Contents/MacOS is where the bundle format puts the executable, so + # it is what current_exe().parent() returns and what the app joins a + # tool name onto. + for tool in ffmpeg ffprobe; do + if [ ! -f "$app/Contents/MacOS/$tool" ]; then + echo "$tool is not beside the executable in the bundle" >&2 + + stray="$(find "$app" -type f -name "$tool" -print -quit 2>/dev/null || true)" + if [ -n "$stray" ]; then + echo " it is at ${stray#"$app"/}, where the app will not look" >&2 + fi + exit 1 + fi + done + + # --deep reaches the nested executables, --strict refuses a seal that + # does not match what is actually in the bundle. + codesign --verify --deep --strict --verbose=2 "$app" + # Kept whether or not there is a release to attach them to. The size of # the download is the reason ffmpeg is built here rather than fetched, and # a manual run is where that gets weighed.