@@ -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)"
0 commit comments