-
Notifications
You must be signed in to change notification settings - Fork 0
325 lines (297 loc) · 14.8 KB
/
Copy pathaddon.yml
File metadata and controls
325 lines (297 loc) · 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
name: addon
# Builds the per-platform Blender addon packages (self-contained bridge bundled), signs the macOS
# bridge binary, and attaches everything to the GitHub Release. Triggered by an `addon-v*` tag; can
# also be run manually.
#
# All three RIDs are cross-published from a single Linux runner. This is deliberate: the bridge weaves
# aspects with AspectInjector at build time, and its osx-arm64 native tool gets SIGKILL'd by Gatekeeper
# on the macOS runner. On Linux the (managed) weaving runs with the Linux tool and `dotnet publish -r
# <rid>` still produces a working self-contained build for every target. Ships the SELF-CONTAINED
# variant (no .NET prerequisite for artists).
#
# SIGNING (mirrors the WitCloud client-release pipeline; same secret names):
# - macOS: a dedicated macos-latest job codesigns the bundled bridge Mach-O (Developer ID
# Application, hardened runtime + .NET entitlements) and notarizes the addon zip with notarytool.
# A bare executable cannot be STAPLED (only .app/.dmg/.pkg can) — Gatekeeper validates the
# notarization ticket online, which is the standard path for CLI binaries shipped in zips.
# Required repo secrets: APPLE_CERT_P12_BASE64, APPLE_CERT_PASSWORD, APPLE_ID,
# APPLE_APP_PASSWORD, APPLE_TEAM_ID. The job degrades to a no-op (with a warning) until they
# are configured, so unsigned debug releases keep working.
# - Windows: a dedicated sign-windows job Authenticode-signs the bundled bridge .exe via SSL.com
# eSigner (cloud CodeSignTool) — the same mechanism + secrets as the WitCloud client release.
# Required repo secrets: ESIGNER_USERNAME, ESIGNER_PASSWORD, ESIGNER_CREDENTIAL_ID,
# ESIGNER_TOTP_SECRET. The eSigner tier is quota-limited (~240/yr), so the job signs public tags
# (-beta / stable / -rc) but NOT internal iteration tags (-dev/-test/-internal); it degrades to a
# no-op (ships UNSIGNED, with a warning) when the secrets are absent.
# - Linux: no per-binary signing (per the signing strategy); the release ships SHA256SUMS and a
# detached GPG signature when GPG_PRIVATE_KEY/GPG_PASSPHRASE are configured.
#
# Not yet automated (see @Archive/docs/addon-packaging-and-distribution.md §4): regenerating +
# publishing the extension repository index.json for in-Blender auto-update — that lands with the
# portal (the index/zips will be hosted there).
on:
push:
tags:
- 'addon-v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rid: [win-x64, linux-x64, osx-arm64]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Build addon package (${{ matrix.rid }}, self-contained)
shell: pwsh
run: ./OutWit.Render.BlenderAddon/Build-BlenderAddonPackage.ps1 -RuntimeIdentifier ${{ matrix.rid }} -DeploymentMode SelfContained
# Guards two packaging invariants: blender_manifest.toml must sit at the zip ROOT (Blender's
# extension installer requirement), and unix bridges must carry the executable bit (native
# zip path in the build script; Compress-Archive would silently drop it).
- name: Verify package layout
shell: bash
run: |
set -euo pipefail
ZIP=$(ls OutWit.Render.BlenderAddon/dist/*.zip | head -1)
unzip -l "$ZIP" | sed -n '1,12p'
unzip -l "$ZIP" | awk '{print $4}' | grep -qx 'blender_manifest.toml' \
|| { echo "::error::blender_manifest.toml is not at the zip root"; exit 1; }
if [ "${{ matrix.rid }}" != "win-x64" ]; then
INFO=$(zipinfo "$ZIP" | grep 'OutWit.Render.BlenderBridge$' || true)
echo "$INFO"
echo "$INFO" | grep -q '^-rwx' \
|| { echo "::error::bridge binary is not executable inside the package"; exit 1; }
fi
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: addon-${{ matrix.rid }}
path: OutWit.Render.BlenderAddon/dist/*.zip
if-no-files-found: error
sign-macos:
name: sign + notarize macOS bridge
needs: build
runs-on: macos-latest
steps:
- name: Download macOS package
uses: actions/download-artifact@v4
with:
name: addon-osx-arm64
path: stage
# Secrets are not available in job-level `if`, so gate inside the job: without the Apple
# credentials this job is a no-op and the unsigned zip ships as-is (debug-friendly).
- name: Check signing credentials
id: gate
env:
APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }}
run: |
if [ -z "${APPLE_CERT_P12_BASE64:-}" ]; then
echo "::warning::APPLE_CERT_P12_BASE64 not configured — shipping the macOS package UNSIGNED. Add APPLE_CERT_P12_BASE64/APPLE_CERT_PASSWORD/APPLE_ID/APPLE_APP_PASSWORD/APPLE_TEAM_ID to sign+notarize."
echo "enabled=false" >> "$GITHUB_OUTPUT"
else
echo "enabled=true" >> "$GITHUB_OUTPUT"
fi
- name: Sign the bridge binary
if: steps.gate.outputs.enabled == 'true'
env:
APPLE_CERT_P12_BASE64: ${{ secrets.APPLE_CERT_P12_BASE64 }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
run: |
set -euo pipefail
ZIP=$(ls stage/*.zip | head -1)
echo "Package: $ZIP"
WORK="$RUNNER_TEMP/addon"; rm -rf "$WORK"; mkdir -p "$WORK"
unzip -q "$ZIP" -d "$WORK"
BRIDGE=$(find "$WORK" -type f -name 'OutWit.Render.BlenderBridge' -path '*osx-arm64*' | head -1)
if [ -z "$BRIDGE" ]; then
echo "::error::bridge binary not found inside the package"; exit 1
fi
echo "Bridge binary: $BRIDGE"
# Belt-and-braces: the build job stores the exec bit via native zip, but make sure the
# re-packed archive always ships a runnable binary.
chmod +x "$BRIDGE"
# Temp keychain with the Developer ID Application cert (client-release pattern).
echo "$APPLE_CERT_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/apple_cert.p12"
KEYCHAIN="$RUNNER_TEMP/sign.keychain-db"
security create-keychain -p ci "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p ci "$KEYCHAIN"
security import "$RUNNER_TEMP/apple_cert.p12" -k "$KEYCHAIN" -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k ci "$KEYCHAIN" >/dev/null
security list-keychains -d user -s "$KEYCHAIN" login.keychain-db
DEVID=$(security find-identity -v -p codesigning "$KEYCHAIN" | awk '/Developer ID Application/{print $2; exit}')
echo "Signing identity: $DEVID"
# .NET hardened-runtime entitlements (the bridge JITs, talks to the cloud over HTTPS and
# binds the loopback REST server).
ENT="$RUNNER_TEMP/entitlements.plist"
cat > "$ENT" <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
<key>com.apple.security.cs.disable-library-validation</key><true/>
<key>com.apple.security.network.client</key><true/>
<key>com.apple.security.network.server</key><true/>
</dict></plist>
PLIST
codesign --force --timestamp --options runtime --entitlements "$ENT" --sign "$DEVID" "$BRIDGE"
codesign --verify --strict --verbose=2 "$BRIDGE"
# Re-zip with the original name and the original internal layout.
NAME=$(basename "$ZIP")
(cd "$WORK" && zip -qry "$RUNNER_TEMP/$NAME" .)
mv "$RUNNER_TEMP/$NAME" "$ZIP"
echo "ZIP_PATH=$ZIP" >> "$GITHUB_ENV"
- name: Notarize the package
if: steps.gate.outputs.enabled == 'true'
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
echo "Submitting $ZIP_PATH to the Apple notary service (waits for the result)..."
SUBMIT_OUT=$(xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" --password "$APPLE_APP_PASSWORD" --team-id "$APPLE_TEAM_ID" \
--wait 2>&1) || true
echo "$SUBMIT_OUT"
SUB_ID=$(echo "$SUBMIT_OUT" | awk '/id:/{print $2; exit}')
if ! echo "$SUBMIT_OUT" | grep -q "status: Accepted"; then
echo "::error::Notarization not accepted (id=$SUB_ID). Fetching the detailed log:"
xcrun notarytool log "$SUB_ID" \
--apple-id "$APPLE_ID" --password "$APPLE_APP_PASSWORD" --team-id "$APPLE_TEAM_ID" || true
exit 1
fi
# A bare binary in a zip cannot be stapled — Gatekeeper validates the ticket online.
- name: Upload signed package
uses: actions/upload-artifact@v4
with:
name: addon-osx-arm64
path: stage/*.zip
overwrite: true
if-no-files-found: error
sign-windows:
name: sign Windows bridge (SSL.com eSigner)
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Windows package
uses: actions/download-artifact@v4
with:
name: addon-win-x64
path: stage
# Authenticode-sign the bundled bridge .exe via SSL.com eSigner (cloud) — the same mechanism the
# WitCloud client release uses (ESIGNER_* secrets). The eSigner tier is quota-limited (~240/yr), so
# sign every tag EXCEPT internal iteration tags (-dev/-test/-internal); the public `-beta`/stable
# releases ARE signed. No-op (ships UNSIGNED) when the ESIGNER_* secrets are absent, so the release
# still ships. macOS notarization + Linux GPG/SHA256SUMS are unaffected.
- name: Decide Windows signing
id: gate
env:
ESIGNER_USERNAME: ${{ secrets.ESIGNER_USERNAME }}
run: |
set -euo pipefail
should=false
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then should=true; fi
if [ "${{ github.ref_type }}" = "tag" ]; then
case "${{ github.ref_name }}" in
*-dev*|*-test*|*-internal*) : ;; # internal iteration tag → don't sign (save quota)
*) should=true ;; # public release (-beta / stable / -rc) → sign
esac
fi
if [ -z "${ESIGNER_USERNAME:-}" ]; then
[ "$should" = "true" ] && echo "::warning::Windows signing requested but ESIGNER_* secrets are not set — shipping the Windows package UNSIGNED."
should=false
fi
echo "enabled=$should" >> "$GITHUB_OUTPUT"
echo "Windows signing: enabled=$should"
- name: Unpack the addon package
if: steps.gate.outputs.enabled == 'true'
run: |
set -euo pipefail
ZIP=$(ls stage/*.zip | head -1)
echo "ZIP_NAME=$(basename "$ZIP")" >> "$GITHUB_ENV"
WORK="$RUNNER_TEMP/addon"; rm -rf "$WORK"; mkdir -p "$WORK"
unzip -q "$ZIP" -d "$WORK"
BRIDGE=$(find "$WORK" -type f -name 'OutWit.Render.BlenderBridge.exe' | head -1)
if [ -z "$BRIDGE" ]; then
echo "::error::bridge .exe not found inside the Windows package"; exit 1
fi
echo "Bridge binary: $BRIDGE"
echo "BRIDGE_PATH=$BRIDGE" >> "$GITHUB_ENV"
echo "WORK_DIR=$WORK" >> "$GITHUB_ENV"
# CodeSignTool copies the signed file into output_path (errors if it equals the input dir), so sign
# into a separate dir and move it back. We sign only the bridge .exe — the native binary Windows /
# SmartScreen evaluate — not the inner DLLs.
- name: Sign the bridge (SSL.com eSigner)
if: steps.gate.outputs.enabled == 'true'
uses: sslcom/esigner-codesign@develop
with:
command: sign
username: ${{ secrets.ESIGNER_USERNAME }}
password: ${{ secrets.ESIGNER_PASSWORD }}
credential_id: ${{ secrets.ESIGNER_CREDENTIAL_ID }}
totp_secret: ${{ secrets.ESIGNER_TOTP_SECRET }}
file_path: ${{ env.BRIDGE_PATH }}
output_path: ${{ runner.temp }}/signed
malware_block: 'false'
environment_name: PROD
- name: Repack with the signed bridge
if: steps.gate.outputs.enabled == 'true'
run: |
set -euo pipefail
mv -f "$RUNNER_TEMP/signed/OutWit.Render.BlenderBridge.exe" "$BRIDGE_PATH"
(cd "$WORK_DIR" && zip -qry "$RUNNER_TEMP/$ZIP_NAME" .)
rm -rf stage-signed; mkdir -p stage-signed
mv -f "$RUNNER_TEMP/$ZIP_NAME" "stage-signed/$ZIP_NAME"
echo "Repacked the Windows package with the signed bridge."
- name: Upload signed package
if: steps.gate.outputs.enabled == 'true'
uses: actions/upload-artifact@v4
with:
name: addon-win-x64
path: stage-signed/*.zip
overwrite: true
if-no-files-found: error
release:
needs: [build, sign-macos, sign-windows]
if: startsWith(github.ref, 'refs/tags/addon-v')
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
# A single SHA256SUMS over every package + a detached GPG signature when the key is
# configured (same trust mechanism as the client release; verify with
# `gpg --verify SHA256SUMS.asc SHA256SUMS && sha256sum -c SHA256SUMS`).
- name: SHA256SUMS + GPG signature
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
shell: bash
run: |
set -euo pipefail
mkdir -p release
find artifacts -type f -name '*.zip' -exec cp {} release/ \;
cd release
sha256sum *.zip > SHA256SUMS
echo "=== SHA256SUMS ==="; cat SHA256SUMS
if [ -z "${GPG_PRIVATE_KEY:-}" ]; then
echo "::warning::GPG_PRIVATE_KEY secret not set — shipping SHA256SUMS WITHOUT a signature."
exit 0
fi
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
gpg --batch --pinentry-mode loopback --passphrase "${GPG_PASSPHRASE:-}" \
--detach-sign --armor -o SHA256SUMS.asc SHA256SUMS
echo "=== signed ==="; ls -la SHA256SUMS*
- name: Attach addon packages to the release
uses: softprops/action-gh-release@v2
with:
files: release/*
prerelease: ${{ contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') || contains(github.ref_name, '-rc') }}