-
Notifications
You must be signed in to change notification settings - Fork 73
268 lines (248 loc) · 11.4 KB
/
Copy pathdashboard-release.yml
File metadata and controls
268 lines (248 loc) · 11.4 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
name: Dashboard Release
on:
push:
tags:
- "dashboard-v*"
workflow_dispatch:
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# Create the draft release ONCE, up front, and hand its id to every build leg.
# The previous design let each matrix leg create-or-find the release by tagName;
# when two legs started simultaneously they both saw "no release yet" and each
# created its own draft, splitting the platform assets (and the per-arch
# latest.json) across two drafts for the same tag. Creating it here first means
# every leg uploads into the same release, so tauri-action merges one complete
# latest.json across all architectures.
create-release:
name: Create draft release
runs-on: ubuntu-latest
outputs:
release_id: ${{ steps.create.outputs.result }}
steps:
- id: create
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const ref = process.env.GITHUB_REF_NAME;
const { owner, repo } = context.repo;
// Reuse an existing release for this tag (re-runs), else create a draft.
try {
const ex = await github.rest.repos.getReleaseByTag({ owner, repo, tag: ref });
core.info(`Reusing existing release ${ex.data.id} for ${ref}`);
return String(ex.data.id);
} catch (e) {
if (e.status !== 404) throw e;
}
const created = await github.rest.repos.createRelease({
owner, repo,
tag_name: ref,
name: `Dashboard ${ref}`,
draft: true,
prerelease: false,
});
core.info(`Created draft release ${created.data.id} for ${ref}`);
return String(created.data.id);
build:
name: Build Dashboard (${{ matrix.settings.label }})
needs: create-release
# Harden cargo against the transient crates.io drops we hit in the wild
# ("download of … failed / curl … Connection reset by peer" mid-build, which
# killed a single matrix leg ~23s in despite the code being fine). Retry hard
# and disable HTTP/2 multiplexing — the documented workaround for spurious
# connection resets against the cargo download CDN on hosted runners.
env:
CARGO_NET_RETRY: "10"
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
CARGO_HTTP_MULTIPLEXING: "false"
strategy:
# Keep building every platform leg even if one fails: a release wants to
# surface ALL broken platforms in one run, not cancel the survivors. The
# release SCRIPT fail-fasts instead (aborts the moment the run concludes
# failure), so a single failed leg no longer hides behind the slow ones.
fail-fast: false
matrix:
settings:
- host: macos-latest
target: aarch64-apple-darwin
label: macOS ARM64
arch: arm64
- host: macos-latest
target: x86_64-apple-darwin
label: macOS Intel
arch: x64
- host: ubuntu-22.04
target: x86_64-unknown-linux-gnu
label: Linux x64
arch: x64
- host: ubuntu-22.04-arm
target: aarch64-unknown-linux-gnu
label: Linux ARM64
arch: arm64
- host: windows-latest
target: x86_64-pc-windows-msvc
label: Windows x64
arch: x64
- host: windows-11-arm
target: aarch64-pc-windows-msvc
label: Windows ARM64
arch: arm64
# WiX has no ARM64 support — NSIS is the only Windows ARM installer
# Tauri can produce. Without this override the default bundle set
# ("all") fails the leg at the .msi step.
bundles: nsis
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.settings.target }}
- name: Install Linux dependencies
if: contains(matrix.settings.host, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install dependencies
run: bun install
# macOS: import Apple certificate for code signing
- name: Import Apple certificate
if: contains(matrix.settings.host, 'macos')
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Extract signing identity — MUST be "Developer ID Application", not "Apple Development"
IDENTITY=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [ -z "$IDENTITY" ]; then
echo "::error::No 'Developer ID Application' certificate found in keychain. Check APPLE_CERTIFICATE secret."
security find-identity -v -p codesigning $KEYCHAIN_PATH
exit 1
fi
echo "Found signing identity: $IDENTITY"
echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> $GITHUB_ENV
# macOS: write App Store Connect API key for notarization
- name: Write Apple API key
if: contains(matrix.settings.host, 'macos')
env:
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_CONTENT }}
run: |
mkdir -p $RUNNER_TEMP
echo "$APPLE_API_KEY_CONTENT" > $RUNNER_TEMP/apple-api-key.p8
- name: Build and upload artifacts
uses: tauri-apps/tauri-action@v0
timeout-minutes: 60
with:
projectPath: packages/dashboard
tauriScript: bunx tauri
args: --target ${{ matrix.settings.target }}${{ matrix.settings.bundles && format(' --bundles {0}', matrix.settings.bundles) || '' }}
updaterJsonPreferNsis: true
# Upload into the single pre-created draft (see create-release). Using
# releaseId (not tagName) guarantees every leg targets the same release,
# so there is exactly one release and one merged latest.json per tag.
releaseId: ${{ needs.create-release.outputs.release_id }}
assetNamePattern: magic-context-dashboard-[platform]-${{ matrix.settings.arch }}[ext]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# macOS code signing + notarization
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
# Deploy latest.json to gh-pages for the updater endpoint.
#
# Tauri's updater endpoint lives at https://cortexkit.github.io/magic-context/latest.json.
# That URL must keep serving the latest signed manifest after every dashboard
# release. We deploy ONLY latest.json by staging it in an isolated directory
# and pointing publish_dir at that directory — previous configs used
# `publish_dir: .` together with the (non-existent) `include_files` input,
# which silently published the entire repo checkout to gh-pages on every
# release.
deploy-updater:
name: Deploy updater manifest
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Download latest.json from release (with retry)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.ref_name }}"
mkdir -p _updater_publish
OUT=_updater_publish/latest.json
# Retry up to 20×15s (5 minutes) — first-time uploads of large
# platform binaries can take longer than a single fixed sleep,
# and tauri-action publishes assets in parallel.
for attempt in $(seq 1 20); do
if gh release download "$TAG" --pattern "latest.json" --output "$OUT" --clobber 2>/dev/null; then
echo "✓ downloaded latest.json on attempt $attempt"
cat "$OUT"
exit 0
fi
echo "attempt $attempt: latest.json not yet available, sleeping 15s…"
sleep 15
done
echo "::error::latest.json never became available on release $TAG"
exit 1
# Pin every asset URL to THIS tag's release. tauri-action writes
# /releases/latest/download/ URLs, but in this repo GitHub's "latest
# release" pointer belongs to the plugin train (v*), which carries no
# dashboard assets — so /latest/ URLs 404 and Desktop auto-update breaks.
# Rewrite to /releases/download/<TAG>/, which is immune to the pointer.
- name: Pin updater URLs to this release tag
run: |
TAG="${{ github.ref_name }}"
FILE=_updater_publish/latest.json
python3 - "$TAG" "$FILE" <<'PY'
import json, sys
tag, path = sys.argv[1], sys.argv[2]
old = "/releases/latest/download/"
new = f"/releases/download/{tag}/"
d = json.load(open(path))
n = 0
for info in d.get("platforms", {}).values():
u = info.get("url", "")
if old in u:
info["url"] = u.replace(old, new); n += 1
json.dump(d, open(path, "w"), indent=2)
print(f"pinned {n} url(s) to {new}")
# Fail closed: a manifest still carrying /latest/ URLs would ship broken.
assert not any(old in i.get("url", "") for i in d.get("platforms", {}).values()), \
"latest.json still has /releases/latest/download/ URLs after rewrite"
PY
cat "$FILE"
- name: Deploy to gh-pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Publish ONLY the staging dir, which contains exactly one file:
# latest.json. Anything else in the workspace stays out of gh-pages.
publish_dir: ./_updater_publish
publish_branch: gh-pages
# keep_files: true preserves any other files that already exist
# on gh-pages so we don't wipe the branch on each release.
keep_files: true
# force_orphan would discard gh-pages history; we keep history so
# the branch acts as a normal append-only artifact log.
force_orphan: false