Skip to content

Commit 45d6b31

Browse files
ci: bootstrap cross-repo packaging workflows
0 parents  commit 45d6b31

5 files changed

Lines changed: 484 additions & 0 deletions

File tree

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
name: Package FutrixData
2+
3+
on:
4+
repository_dispatch:
5+
types:
6+
- futrix-package-request
7+
workflow_dispatch:
8+
inputs:
9+
source_repository:
10+
description: "Source repository in owner/name format"
11+
required: true
12+
type: string
13+
source_ref:
14+
description: "Source git ref, for example refs/heads/main or refs/tags/v1.0.0"
15+
required: true
16+
default: "refs/heads/main"
17+
type: string
18+
source_sha:
19+
description: "Optional exact source commit SHA"
20+
required: false
21+
type: string
22+
version:
23+
description: "Optional version override"
24+
required: false
25+
type: string
26+
release:
27+
description: "Create a release in the packaging repository"
28+
required: false
29+
default: false
30+
type: boolean
31+
32+
permissions:
33+
contents: write
34+
35+
jobs:
36+
prepare:
37+
runs-on: ubuntu-latest
38+
outputs:
39+
source_repository: ${{ steps.resolve.outputs.source_repository }}
40+
source_ref: ${{ steps.resolve.outputs.source_ref }}
41+
source_sha: ${{ steps.resolve.outputs.source_sha }}
42+
source_checkout_ref: ${{ steps.resolve.outputs.source_checkout_ref }}
43+
version: ${{ steps.resolve.outputs.version }}
44+
release: ${{ steps.resolve.outputs.release }}
45+
source_run_url: ${{ steps.resolve.outputs.source_run_url }}
46+
triggered_by: ${{ steps.resolve.outputs.triggered_by }}
47+
event_name: ${{ steps.resolve.outputs.event_name }}
48+
steps:
49+
- name: Resolve packaging request
50+
id: resolve
51+
shell: bash
52+
run: |
53+
set -euo pipefail
54+
55+
source_repository="$(jq -r '.client_payload.source_repository // .inputs.source_repository // ""' "$GITHUB_EVENT_PATH")"
56+
source_ref="$(jq -r '.client_payload.source_ref // .inputs.source_ref // ""' "$GITHUB_EVENT_PATH")"
57+
source_sha="$(jq -r '.client_payload.source_sha // .inputs.source_sha // ""' "$GITHUB_EVENT_PATH")"
58+
source_checkout_ref="${source_sha:-$source_ref}"
59+
version="$(jq -r '.client_payload.version // .inputs.version // ""' "$GITHUB_EVENT_PATH")"
60+
release="$(jq -r '.client_payload.release // .inputs.release // "false"' "$GITHUB_EVENT_PATH")"
61+
source_run_url="$(jq -r '.client_payload.source_run_url // ""' "$GITHUB_EVENT_PATH")"
62+
triggered_by="$(jq -r '.client_payload.triggered_by // ""' "$GITHUB_EVENT_PATH")"
63+
event_name="$(jq -r '.client_payload.event_name // ""' "$GITHUB_EVENT_PATH")"
64+
65+
if [ -z "$triggered_by" ]; then
66+
triggered_by="$GITHUB_ACTOR"
67+
fi
68+
69+
if [ -z "$event_name" ]; then
70+
event_name="$GITHUB_EVENT_NAME"
71+
fi
72+
73+
if [ -z "$source_repository" ]; then
74+
echo "source_repository is required" >&2
75+
exit 1
76+
fi
77+
78+
if [ -z "$source_ref" ] && [ -z "$source_sha" ]; then
79+
echo "Either source_ref or source_sha is required" >&2
80+
exit 1
81+
fi
82+
83+
if [ -z "$version" ]; then
84+
if [[ "$source_ref" == refs/tags/* ]]; then
85+
version="${source_ref#refs/tags/}"
86+
elif [ -n "$source_sha" ]; then
87+
version="sha-${source_sha:0:7}"
88+
else
89+
version="manual-${GITHUB_RUN_ID}"
90+
fi
91+
fi
92+
93+
if [[ "$source_ref" == refs/tags/* ]]; then
94+
release=true
95+
fi
96+
97+
{
98+
echo "source_repository=$source_repository"
99+
echo "source_ref=$source_ref"
100+
echo "source_sha=$source_sha"
101+
echo "source_checkout_ref=$source_checkout_ref"
102+
echo "version=$version"
103+
echo "release=$release"
104+
echo "source_run_url=$source_run_url"
105+
echo "triggered_by=$triggered_by"
106+
echo "event_name=$event_name"
107+
} >> "$GITHUB_OUTPUT"
108+
109+
package-macos:
110+
needs: prepare
111+
runs-on: macos-latest
112+
env:
113+
SOURCE_REPOSITORY: ${{ needs.prepare.outputs.source_repository }}
114+
SOURCE_REF: ${{ needs.prepare.outputs.source_ref }}
115+
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
116+
SOURCE_CHECKOUT_REF: ${{ needs.prepare.outputs.source_checkout_ref }}
117+
VERSION: ${{ needs.prepare.outputs.version }}
118+
MACOS_WAILS_PLATFORM: ${{ vars.MACOS_WAILS_PLATFORM || 'darwin/universal' }}
119+
MACOS_BUNDLE_ID: ${{ vars.MACOS_BUNDLE_ID || 'com.futrixdata.app' }}
120+
steps:
121+
- name: Checkout packaging repository
122+
uses: actions/checkout@v4
123+
124+
- name: Checkout source repository
125+
uses: actions/checkout@v4
126+
with:
127+
repository: ${{ env.SOURCE_REPOSITORY }}
128+
ref: ${{ env.SOURCE_CHECKOUT_REF }}
129+
token: ${{ secrets.SOURCE_REPO_READ_TOKEN }}
130+
path: source
131+
fetch-depth: 1
132+
133+
- name: Set up Go
134+
uses: actions/setup-go@v5
135+
with:
136+
go-version-file: source/go.mod
137+
138+
- name: Set up Node.js
139+
uses: actions/setup-node@v4
140+
with:
141+
node-version: 20
142+
cache: npm
143+
cache-dependency-path: source/frontend/package-lock.json
144+
145+
- name: Install Wails CLI
146+
shell: bash
147+
run: |
148+
set -euo pipefail
149+
go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
150+
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
151+
152+
- name: Import macOS signing certificate
153+
shell: bash
154+
env:
155+
MACOS_CERTIFICATE_P12_BASE64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
156+
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
157+
run: |
158+
set -euo pipefail
159+
CERT_PATH="$RUNNER_TEMP/macos-signing-cert.p12"
160+
KEYCHAIN_PATH="$RUNNER_TEMP/packaging.keychain-db"
161+
KEYCHAIN_PASSWORD="$(uuidgen)"
162+
163+
MACOS_CERTIFICATE_P12_BASE64="$MACOS_CERTIFICATE_P12_BASE64" \
164+
python3 - <<'PY' > "$CERT_PATH"
165+
import base64
166+
import os
167+
import sys
168+
169+
sys.stdout.buffer.write(base64.b64decode(os.environ["MACOS_CERTIFICATE_P12_BASE64"]))
170+
PY
171+
172+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
173+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
174+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
175+
security import "$CERT_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
176+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
177+
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
178+
179+
- name: Decode App Store Connect key
180+
shell: bash
181+
env:
182+
MACOS_NOTARY_PRIVATE_KEY_BASE64: ${{ secrets.MACOS_NOTARY_PRIVATE_KEY_BASE64 }}
183+
run: |
184+
set -euo pipefail
185+
NOTARY_KEY_PATH="$RUNNER_TEMP/AuthKey.p8"
186+
MACOS_NOTARY_PRIVATE_KEY_BASE64="$MACOS_NOTARY_PRIVATE_KEY_BASE64" \
187+
python3 - <<'PY' > "$NOTARY_KEY_PATH"
188+
import base64
189+
import os
190+
import sys
191+
192+
sys.stdout.buffer.write(base64.b64decode(os.environ["MACOS_NOTARY_PRIVATE_KEY_BASE64"]))
193+
PY
194+
echo "MACOS_NOTARY_KEY_FILE=$NOTARY_KEY_PATH" >> "$GITHUB_ENV"
195+
196+
- name: Package macOS app
197+
shell: bash
198+
env:
199+
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
200+
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
201+
MACOS_NOTARY_ISSUER: ${{ secrets.MACOS_NOTARY_ISSUER }}
202+
PRODUCT_NAME: ${{ vars.PRODUCT_NAME || 'FutrixData' }}
203+
run: ./scripts/package-macos.sh
204+
205+
- name: Upload macOS artifacts
206+
uses: actions/upload-artifact@v4
207+
with:
208+
name: dist-macos-${{ env.VERSION }}
209+
path: dist/macos/*
210+
if-no-files-found: error
211+
212+
package-windows:
213+
needs: prepare
214+
runs-on: windows-latest
215+
env:
216+
SOURCE_REPOSITORY: ${{ needs.prepare.outputs.source_repository }}
217+
SOURCE_REF: ${{ needs.prepare.outputs.source_ref }}
218+
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
219+
SOURCE_CHECKOUT_REF: ${{ needs.prepare.outputs.source_checkout_ref }}
220+
VERSION: ${{ needs.prepare.outputs.version }}
221+
WINDOWS_WAILS_PLATFORM: ${{ vars.WINDOWS_WAILS_PLATFORM || 'windows/amd64' }}
222+
WINDOWS_TIMESTAMP_URL: ${{ vars.WINDOWS_TIMESTAMP_URL || 'http://timestamp.digicert.com' }}
223+
steps:
224+
- name: Checkout packaging repository
225+
uses: actions/checkout@v4
226+
227+
- name: Checkout source repository
228+
uses: actions/checkout@v4
229+
with:
230+
repository: ${{ env.SOURCE_REPOSITORY }}
231+
ref: ${{ env.SOURCE_CHECKOUT_REF }}
232+
token: ${{ secrets.SOURCE_REPO_READ_TOKEN }}
233+
path: source
234+
fetch-depth: 1
235+
236+
- name: Set up Go
237+
uses: actions/setup-go@v5
238+
with:
239+
go-version-file: source/go.mod
240+
241+
- name: Set up Node.js
242+
uses: actions/setup-node@v4
243+
with:
244+
node-version: 20
245+
cache: npm
246+
cache-dependency-path: source/frontend/package-lock.json
247+
248+
- name: Install Wails CLI
249+
shell: pwsh
250+
run: |
251+
go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
252+
"$((go env GOPATH) -replace '\\','/')/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
253+
254+
- name: Decode Windows signing certificate
255+
shell: pwsh
256+
env:
257+
WINDOWS_CERTIFICATE_PFX_BASE64: ${{ secrets.WINDOWS_CERTIFICATE_PFX_BASE64 }}
258+
run: |
259+
$certPath = Join-Path $env:RUNNER_TEMP "windows-signing-cert.pfx"
260+
[System.IO.File]::WriteAllBytes($certPath, [System.Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_PFX_BASE64))
261+
"WINDOWS_CERTIFICATE_PATH=$certPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
262+
263+
- name: Package Windows app
264+
shell: pwsh
265+
env:
266+
PRODUCT_NAME: ${{ vars.PRODUCT_NAME || 'FutrixData' }}
267+
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
268+
run: ./scripts/package-windows.ps1
269+
270+
- name: Upload Windows artifacts
271+
uses: actions/upload-artifact@v4
272+
with:
273+
name: dist-windows-${{ env.VERSION }}
274+
path: dist/windows/*
275+
if-no-files-found: error
276+
277+
release:
278+
needs:
279+
- prepare
280+
- package-macos
281+
- package-windows
282+
if: ${{ needs.prepare.outputs.release == 'true' }}
283+
runs-on: ubuntu-latest
284+
steps:
285+
- name: Download packaged artifacts
286+
uses: actions/download-artifact@v4
287+
with:
288+
pattern: dist-*
289+
path: release-assets
290+
merge-multiple: true
291+
292+
- name: Create release notes
293+
shell: bash
294+
run: |
295+
set -euo pipefail
296+
{
297+
echo "Source repository: ${{ needs.prepare.outputs.source_repository }}"
298+
echo "Source ref: ${{ needs.prepare.outputs.source_ref }}"
299+
echo "Source sha: ${{ needs.prepare.outputs.source_sha }}"
300+
echo "Triggered by: ${{ needs.prepare.outputs.triggered_by }}"
301+
if [ -n "${{ needs.prepare.outputs.source_run_url }}" ]; then
302+
echo "Source workflow: ${{ needs.prepare.outputs.source_run_url }}"
303+
fi
304+
} > RELEASE_NOTES.txt
305+
306+
- name: Publish GitHub release
307+
uses: softprops/action-gh-release@v2
308+
with:
309+
tag_name: ${{ needs.prepare.outputs.version }}
310+
name: FutrixData ${{ needs.prepare.outputs.version }}
311+
body_path: RELEASE_NOTES.txt
312+
files: release-assets/*

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
artifacts/
3+
.DS_Store

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# FutrixData Packaging Repository
2+
3+
This repository only owns CI packaging, code signing, notarization, and release publication for FutrixData.
4+
Application source stays in the source repository and is checked out at an exact commit during each packaging run.
5+
6+
## Event Contract
7+
8+
Primary trigger:
9+
10+
- `repository_dispatch`
11+
- Event type: `futrix-package-request`
12+
13+
Supported manual trigger:
14+
15+
- `workflow_dispatch`
16+
17+
Expected payload fields:
18+
19+
- `source_repository`
20+
- `source_ref`
21+
- `source_sha`
22+
- `version`
23+
- `source_run_url`
24+
- `triggered_by`
25+
- `event_name`
26+
- `release`
27+
28+
## Required Secrets
29+
30+
- `SOURCE_REPO_READ_TOKEN`
31+
- Token that can read the source repository.
32+
- `MACOS_CERTIFICATE_P12_BASE64`
33+
- Base64 encoded Developer ID Application `.p12`.
34+
- `MACOS_CERTIFICATE_PASSWORD`
35+
- Password for the `.p12`.
36+
- `MACOS_SIGNING_IDENTITY`
37+
- Example: `Developer ID Application: Your Company (TEAMID)`.
38+
- `MACOS_NOTARY_KEY_ID`
39+
- App Store Connect API key ID.
40+
- `MACOS_NOTARY_ISSUER`
41+
- App Store Connect issuer ID.
42+
- `MACOS_NOTARY_PRIVATE_KEY_BASE64`
43+
- Base64 encoded `.p8` private key contents.
44+
- `WINDOWS_CERTIFICATE_PFX_BASE64`
45+
- Base64 encoded code-signing `.pfx`.
46+
- `WINDOWS_CERTIFICATE_PASSWORD`
47+
- Password for the `.pfx`.
48+
49+
## Optional Repository Variables
50+
51+
- `PRODUCT_NAME`
52+
- Default: `FutrixData`
53+
- `MACOS_WAILS_PLATFORM`
54+
- Default: `darwin/universal`
55+
- `WINDOWS_WAILS_PLATFORM`
56+
- Default: `windows/amd64`
57+
- `MACOS_BUNDLE_ID`
58+
- Example: `com.futrixdata.app`
59+
- `WINDOWS_TIMESTAMP_URL`
60+
- Default: `http://timestamp.digicert.com`
61+
62+
## Release Behavior
63+
64+
- Branch build requests upload workflow artifacts.
65+
- Tag build requests also publish a GitHub Release in this repository.

0 commit comments

Comments
 (0)