Release #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| schedule: | |
| - cron: "0 3 * * 1" # every Monday — check for new core binaries | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: 'Version bump type' | |
| type: choice | |
| options: [patch, minor, major] | |
| default: patch | |
| skip_bump: | |
| description: 'Skip version bump (use current version)' | |
| type: boolean | |
| default: false | |
| force: | |
| description: 'Force a release even when change detection finds nothing' | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| # Never run two releases at once (they both bump module.prop). | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| # Third-party actions are pinned to commit SHAs (with the human-readable tag in a | |
| # trailing comment) so a moved tag cannot silently change what runs with the | |
| # `contents: write` token. Bump the SHA + comment together when upgrading. | |
| jobs: | |
| # ── Prepare the release: decide whether to cut one, bump the version, regenerate | |
| # update.json/changelog, commit+tag, and create the (empty) GitHub release. The | |
| # two build jobs below then attach their artifacts to it in parallel — neither | |
| # waits on the other, and the heavy Android/desktop toolchains stay out of here. ── | |
| prepare: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.bump.outputs.version }} | |
| skip: ${{ steps.changes.outputs.skip }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| # Push the version-bump commit under a PAT so it bypasses the `main` | |
| # ruleset (the default GITHUB_TOKEN / github-actions app is not in the | |
| # bypass list and cannot be added on a user-owned repo). Set the | |
| # RELEASE_TOKEN secret to a PAT of a user holding the bypass role; it | |
| # falls back to GITHUB_TOKEN, with which the push step would still be | |
| # rejected by the ruleset. | |
| token: ${{ secrets.RELEASE_TOKEN || github.token }} | |
| persist-credentials: true | |
| - name: Check for changes since last release | |
| id: changes | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| skip=true | |
| # 1. New code commits (docs/ci/chore-only changes do not trigger release). | |
| # These only gate a *manual* run: on the weekly schedule we release | |
| # solely for upstream core bumps (below), never just because code | |
| # landed — cut a code release on demand via workflow_dispatch. | |
| last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") | |
| has_code_commits=false | |
| if [ -z "$last_tag" ]; then | |
| has_code_commits=true | |
| else | |
| code_commits=$(git log "$last_tag"..HEAD --oneline \ | |
| -- \ | |
| 'module/*.sh' \ | |
| 'module/bin/kasumi-proxyctl' \ | |
| 'module/bin/utils.sh' \ | |
| 'module/webroot/cgi-bin/' \ | |
| 'frontend/src/' \ | |
| 'crates/' \ | |
| 'src-tauri/' \ | |
| 'scripts/fetch-binaries.sh' \ | |
| 'scripts/binaries.json' \ | |
| 'scripts/binary-versions.sh' \ | |
| 'scripts/package-release.sh' \ | |
| 2>/dev/null | wc -l) | |
| [ "$code_commits" -gt 0 ] && has_code_commits=true | |
| fi | |
| if [ "$has_code_commits" = "true" ] && [ "${{ github.event_name }}" != "schedule" ]; then | |
| skip=false | |
| fi | |
| # 2. Upstream binary updates | |
| pinned_xray=$(grep -m1 'XRAY_VERSION=.*:-' scripts/binary-versions.sh | sed 's/.*:-\([^}"]*\)}.*/\1/') | |
| pinned_t2s=$(grep -m1 'TUN2SOCKS_VERSION=.*:-' scripts/binary-versions.sh | sed 's/.*:-\([^}"]*\)}.*/\1/') | |
| pinned_sb=$(grep -m1 'SINGBOX_VERSION=.*:-' scripts/binary-versions.sh | sed 's/.*:-\([^}"]*\)}.*/\1/') | |
| latest_xray=$(gh release view --repo XTLS/Xray-core --json tagName -q .tagName) | |
| latest_t2s=$(gh release view --repo xjasonlyu/tun2socks --json tagName -q .tagName) | |
| latest_sb=$(gh release view --repo SagerNet/sing-box --json tagName -q .tagName) | |
| bins_changed=false | |
| [ "$pinned_xray" != "$latest_xray" ] && { skip=false; bins_changed=true; } | |
| [ "$pinned_t2s" != "$latest_t2s" ] && { skip=false; bins_changed=true; } | |
| [ "$pinned_sb" != "$latest_sb" ] && { skip=false; bins_changed=true; } | |
| if [ "$bins_changed" = "true" ]; then | |
| echo "bump=minor" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "bump=${{ inputs.bump || 'patch' }}" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Manual override: a workflow_dispatch with force=true releases even when | |
| # nothing changed (empty on schedule/non-dispatch runs, so it's a no-op there). | |
| if [ "${{ inputs.force }}" = "true" ]; then | |
| skip=false | |
| fi | |
| { | |
| echo "xray_old=$pinned_xray" | |
| echo "t2s_old=$pinned_t2s" | |
| echo "sb_old=$pinned_sb" | |
| echo "skip=$skip" | |
| echo "bins_changed=$bins_changed" | |
| echo "xray_version=$latest_xray" | |
| echo "t2s_version=$latest_t2s" | |
| echo "sb_version=$latest_sb" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Sync core pins + nix hashes | |
| # When upstream cores advanced, write the new versions into the pinned | |
| # source of truth (binary-versions.sh) and regenerate the nix FOD hashes | |
| # (binary-hashes.json), so the next build — and the committed flake — match. | |
| # Both build jobs check out the tag, so the synced pins are authoritative | |
| # for the Android and desktop core fetches; no per-build version override. | |
| if: steps.changes.outputs.skip != 'true' && steps.changes.outputs.bins_changed == 'true' | |
| env: | |
| XRAY_NEW: ${{ steps.changes.outputs.xray_version }} | |
| T2S_NEW: ${{ steps.changes.outputs.t2s_version }} | |
| SB_NEW: ${{ steps.changes.outputs.sb_version }} | |
| run: | | |
| sed -i -E \ | |
| -e "s|(XRAY_VERSION=\"\\\$\{XRAY_VERSION:-)[^}]*|\1${XRAY_NEW}|" \ | |
| -e "s|(TUN2SOCKS_VERSION=\"\\\$\{TUN2SOCKS_VERSION:-)[^}]*|\1${T2S_NEW}|" \ | |
| -e "s|(SINGBOX_VERSION=\"\\\$\{SINGBOX_VERSION:-)[^}]*|\1${SB_NEW}|" \ | |
| scripts/binary-versions.sh | |
| scripts/update-binary-hashes.sh | |
| - name: Setup Bun | |
| if: steps.changes.outputs.skip != 'true' | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: latest | |
| - name: Cache Bun dependencies | |
| if: steps.changes.outputs.skip != 'true' | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }} | |
| restore-keys: bun-${{ runner.os }}- | |
| - name: Install UI dependencies | |
| if: steps.changes.outputs.skip != 'true' | |
| # bun workspace rooted at the repo — install from root against the single bun.lock. | |
| run: bun install --frozen-lockfile | |
| - name: Lint & test | |
| # Gate the release on a green frontend before any version is bumped or tagged. | |
| if: steps.changes.outputs.skip != 'true' | |
| run: | | |
| bunx biome check | |
| bun run frontend/scripts/check-i18n.ts | |
| ( cd frontend && bunx vitest run ) | |
| # ── Block the auto-bump when a new core's config schema drifted from our | |
| # generators. "Sync core pins" above already wrote the new versions into | |
| # binary-versions.sh, so this stages those exact cores and runs the | |
| # config-validation harness against them. A rejected config fails the job | |
| # before any version is bumped or tagged — so we never ship a release whose | |
| # pinned core rejects the configs we generate. Only runs when cores moved. ── | |
| - name: Setup Rust (core-compat gate) | |
| if: steps.changes.outputs.skip != 'true' && steps.changes.outputs.bins_changed == 'true' | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| toolchain: stable | |
| targets: x86_64-unknown-linux-gnu | |
| - name: Verify the new cores accept our generated configs | |
| if: steps.changes.outputs.skip != 'true' && steps.changes.outputs.bins_changed == 'true' | |
| run: scripts/check-binary-compat.sh | |
| - name: Bump version | |
| if: steps.changes.outputs.skip != 'true' | |
| id: bump | |
| run: | | |
| if [ "${{ inputs.skip_bump }}" = "true" ]; then | |
| version=$(grep -m1 '^version=' module/module.prop | cut -d= -f2) | |
| else | |
| version=$(scripts/bump-version.sh ${{ inputs.bump || 'patch' }}) | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| - name: Generate update.json | |
| if: steps.changes.outputs.skip != 'true' | |
| run: scripts/gen-update-json.sh | |
| - name: Generate changelog | |
| if: steps.changes.outputs.skip != 'true' | |
| env: | |
| VERSION: ${{ steps.bump.outputs.version }} | |
| XRAY_OLD: ${{ steps.changes.outputs.xray_old }} | |
| XRAY_NEW: ${{ steps.changes.outputs.xray_version }} | |
| T2S_OLD: ${{ steps.changes.outputs.t2s_old }} | |
| T2S_NEW: ${{ steps.changes.outputs.t2s_version }} | |
| SB_OLD: ${{ steps.changes.outputs.sb_old }} | |
| SB_NEW: ${{ steps.changes.outputs.sb_version }} | |
| run: | | |
| scripts/gen-changelog.sh \ | |
| "$VERSION" "$XRAY_OLD" "$XRAY_NEW" "$T2S_OLD" "$T2S_NEW" "$SB_OLD" "$SB_NEW" | |
| - name: Commit version bump | |
| if: steps.changes.outputs.skip != 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add module/module.prop update.json CHANGELOG.md \ | |
| scripts/binary-versions.sh scripts/binary-hashes.json | |
| # The version/changelog may already be committed (a hand-prepared | |
| # skip_bump release, or a re-run) — don't fail when there is nothing new. | |
| if git diff --cached --quiet; then | |
| echo "nothing to commit — version, update.json and changelog already current" | |
| else | |
| git commit -m "chore(release): ${{ steps.bump.outputs.version }}" | |
| git push | |
| fi | |
| - name: Create GitHub Release | |
| # Created empty here; the module and desktop jobs attach their artifacts to | |
| # this tag in parallel. target_commitish points at the just-pushed bump | |
| # commit, so the tag the build jobs check out carries the bumped version. | |
| if: steps.changes.outputs.skip != 'true' | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | |
| with: | |
| tag_name: ${{ steps.bump.outputs.version }} | |
| target_commitish: ${{ github.ref_name }} | |
| name: Kasumi Proxy ${{ steps.bump.outputs.version }} | |
| generate_release_notes: true | |
| # ── Android module zip → uploaded to the release `prepare` created. Runs in | |
| # parallel with `desktop`; the committed binary-versions.sh on the tag pins the | |
| # core fetch. ── | |
| module: | |
| needs: prepare | |
| if: needs.prepare.outputs.skip != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout the released tag | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.version }} | |
| fetch-depth: 0 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 | |
| with: | |
| extra-conf: | | |
| accept-flake-config = true | |
| - name: Cachix | |
| uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: kasumi-proxy | |
| authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| - name: Build release zip | |
| run: | | |
| nix develop .#android --command bash -euo pipefail -c ' | |
| bun install --frozen-lockfile | |
| scripts/package-release.sh | |
| ' | |
| - name: Upload to the release | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| # Glob, not a hardcoded name: package-release.sh owns the zip's | |
| # filename format (the module job's checkout produces exactly one zip | |
| # here), so the name lives in one place — same as nightly.yml. | |
| files: build/*.zip | |
| # ── Desktop installers (Linux deb/rpm/AppImage, Windows NSIS/MSI + portable zip) → | |
| # uploaded to the same release. Cores are fetched per-target into | |
| # src-tauri/binaries/ and bundled via the externalBin overlay. AppImage GPG | |
| # signing is opt-in (active only when the secret is set). macOS isn't shipped | |
| # yet (no Platform port) — adding it is a new matrix row. ── | |
| desktop: | |
| needs: prepare | |
| if: needs.prepare.outputs.skip != 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout the released tag | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.version }} | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| toolchain: stable | |
| targets: ${{ matrix.target }} | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: latest | |
| - name: Install Tauri CLI | |
| uses: taiki-e/install-action@15449e3094499af05d8d964a1c884208e4b8b595 # v2 | |
| with: | |
| tool: tauri-cli | |
| - name: Linux desktop deps | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \ | |
| libayatana-appindicator3-dev patchelf | |
| - name: Build the UI | |
| run: | | |
| bun install --frozen-lockfile | |
| ( cd frontend && bun run build ) | |
| - name: Fetch desktop binaries | |
| shell: bash | |
| run: scripts/fetch-binaries.sh desktop ${{ matrix.target }} | |
| - name: Import GPG key (Linux signing, optional) | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| env: | |
| GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} | |
| run: | | |
| if [ -n "$GPG_PRIVATE_KEY" ]; then | |
| echo "$GPG_PRIVATE_KEY" | gpg --batch --import | |
| fi | |
| - name: Build desktop bundles | |
| shell: bash | |
| env: | |
| # linuxdeploy/appimagetool mount themselves with FUSE, which the runner | |
| # doesn't provide — extract-and-run instead, or AppImage bundling aborts | |
| # the whole job ("failed to run linuxdeploy") after the .deb is built. | |
| APPIMAGE_EXTRACT_AND_RUN: 1 | |
| # Linux AppImage GPG signing — active only when the key/passphrase are set. | |
| SIGN: ${{ secrets.GPG_PRIVATE_KEY != '' && '1' || '' }} | |
| SIGN_KEY: ${{ secrets.GPG_KEY_ID }} | |
| APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} | |
| # minisign key for the auto-updater — makes `tauri build` emit a `.sig` | |
| # next to each updatable artifact (AppImage / NSIS). Distinct from the | |
| # GPG key above, which signs the AppImage file for manual verification. | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| run: | | |
| # Build + stage the privilege helper as a sidecar so the bundles ship it | |
| # next to the app (the Linux GUI spawns it elevated; the Windows build is | |
| # an unused stub). | |
| t="${{ matrix.target }}" | |
| ext=""; case "$t" in *windows*) ext=".exe";; esac | |
| cargo build --release --target "$t" --bin kasumi-helper | |
| cp "target/$t/release/kasumi-helper$ext" "src-tauri/binaries/kasumi-helper-$t$ext" | |
| # Inject the real product version (module.prop is the single source of | |
| # truth) so the bundles aren't the 0.0.0 placeholder. | |
| ver=$(scripts/app-version.sh) | |
| cargo tauri build --target "$t" \ | |
| --config src-tauri/tauri.bundle.conf.json \ | |
| --config "{\"version\":\"$ver\"}" | |
| - name: Collect artifacts | |
| id: collect | |
| shell: bash | |
| run: | | |
| dir="target/${{ matrix.target }}/release/bundle" | |
| mkdir -p dist | |
| find "$dir" -type f \ | |
| \( -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' -o -name '*-setup.exe' -o -name '*.msi' -o -name '*.sig' \) \ | |
| -exec cp {} dist/ \; | |
| ls -la dist | |
| - name: Assemble Windows portable zip | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| # Portable build: app exe + cores (suffix stripped) + wintun.dll + | |
| # libcronet.dll + a `portable.dat` marker that pins all state next to the | |
| # exe at runtime. | |
| t="${{ matrix.target }}" | |
| out="dist/portable/Kasumi-Proxy" | |
| mkdir -p "$out" | |
| cp "target/$t/release/kasumi-desktop.exe" "$out/" | |
| cp "target/$t/release/kasumi-helper.exe" "$out/" | |
| for c in xray sing-box tun2socks hev-socks5-tunnel; do | |
| cp "src-tauri/binaries/$c-$t.exe" "$out/$c.exe" | |
| done | |
| cp src-tauri/binaries/wintun.dll "$out/" | |
| # libcronet.dll must sit next to sing-box for its naive outbound to load. | |
| cp src-tauri/binaries/libcronet.dll "$out/" | |
| # msys-2.0.dll must sit next to hev-socks5-tunnel (msys2 build) to load. | |
| cp src-tauri/binaries/msys-2.0.dll "$out/" | |
| : > "$out/portable.dat" | |
| name=$(scripts/artifact-name.sh windows-portable) | |
| ( cd dist/portable && 7z a -tzip "../$name" Kasumi-Proxy >/dev/null ) | |
| rm -rf dist/portable | |
| ls -la dist | |
| - name: Assemble Linux portable zip | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| # Portable build: app binary + cores (suffix stripped) + a `portable.dat` | |
| # marker that pins all state next to the binary at runtime (USB-stick run). | |
| t="${{ matrix.target }}" | |
| out="dist/portable/Kasumi-Proxy" | |
| mkdir -p "$out" | |
| cp "target/$t/release/kasumi-desktop" "$out/" | |
| cp "target/$t/release/kasumi-helper" "$out/" | |
| for c in xray sing-box tun2socks hev-socks5-tunnel; do | |
| cp "src-tauri/binaries/$c-$t" "$out/$c" | |
| done | |
| # libcronet.so must sit next to sing-box for its naive outbound to load. | |
| cp src-tauri/binaries/libcronet.so "$out/" | |
| chmod +x "$out"/* | |
| : > "$out/portable.dat" | |
| name=$(scripts/artifact-name.sh linux-portable) | |
| ( cd dist/portable && zip -qr "../$name" Kasumi-Proxy ) | |
| rm -rf dist/portable | |
| ls -la dist | |
| - name: Upload to the release | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| files: dist/* | |
| # Per-platform slice of the updater manifest: the `.sig` content + the asset | |
| # URL. The `updater-manifest` job merges every platform's slice into one | |
| # latest.json (the updater endpoint must list all platforms in a single file). | |
| - name: Build updater manifest fragment | |
| shell: bash | |
| run: | | |
| case "${{ matrix.target }}" in | |
| *linux*) plat=linux-x86_64; sig=$(find dist -maxdepth 1 -name '*.AppImage.sig' | head -1) ;; | |
| *windows*) plat=windows-x86_64; sig=$(find dist -maxdepth 1 -name '*-setup.exe.sig' | head -1) ;; | |
| *) plat=""; sig="" ;; | |
| esac | |
| if [ -z "${sig:-}" ]; then | |
| echo "no updater signature for ${{ matrix.target }} (signing key unset?) — skipping" | |
| exit 0 | |
| fi | |
| # GitHub rewrites spaces in uploaded asset names to dots — match that so | |
| # the URL resolves (the AppImage carries the "Kasumi Proxy" product name). | |
| asset=$(basename "${sig%.sig}" | tr ' ' '.') | |
| tag="${{ needs.prepare.outputs.version }}" | |
| url="https://github.com/loss-and-quick/Kasumi-Proxy/releases/download/${tag}/${asset}" | |
| mkdir -p frag | |
| jq -n --arg p "$plat" --arg s "$(cat "$sig")" --arg u "$url" \ | |
| '{($p): {signature: $s, url: $u}}' > "frag/${plat}.json" | |
| cat "frag/${plat}.json" | |
| - name: Upload updater manifest fragment | |
| if: hashFiles('frag/*.json') != '' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: updater-frag-${{ matrix.target }} | |
| path: frag/*.json | |
| retention-days: 1 | |
| # ── Build the desktop app through the flake and push it + its closure to our | |
| # Cachix cache, so Nix users who add this repo as a flake input pull the | |
| # prebuilt `kasumi-desktop` for the released tag instead of compiling it. Runs | |
| # in parallel with the artifact jobs and gates nothing (a slow/failed nix build | |
| # never blocks the release). Consumers must add the cache to their own Nix | |
| # config — a flake's nixConfig does not propagate to downstream flakes (README). ── | |
| cachix-desktop: | |
| needs: prepare | |
| if: needs.prepare.outputs.skip != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout the released tag | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| ref: ${{ needs.prepare.outputs.version }} | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 | |
| with: | |
| extra-conf: | | |
| accept-flake-config = true | |
| - name: Cachix | |
| uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: kasumi-proxy | |
| authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| - name: Build kasumi-desktop (cachix-action pushes the result + closure) | |
| run: nix build .#kasumi-desktop --accept-flake-config --print-build-logs | |
| # ── Merge the per-platform updater slices into a single latest.json and attach | |
| # it to the release — this is the static endpoint the desktop updater polls. ── | |
| updater-manifest: | |
| needs: [prepare, desktop] | |
| if: needs.prepare.outputs.skip != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download manifest fragments | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: updater-frag-* | |
| path: frag | |
| merge-multiple: true | |
| - name: Assemble latest.json | |
| shell: bash | |
| run: | | |
| if ! compgen -G "frag/*.json" >/dev/null; then | |
| echo "no updater fragments — skipping latest.json" | |
| exit 0 | |
| fi | |
| tag="${{ needs.prepare.outputs.version }}" | |
| platforms=$(jq -s 'add' frag/*.json) | |
| jq -n \ | |
| --arg version "${tag#v}" \ | |
| --arg pub "$(date -u +%FT%TZ)" \ | |
| --argjson platforms "$platforms" \ | |
| '{version: $version, pub_date: $pub, platforms: $platforms}' > latest.json | |
| cat latest.json | |
| - name: Upload latest.json to the release | |
| if: hashFiles('latest.json') != '' | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | |
| with: | |
| tag_name: ${{ needs.prepare.outputs.version }} | |
| files: latest.json |