Skip to content

Commit 76d6c9b

Browse files
committed
Rework macOS signing for Alchemy app layout and libs
1 parent 9d8bb0f commit 76d6c9b

File tree

4 files changed

+336
-198
lines changed

4 files changed

+336
-198
lines changed

sign-pkg-mac/action.yaml

Lines changed: 62 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: sign-pkg-mac
22
description:
3-
Sign and package the macOS Linden viewer.
3+
Sign and package the macOS Alchemy viewer.
44

55
inputs:
66
imagename:
@@ -39,7 +39,7 @@ inputs:
3939
description: "setting for all steps"
4040
type: string
4141
required: false
42-
default: "Second Life"
42+
default: "Alchemy"
4343

4444
runs:
4545
using: composite
@@ -50,127 +50,93 @@ runs:
5050
name: macOS-app
5151
path: .tarball
5252

53-
- name: Unpack the tarball
53+
- name: Unpack the tarball and set vars
5454
id: unpack
5555
shell: bash
5656
run: |
5757
set -x
5858
mkdir -p ".app"
5959
tar xJf .tarball/* -C ".app"
6060
61-
- name: Set up the app sparseimage
62-
shell: bash
63-
run: |
64-
set -x -e
65-
# MBW -- If the mounted volume name changes, it breaks the .DS_Store's
66-
# background image and icon positioning. If we really need
67-
# differently named volumes, we'll need to create multiple DS_Store
68-
# file images, or use some other trick.
69-
# DO NOT CHANGE without understanding comment above
70-
volname="${{ inputs.channel_vendor_base }} Installer"
71-
sparsename="${{ inputs.imagename}}.sparseimage"
72-
rm "$sparsename" || true
73-
echo "sparsename=$sparsename" >> "$GITHUB_ENV"
74-
75-
# The capacity of the .sparseimage, which sometimes needs to be
76-
# changed, is hard-coded here instead of defined as an input because
77-
# changing it here allows us to rerun just this job. Changing it in
78-
# the viewer's build.yaml would require first rebuilding the viewer.
79-
hdiutil create "$sparsename" -volname "$volname" -fs HFS+ \
80-
-type SPARSE -megabytes 2000 -layout SPUD
81-
82-
# mount the image and get the name of the mount point and device node
83-
hdi_output="$(hdiutil attach -private "$sparsename")"
84-
85-
# with set -e in effect, test has the force of an assert
86-
[[ "$hdi_output" =~ (/dev/disk[0-9]+)[^s] ]]
87-
devfile="${BASH_REMATCH[1]}"
88-
echo "devfile=$devfile" >> "$GITHUB_ENV"
89-
[[ "$hdi_output" =~ HFS[[:space:]]+(.+) ]]
90-
volpath="${BASH_REMATCH[1]}"
91-
92-
# copy everything to the mounted sparseimage
93-
94-
# What follows is oddly based on a predefined .DS_Store file, which
95-
# apparently used to be one of several -- despite the presence of
96-
# dmg-cleanup.applescript since at least 2009. Leaving legacy behavior
97-
# for now, albeit with files transplanted from the viewer repo.
98-
dmg_prefill="${{ github.action_path }}/installer/release-dmg"
99-
for f in _VolumeIcon.icns _DS_Store background.jpg
100-
do
101-
dest="$volpath/${f/_/.}"
102-
cp -v "$dmg_prefill/$f" "$dest"
103-
# hide the files used only to control the Finder display
104-
SetFile -a V "$dest"
105-
done
106-
107-
# don't forget the application itself
10861
artifact_app="$(ls -dt .app/*.app | head -n 1)"
109-
cp -a "$artifact_app" "$volpath/"
110-
app_name="$(basename "$artifact_app")"
111-
echo "app_path=$volpath/$app_name" >> "$GITHUB_ENV"
112-
113-
# Create the alias file (which is a resource file) from the .r
114-
Rez "$dmg_prefill/Applications-alias.r" -o "$volpath/Applications"
62+
echo "app_path=$artifact_app" >> "$GITHUB_ENV"
11563
116-
# Set the alias file's alias bit
117-
SetFile -a A "$volpath/Applications"
64+
- name: Setup Python
65+
uses: actions/setup-python@v6
66+
with:
67+
python-version: '3.13'
11868

119-
# Set the disk image root's custom icon bit
120-
SetFile -a C "$volpath"
69+
- name: Install Python dependencies
70+
shell: bash
71+
run: pip install "dmgbuild[badge_icons]"
12172

122-
- name: Sign and notarize the app
73+
- name: Sign the app bundle
12374
if: inputs.cert_base64 && inputs.cert_name && inputs.cert_pass && inputs.note_user && inputs.note_pass && inputs.note_team
12475
shell: bash
12576
env:
12677
cert_base64: "${{ inputs.cert_base64 }}"
12778
cert_name: "${{ inputs.cert_name }}"
12879
cert_pass: "${{ inputs.cert_pass }}"
129-
note_user: "${{ inputs.note_user }}"
130-
note_pass: "${{ inputs.note_pass }}"
131-
note_team: "${{ inputs.note_team }}"
13280
run: |
133-
# Sign the app; do this in the copy that's in the .dmg so that the
134-
# extended attributes used by the signature are preserved; moving the
135-
# files would leave them behind and invalidate the signatures.
13681
"${{ github.action_path }}/sign.sh" "${{ env.app_path }}"
13782
138-
- name: Unmount the sparseimage
139-
# unmount even if the above fails
140-
if: ${{ ! cancelled() }}
83+
- name: Build DMG
14184
shell: bash
14285
run: |
143-
# Empirically, on GitHub we've hit errors like:
144-
# hdiutil: couldn't eject "disk10" - Resource busy
145-
retries=3
146-
retry_wait=2
147-
for (( attempt=0; attempt < $retries; attempt+=1 ))
148-
do
149-
if [[ $attempt -gt 0 ]]
150-
then
151-
echo "detach $attempt failed, waiting $retry_wait seconds before retrying" >&2
152-
sleep $retry_wait
153-
(( retry_wait*=2 ))
154-
fi
155-
hdiutil detach -force ${{ env.devfile }} && break
156-
done
157-
if [[ $? -ne 0 ]]
158-
then echo "::warning::$retries attempts to detach ${{ env.devfile }} failed"
159-
fi
86+
mkdir -p .installer
87+
dmgbuild -s "${{ github.action_path }}/dmgsettings.py" -D app="${{ env.app_path }}" "${{ inputs.channel_vendor_base }} Installer" .installer/${{ inputs.imagename }}.dmg
88+
installer=".installer/${{ inputs.imagename }}.dmg"
89+
echo "installer=$installer" >> "$GITHUB_ENV"
16090
161-
- name: Package the sparseimage as .dmg
91+
- name: Sign and notarize the dmg
92+
if: inputs.cert_base64 && inputs.cert_name && inputs.cert_pass && inputs.note_user && inputs.note_pass && inputs.note_team
16293
shell: bash
94+
env:
95+
cert_name: "${{ inputs.cert_name }}"
96+
note_user: "${{ inputs.note_user }}"
97+
note_pass: "${{ inputs.note_pass }}"
98+
note_team: "${{ inputs.note_team }}"
16399
run: |
100+
codesign --verbose --force --timestamp --keychain viewer.keychain --sign "$cert_name" "${{ env.installer }}"
101+
102+
set -x -e
103+
104+
credentials=(--apple-id "$note_user" --password "$note_pass" --team-id "$note_team")
105+
106+
# Here we send the notarization request to Apple's Notarization service,
107+
# waiting for the result. This typically takes a few seconds inside a CI
108+
# environment, but it might take more depending on the App characteristics.
109+
# Visit the Notarization docs for more information and strategies on how to
110+
# optimize it if you're curious.
111+
# emit notarytool output to stderr in real time but also capture in variable
112+
set +e
113+
output="$(xcrun notarytool submit "${{ env.installer }}" --wait \
114+
"${credentials[@]}" 2>&1 | \
115+
tee /dev/stderr ; \
116+
exit "${PIPESTATUS[0]}")"
117+
# Without the final 'exit' above, we'd be checking the rc from 'tee' rather
118+
# than 'notarytool'.
119+
rc=$?
120+
set +x
121+
[[ "$output" =~ 'id: '([^[:space:]]+) ]]
122+
match=$?
164123
set -x
165-
mkdir -p .installer
166-
installer=".installer/${{ inputs.imagename }}.dmg"
167-
rm "$installer" || true
168-
# pass installer to next step
169-
echo "installer=$installer" >> "$GITHUB_ENV"
124+
# Run notarytool log if we find an id: anywhere in the output, regardless of
125+
# rc: notarytool can terminate with rc 0 even if it fails.
126+
if [[ $match -eq 0 ]]
127+
then
128+
xcrun notarytool log "${BASH_REMATCH[1]}" "${credentials[@]}"
129+
fi
130+
[[ $rc -ne 0 ]] && exit $rc
131+
set -e
132+
133+
# Finally, we need to "attach the staple" to our executable, which will allow
134+
# our app to be validated by macOS even when an internet connection is not
135+
# available.
136+
xcrun stapler staple "${{ env.installer }}"
170137
171-
hdiutil convert "${{ env.sparsename }}" -format ULMO \
172-
-o "$installer"
173-
rm "${{ env.sparsename }}"
138+
spctl -a -tinstall -vvvv "${{ env.installer }}"
139+
xcrun stapler validate "${{ env.installer }}"
174140
175141
- name: Post the installer
176142
uses: actions/upload-artifact@v4

0 commit comments

Comments
 (0)