Skip to content

Creating OTA Bundles

This guide walks through building, signing, and publishing frontend bundles for hotswap delivery.


Terminal window
pnpm build
# or: npm run build, yarn build, etc.

This produces your dist/ or build/ directory — the same output that Tauri embeds via frontendDist.


tar.gz (default, no extra features needed)

Section titled “tar.gz (default, no extra features needed)”
Terminal window
tar -czf frontend.tar.gz -C dist .
Terminal window
cd dist && zip -r ../frontend.zip . && cd ..

⚠️ The archive root should contain index.html directly — not a subdirectory. Use -C dist . (not -C . dist).

Note: tar -C dist . produces entries with a ./ prefix (e.g. ./index.html). This is fine — the plugin strips leading ./ components during extraction. What is rejected: absolute paths and .. path traversal.


Use the same signing key you generated for the plugin (see Security > Signing Guide).

Terminal window
# Using the Tauri CLI
pnpm tauri signer sign frontend.tar.gz -k ~/.tauri/hotswap.key
# Or with minisign directly
minisign -Sm frontend.tar.gz -s ~/.tauri/hotswap.key

This creates frontend.tar.gz.sig. Read its contents — this is the signature field in your manifest.

Terminal window
# Read the signature
cat frontend.tar.gz.sig

Upload both files:

Terminal window
# AWS S3
aws s3 cp frontend.tar.gz s3://my-bucket/updates/v0.1.0-ota.3/
aws s3 cp frontend.tar.gz.sig s3://my-bucket/updates/v0.1.0-ota.3/
# Or any HTTPS-accessible host

Make your update endpoint return the manifest pointing to the uploaded bundle:

{
"version": "0.1.0-ota.3",
"sequence": 43,
"min_binary_version": "0.1.0",
"url": "https://cdn.example.com/updates/v0.1.0-ota.3/frontend.tar.gz",
"signature": "untrusted comment: signature from minisign secret key\nRWQ...<base64>...",
"notes": "Fixed dark mode colors",
"bundle_size": 2097152
}

See the full Server Contract for details.


name: OTA Release
on:
workflow_dispatch:
inputs:
version:
description: 'OTA version (e.g. 0.1.0-ota.3)'
required: true
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- name: Create bundle
run: tar -czf frontend.tar.gz -C dist .
- name: Sign bundle
run: |
echo "${{ secrets.HOTSWAP_PRIVATE_KEY }}" > /tmp/hotswap.key
npx @tauri-apps/cli signer sign frontend.tar.gz -k /tmp/hotswap.key
rm /tmp/hotswap.key
- name: Upload to S3
run: |
aws s3 cp frontend.tar.gz s3://my-bucket/updates/v${{ inputs.version }}/
aws s3 cp frontend.tar.gz.sig s3://my-bucket/updates/v${{ inputs.version }}/
- name: Publish manifest
run: |
SIGNATURE=$(cat frontend.tar.gz.sig)
SIZE=$(stat -f%z frontend.tar.gz 2>/dev/null || stat -c%s frontend.tar.gz)
curl -X POST https://api.example.com/updates \
-H "Authorization: Bearer ${{ secrets.API_TOKEN }}" \
-H "Content-Type: application/json" \
-d "{
\"version\": \"${{ inputs.version }}\",
\"sequence\": ${{ github.run_number }},
\"min_binary_version\": \"0.1.0\",
\"url\": \"https://cdn.example.com/updates/v${{ inputs.version }}/frontend.tar.gz\",
\"signature\": $(echo "$SIGNATURE" | jq -Rs .),
\"bundle_size\": $SIZE
}"

💡 Using ${{ github.run_number }} as the sequence is a simple way to get a monotonically increasing counter.


EventVersionSequence
Binary release 0.1.0
First OTA hotfix0.1.0-ota.11
Second OTA hotfix0.1.0-ota.22
Binary release 0.2.0
First OTA for 0.2.00.2.0-ota.13

Sequences are global and always increase. The version field is for display only.