# A release: push a tag `v1.8.0` (the version in tauri.conf.json, package.json # and Cargo.toml must already say 1.8.0) and this builds the Windows installer, # signs its updater artifact with the project key, and publishes a GitHub # release carrying the installer, its .sig and latest.json — the manifest the # app's Stable channel reads (src-tauri/src/updater.rs). # # Secrets this needs on the repository: # TAURI_SIGNING_PRIVATE_KEY the private key (content of soulstream.key) # TAURI_SIGNING_PRIVATE_KEY_PASSWORD its password — empty (or unset) for a key without one # Optional, to publish somewhere other than this repository (this one is # private, and a private release cannot be read by the updater without a # token — a separate PUBLIC releases repo is the usual answer): # RELEASES_TOKEN a token with contents:write on that repository # vars.RELEASES_OWNER / vars.RELEASES_REPO where to publish name: release on: push: tags: ["v*"] workflow_dispatch: permissions: contents: write jobs: windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 with: workspaces: "./src-tauri -> ../target" - run: npm ci - name: Read the version id: version shell: bash run: | echo "version=$(node -p "require('./src-tauri/tauri.conf.json').version")" >> "$GITHUB_OUTPUT" - name: Build, sign and publish uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.RELEASES_TOKEN || secrets.GITHUB_TOKEN }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} with: owner: ${{ vars.RELEASES_OWNER || github.repository_owner }} repo: ${{ vars.RELEASES_REPO || github.event.repository.name }} tagName: v__VERSION__ releaseName: "SoulStream __VERSION__" releaseBody: "See the commits since the last release." releaseDraft: false prerelease: ${{ contains(github.ref_name, '-') }} includeUpdaterJson: true args: --bundles nsis # A private repository's assets are reachable only through the API, so the # manifest's download URLs become API asset URLs — the app sends its token # and `Accept: application/octet-stream` for those (updater.rs). Public # repositories serve the same URLs to anyone, so nothing is lost. - name: Point the manifest at the API assets shell: bash env: GH_TOKEN: ${{ secrets.RELEASES_TOKEN || secrets.GITHUB_TOKEN }} TARGET: ${{ vars.RELEASES_OWNER || github.repository_owner }}/${{ vars.RELEASES_REPO || github.event.repository.name }} TAG: v${{ steps.version.outputs.version }} run: | gh release download "$TAG" --repo "$TARGET" --pattern latest.json --output latest.json --clobber gh api "repos/$TARGET/releases/tags/$TAG" > release.json node -e ' const fs = require("fs"); const m = JSON.parse(fs.readFileSync("latest.json", "utf8")); const rel = JSON.parse(fs.readFileSync("release.json", "utf8")); for (const p of Object.values(m.platforms)) { const name = decodeURIComponent(p.url.split("/").pop()); const a = rel.assets.find((x) => x.name === name); if (!a) { console.error("no asset named", name); process.exit(1); } p.url = a.url; } fs.writeFileSync("latest.json", JSON.stringify(m, null, 2) + "\n"); console.log(JSON.stringify(m, null, 2)); ' gh release upload "$TAG" latest.json --repo "$TARGET" --clobber # The file testers run: our own setup window (setup/). It carries no # installer — it downloads the latest release with the tester's key and # verifies the signature — so it is built the same for every release. - name: Build SoulStream Setup shell: bash env: VERSION: ${{ steps.version.outputs.version }} run: | npx vite build --config vite.setup.config.js cargo build --release -p soulstream-setup cp target/release/SoulStream-Setup.exe "SoulStream-Setup-$VERSION.exe" ls -la "SoulStream-Setup-$VERSION.exe" # Six files on the page and nothing saying which one a tester runs: the # NSIS installer is the biggest and reads like the download, and running # it puts the app on with no key gate and no signature check. The notes # go on last, once the file they point at is actually up. - name: Publish SoulStream Setup shell: bash env: GH_TOKEN: ${{ secrets.RELEASES_TOKEN || secrets.GITHUB_TOKEN }} TARGET: ${{ vars.RELEASES_OWNER || github.repository_owner }}/${{ vars.RELEASES_REPO || github.event.repository.name }} TAG: v${{ steps.version.outputs.version }} VERSION: ${{ steps.version.outputs.version }} run: | gh release upload "$TAG" "SoulStream-Setup-$VERSION.exe" --repo "$TARGET" --clobber { echo "### Download \`SoulStream-Setup-$VERSION.exe\`" echo echo "It asks for your alpha key, fetches this release with it, checks the signature and installs — no admin prompt." echo echo "The other files are the ones Setup uses:" echo echo "- \`SoulStream_${VERSION}_x64-setup.exe\` — the bare installer Setup downloads. Running it yourself skips the key and the signature check." echo "- \`SoulStream_${VERSION}_x64-setup.exe.sig\` — its signature." echo "- \`latest.json\` — the manifest the in-app updater reads." echo echo "See the commits since the last release." } > notes.md gh release edit "$TAG" --repo "$TARGET" --notes-file notes.md