Skip to content

Commit d1709c1

Browse files
committed
feat(ci): manual updater signing + latest.json generation
tauri-action's internal signing doesn't reliably produce .sig files. Added explicit steps: 1. Sign updater archives: finds .app.tar.gz / .nsis.zip / .AppImage.tar.gz in the build output, runs tauri signer sign, uploads .sig files to the release. 2. updater-manifest job: runs after all platforms complete, reads .sig content from the release assets, generates latest.json with per-platform entries, uploads it.
1 parent 43f3351 commit d1709c1

5 files changed

Lines changed: 164 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,45 @@ jobs:
163163
includeUpdaterJson: true
164164
args: ${{ matrix.args }}
165165

166+
# Sign updater archives and upload .sig files manually.
167+
# tauri-action creates .app.tar.gz / .nsis.zip / .AppImage.tar.gz
168+
# but its internal signing logic doesn't reliably produce .sig
169+
# files, so we run `tauri signer sign` ourselves.
170+
- name: Sign updater archives
171+
if: steps.tag.outputs.name != ''
172+
shell: bash
173+
env:
174+
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
175+
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
176+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
177+
run: |
178+
RELEASE_ID="${{ steps.tauri.outputs.releaseId }}"
179+
REPO="${{ github.repository }}"
180+
if [ -z "$RELEASE_ID" ] || [ -z "$TAURI_SIGNING_PRIVATE_KEY" ]; then
181+
echo "Skipping signing (no release ID or no signing key)"
182+
exit 0
183+
fi
184+
185+
# Find updater archives in the build output
186+
ARCHIVES=$(find src-tauri/target -type f \( \
187+
-name "*.app.tar.gz" -o \
188+
-name "*.nsis.zip" -o \
189+
-name "*.AppImage.tar.gz" \
190+
\) ! -name "*.sig" 2>/dev/null)
191+
192+
for ARCHIVE in $ARCHIVES; do
193+
echo "Signing: $ARCHIVE"
194+
npx tauri signer sign "$ARCHIVE"
195+
SIG="${ARCHIVE}.sig"
196+
if [ -f "$SIG" ]; then
197+
echo "Uploading: $(basename "$SIG")"
198+
gh release upload "${{ steps.tag.outputs.name }}" "$SIG" \
199+
--repo "$REPO" --clobber
200+
else
201+
echo "::warning::Signature file not created for $ARCHIVE"
202+
fi
203+
done
204+
166205
# Rename release assets to the project's convention:
167206
# SwitchHosts-{version}-{platform}-{arch}.{ext}
168207
# Uses the GitHub API PATCH endpoint to rename in place (no
@@ -225,3 +264,123 @@ jobs:
225264
gh api -X PATCH "repos/$REPO/releases/assets/$AID" -f name="$NEW" --silent
226265
fi
227266
done
267+
268+
# ── Generate latest.json after all platform builds complete ───────
269+
updater-manifest:
270+
needs: release
271+
if: ${{ needs.release.result == 'success' }}
272+
runs-on: ubuntu-latest
273+
steps:
274+
- name: Checkout
275+
uses: actions/checkout@v4
276+
277+
- name: Resolve tag
278+
id: tag
279+
shell: bash
280+
run: |
281+
if [ "${{ github.ref_type }}" = "tag" ]; then
282+
echo "name=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
283+
elif [ -n "${{ github.event.inputs.tag }}" ]; then
284+
echo "name=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
285+
else
286+
echo "name=" >> "$GITHUB_OUTPUT"
287+
fi
288+
289+
- name: Generate latest.json
290+
if: steps.tag.outputs.name != ''
291+
shell: bash
292+
env:
293+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
294+
run: |
295+
TAG="${{ steps.tag.outputs.name }}"
296+
VERSION=$(node -p "require('./package.json').version")
297+
REPO="${{ github.repository }}"
298+
299+
# Get release ID from tag (release is a draft, so use list endpoint)
300+
RELEASE_ID=$(gh api "repos/$REPO/releases" --paginate \
301+
--jq ".[] | select(.tag_name == \"$TAG\") | .id" | head -1)
302+
if [ -z "$RELEASE_ID" ]; then
303+
echo "::warning::No release found for tag $TAG"
304+
exit 0
305+
fi
306+
307+
DOWNLOAD_BASE="https://github.com/$REPO/releases/download/$TAG"
308+
309+
# Helper: read .sig file content from release assets
310+
get_sig() {
311+
local sig_name="$1"
312+
gh api "repos/$REPO/releases/$RELEASE_ID/assets" --paginate \
313+
--jq ".[] | select(.name == \"$sig_name\") | .url" | head -1 | \
314+
xargs -I{} gh api -H "Accept: application/octet-stream" {} 2>/dev/null || echo ""
315+
}
316+
317+
# Collect per-platform entries
318+
PLATFORMS="{}"
319+
320+
# macOS universal
321+
SIG=$(get_sig "SwitchHosts_universal.app.tar.gz.sig")
322+
if [ -n "$SIG" ]; then
323+
PLATFORMS=$(echo "$PLATFORMS" | jq \
324+
--arg url "$DOWNLOAD_BASE/SwitchHosts_universal.app.tar.gz" \
325+
--arg sig "$SIG" \
326+
'. + {"darwin-universal": {"url": $url, "signature": $sig}}')
327+
fi
328+
329+
# macOS aarch64
330+
SIG=$(get_sig "SwitchHosts_aarch64.app.tar.gz.sig")
331+
if [ -n "$SIG" ]; then
332+
PLATFORMS=$(echo "$PLATFORMS" | jq \
333+
--arg url "$DOWNLOAD_BASE/SwitchHosts_aarch64.app.tar.gz" \
334+
--arg sig "$SIG" \
335+
'. + {"darwin-aarch64": {"url": $url, "signature": $sig}}')
336+
fi
337+
338+
# macOS x64
339+
SIG=$(get_sig "SwitchHosts_x64.app.tar.gz.sig")
340+
if [ -n "$SIG" ]; then
341+
PLATFORMS=$(echo "$PLATFORMS" | jq \
342+
--arg url "$DOWNLOAD_BASE/SwitchHosts_x64.app.tar.gz" \
343+
--arg sig "$SIG" \
344+
'. + {"darwin-x86_64": {"url": $url, "signature": $sig}}')
345+
fi
346+
347+
# Windows x64
348+
SIG=$(get_sig "SwitchHosts_${VERSION}_x64-setup.nsis.zip.sig")
349+
if [ -n "$SIG" ]; then
350+
PLATFORMS=$(echo "$PLATFORMS" | jq \
351+
--arg url "$DOWNLOAD_BASE/SwitchHosts_${VERSION}_x64-setup.nsis.zip" \
352+
--arg sig "$SIG" \
353+
'. + {"windows-x86_64": {"url": $url, "signature": $sig}}')
354+
fi
355+
356+
# Linux x86_64
357+
SIG=$(get_sig "SwitchHosts_${VERSION}_amd64.AppImage.tar.gz.sig")
358+
if [ -n "$SIG" ]; then
359+
PLATFORMS=$(echo "$PLATFORMS" | jq \
360+
--arg url "$DOWNLOAD_BASE/SwitchHosts_${VERSION}_amd64.AppImage.tar.gz" \
361+
--arg sig "$SIG" \
362+
'. + {"linux-x86_64": {"url": $url, "signature": $sig}}')
363+
fi
364+
365+
# Linux aarch64
366+
SIG=$(get_sig "SwitchHosts_${VERSION}_aarch64.AppImage.tar.gz.sig")
367+
if [ -n "$SIG" ]; then
368+
PLATFORMS=$(echo "$PLATFORMS" | jq \
369+
--arg url "$DOWNLOAD_BASE/SwitchHosts_${VERSION}_aarch64.AppImage.tar.gz" \
370+
--arg sig "$SIG" \
371+
'. + {"linux-aarch64": {"url": $url, "signature": $sig}}')
372+
fi
373+
374+
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
375+
LATEST=$(jq -n \
376+
--arg version "$VERSION" \
377+
--arg pub_date "$NOW" \
378+
--argjson platforms "$PLATFORMS" \
379+
'{version: $version, pub_date: $pub_date, platforms: $platforms}')
380+
381+
echo "$LATEST" | jq .
382+
echo "$LATEST" > latest.json
383+
384+
# Upload (clobber if already exists from a previous run)
385+
gh release upload "$TAG" latest.json --repo "$REPO" --clobber
386+
echo "latest.json uploaded with $(echo "$PLATFORMS" | jq 'keys | length') platform(s)"

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"private": true,
3-
"version": "5.0.0-beta.12",
3+
"version": "5.0.0-beta.14",
44
"scripts": {
55
"start": "cross-env NODE_ENV=development electron ./build/main.js",
66
"pretest": "rimraf ./test/tmp",

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "SwitchHosts",
4-
"version": "5.0.0-beta.12",
4+
"version": "5.0.0-beta.14",
55
"identifier": "net.oldj.switchhosts",
66
"build": {
77
"beforeDevCommand": "npm run dev:renderer",

src/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"5.0.0-beta.12"
1+
"5.0.0-beta.14"

0 commit comments

Comments
 (0)