Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ env:
# Ubuntu 22.04 and 24.04, Windows MSVC + WebView2 on windows-2025. The 22.04
# packages are extra release assets (`*_ubuntu2204_*`) because that glibc is
# too old for a binary built on 24.04. Updater latest.json stays on the 24.04
# deb and AppImage. Do not cross-compile the Windows installer from Linux here;
# deb and AppImage, plus the signed Apple Silicon disk image (ADR 0126).
# Do not cross-compile the Windows installer from Linux here;
# the Linux Tauri CLI rejects --bundles nsis.

concurrency:
Expand Down Expand Up @@ -234,6 +235,18 @@ jobs:
TAURI_PRIVATE_KEY="${TAURI_SIGNING_PRIVATE_KEY}" \
TAURI_PRIVATE_KEY_PASSWORD="${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" \
pnpm exec tauri signer sign "${debs[0]}"
- name: Sign macOS updater package
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
dmgs=(artifacts/macos/*.dmg)
(( ${#dmgs[@]} == 1 )) || { echo "Expected one macOS disk image to sign"; exit 1; }
TAURI_PRIVATE_KEY="${TAURI_SIGNING_PRIVATE_KEY}" \
TAURI_PRIVATE_KEY_PASSWORD="${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" \
pnpm exec tauri signer sign "${dmgs[0]}"
[[ -f "${dmgs[0]}.sig" ]] || { echo "macOS updater signature was not written"; exit 1; }
- name: Stage portable Windows app
if: runner.os == 'Windows'
shell: bash
Expand Down Expand Up @@ -270,7 +283,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: release-macos
path: artifacts/macos/*.dmg
path: |
artifacts/macos/*.dmg
artifacts/macos/*.dmg.sig
if-no-files-found: error
retention-days: 7

Expand Down Expand Up @@ -321,10 +336,11 @@ jobs:
mapfile -t installers < <(find release-assets -type f -name '*-setup.exe' -print | sort)
mapfile -t installer_sigs < <(find release-assets -type f -name '*-setup.exe.sig' -print | sort)
mapfile -t manifests < <(find release-assets -type f -name 'latest.json' -print | sort)
mapfile -t dmgs < <(find release-assets -type f -name '*.dmg' -print | sort)
mapfile -t dmgs < <(find release-assets -type f -name '*.dmg' ! -name '*.dmg.sig' -print | sort)
mapfile -t dmg_sigs < <(find release-assets -type f -name '*.dmg.sig' -print | sort)

if (( ${#debs[@]} != 1 || ${#deb_sigs[@]} != 1 || ${#appimages[@]} != 1 || ${#appimage_sigs[@]} != 1 || ${#ubuntu_debs[@]} != 1 || ${#ubuntu_deb_sigs[@]} != 1 || ${#ubuntu_appimages[@]} != 1 || ${#ubuntu_appimage_sigs[@]} != 1 || ${#portable[@]} != 1 || ${#installers[@]} != 1 || ${#installer_sigs[@]} != 1 || ${#manifests[@]} != 1 || ${#dmgs[@]} != 1 )); then
echo "Expected Ubuntu 24.04 and Ubuntu 22.04 deb/AppImage pairs with signatures, one portable exe, one NSIS installer and signature, one macOS disk image, and latest.json"
if (( ${#debs[@]} != 1 || ${#deb_sigs[@]} != 1 || ${#appimages[@]} != 1 || ${#appimage_sigs[@]} != 1 || ${#ubuntu_debs[@]} != 1 || ${#ubuntu_deb_sigs[@]} != 1 || ${#ubuntu_appimages[@]} != 1 || ${#ubuntu_appimage_sigs[@]} != 1 || ${#portable[@]} != 1 || ${#installers[@]} != 1 || ${#installer_sigs[@]} != 1 || ${#manifests[@]} != 1 || ${#dmgs[@]} != 1 || ${#dmg_sigs[@]} != 1 )); then
echo "Expected Ubuntu 24.04 and Ubuntu 22.04 deb/AppImage pairs with signatures, one portable exe, one NSIS installer and signature, one signed macOS disk image, and latest.json"
find release-assets -type f -print
exit 1
fi
Expand All @@ -337,7 +353,7 @@ jobs:
git push origin "refs/tags/${tag}"
fi

assets=("${debs[@]}" "${deb_sigs[@]}" "${appimages[@]}" "${appimage_sigs[@]}" "${ubuntu_debs[@]}" "${ubuntu_deb_sigs[@]}" "${ubuntu_appimages[@]}" "${ubuntu_appimage_sigs[@]}" "${portable[@]}" "${installers[@]}" "${installer_sigs[@]}" "${dmgs[@]}" "${manifests[@]}")
assets=("${debs[@]}" "${deb_sigs[@]}" "${appimages[@]}" "${appimage_sigs[@]}" "${ubuntu_debs[@]}" "${ubuntu_deb_sigs[@]}" "${ubuntu_appimages[@]}" "${ubuntu_appimage_sigs[@]}" "${portable[@]}" "${installers[@]}" "${installer_sigs[@]}" "${dmgs[@]}" "${dmg_sigs[@]}" "${manifests[@]}")
if ! gh release view "${tag}" >/dev/null 2>&1; then
gh release create "${tag}" \
--draft \
Expand Down
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -764,3 +764,11 @@ SHA-256")`); launchd `KeepAlive` then restarts it in a crash loop and the
status icon while that title is shown and restore the retained image when
it is cleared. Widen both lines together when a fixed unit is longer
(ADR 0122).

- Apple Silicon in-app update asks the Tauri updater for platform
`macos-aarch64`. A `latest.json` that only lists Linux and Windows fails
with "the platform macos-aarch64 was not found in the response platforms
object" even when the GitHub Release already has
`BiFlow_*_aarch64.dmg`. Sign that disk image in the release job and publish
it as the `macos-aarch64` entry. The platform key must stay identical to
`signed_target` for `InstallKind::Dmg` on `aarch64` (ADR 0126).
30 changes: 15 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ members = [
]

[workspace.package]
version = "6.2.74"
version = "6.2.75"
edition = "2021"
license = "MIT OR Apache-2.0"
rust-version = "1.88"
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@iran-split/desktop",
"version": "6.2.74",
"version": "6.2.75",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/tsconfig.app.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"root":["./src/App.test.tsx","./src/App.tsx","./src/installContextMenuGuard.ts","./src/main.tsx","./src/palette.test.ts","./src/shell.test.ts","./src/version.test.ts","./src/version.ts","./src/vite-env.d.ts","./src/api/desktop.native.test.ts","./src/api/desktop.ts","./src/api/mock.test.ts","./src/api/mock.ts","./src/api/models.ts","./src/components/About.test.tsx","./src/components/About.tsx","./src/components/AddSiteBar.tsx","./src/components/AppButton.test.tsx","./src/components/AppButton.tsx","./src/components/AppStatusBar.test.tsx","./src/components/AppStatusBar.tsx","./src/components/BasicDashboard.test.tsx","./src/components/BasicDashboard.tsx","./src/components/BottomNav.test.tsx","./src/components/BottomNav.tsx","./src/components/ClientRegistry.test.tsx","./src/components/ClientRegistry.tsx","./src/components/ConnectionActionButton.test.tsx","./src/components/ConnectionActionButton.tsx","./src/components/Dashboard.test.tsx","./src/components/Dashboard.tsx","./src/components/DefaultRouteSelect.tsx","./src/components/Diagnostics.test.tsx","./src/components/Diagnostics.tsx","./src/components/DirectRules.test.tsx","./src/components/DirectRules.tsx","./src/components/InputContextMenu.test.tsx","./src/components/InputContextMenu.tsx","./src/components/LifecycleCancelButton.tsx","./src/components/MihomoConfigDialog.tsx","./src/components/PageSkeleton.tsx","./src/components/RecentSites.test.tsx","./src/components/RecentSites.tsx","./src/components/Settings.test.tsx","./src/components/Settings.tsx","./src/components/SettingsApplyBanner.test.tsx","./src/components/SettingsApplyBanner.tsx","./src/components/SortHeader.tsx","./src/components/StatusPill.tsx","./src/components/TrafficFlow.tsx","./src/components/UiModeSwitch.test.tsx","./src/components/UiModeSwitch.tsx","./src/components/country.ts","./src/components/ui/InfoTip.tsx","./src/components/ui/PageHeader.tsx","./src/components/ui/Tabs.tsx","./src/components/ui/Toast.tsx","./src/components/ui/usePageTab.ts","./src/dev/local-deps.test.ts","./src/i18n/config.ts","./src/lib/clients.test.ts","./src/lib/clients.ts","./src/lib/clipboard.ts","./src/lib/connectComponentProgress.test.ts","./src/lib/connectRequirements.test.ts","./src/lib/connectRequirements.ts","./src/lib/connectionGroups.test.ts","./src/lib/connectionGroups.ts","./src/lib/connectionProgress.test.ts","./src/lib/connectionProgress.ts","./src/lib/directDns.test.ts","./src/lib/directDns.ts","./src/lib/editableTarget.test.ts","./src/lib/editableTarget.ts","./src/lib/failureReason.test.ts","./src/lib/failureReason.ts","./src/lib/formatTraffic.test.ts","./src/lib/formatTraffic.ts","./src/lib/hiddenHosts.test.ts","./src/lib/hiddenHosts.ts","./src/lib/host.test.ts","./src/lib/host.ts","./src/lib/lifecycle.test.ts","./src/lib/lifecycle.ts","./src/lib/navigation.ts","./src/lib/outbound.ts","./src/lib/presets.test.ts","./src/lib/presets.ts","./src/lib/settingsApply.test.ts","./src/lib/settingsApply.ts","./src/lib/sideTunnelConnect.test.ts","./src/lib/sideTunnelConnect.ts","./src/lib/tableSort.test.ts","./src/lib/tableSort.ts","./src/lib/uiMode.ts","./src/lib/viewport.test.ts","./src/lib/viewport.ts","./src/lib/yamlHighlight.test.ts","./src/lib/yamlHighlight.ts","./src/store/app.test.ts","./src/store/app.ts","./src/test/fixtures.ts","./src/test/localStorage-shim.ts","./src/test/setup.ts"],"version":"5.8.3"}
{"root":["./src/app.test.tsx","./src/app.tsx","./src/installcontextmenuguard.ts","./src/main.tsx","./src/palette.test.ts","./src/shell.test.ts","./src/version.test.ts","./src/version.ts","./src/vite-env.d.ts","./src/api/desktop.native.test.ts","./src/api/desktop.ts","./src/api/mock.test.ts","./src/api/mock.ts","./src/api/models.ts","./src/components/about.test.tsx","./src/components/about.tsx","./src/components/addsitebar.tsx","./src/components/appbutton.test.tsx","./src/components/appbutton.tsx","./src/components/appstatusbar.test.tsx","./src/components/appstatusbar.tsx","./src/components/basicdashboard.test.tsx","./src/components/basicdashboard.tsx","./src/components/bottomnav.test.tsx","./src/components/bottomnav.tsx","./src/components/clientregistry.test.tsx","./src/components/clientregistry.tsx","./src/components/connectionactionbutton.test.tsx","./src/components/connectionactionbutton.tsx","./src/components/dashboard.test.tsx","./src/components/dashboard.tsx","./src/components/defaultrouteselect.tsx","./src/components/diagnostics.test.tsx","./src/components/diagnostics.tsx","./src/components/directrules.test.tsx","./src/components/directrules.tsx","./src/components/inputcontextmenu.test.tsx","./src/components/inputcontextmenu.tsx","./src/components/lifecyclecancelbutton.tsx","./src/components/mihomoconfigdialog.tsx","./src/components/pageskeleton.tsx","./src/components/recentsites.test.tsx","./src/components/recentsites.tsx","./src/components/settings.test.tsx","./src/components/settings.tsx","./src/components/settingsapplybanner.test.tsx","./src/components/settingsapplybanner.tsx","./src/components/sortheader.tsx","./src/components/statuspill.tsx","./src/components/trafficflow.tsx","./src/components/uimodeswitch.test.tsx","./src/components/uimodeswitch.tsx","./src/components/country.ts","./src/components/ui/infotip.tsx","./src/components/ui/pageheader.tsx","./src/components/ui/tabs.tsx","./src/components/ui/toast.tsx","./src/components/ui/usepagetab.ts","./src/dev/local-deps.test.ts","./src/i18n/config.ts","./src/lib/clients.test.ts","./src/lib/clients.ts","./src/lib/clipboard.ts","./src/lib/connectcomponentprogress.test.ts","./src/lib/connectrequirements.test.ts","./src/lib/connectrequirements.ts","./src/lib/connectiongroups.test.ts","./src/lib/connectiongroups.ts","./src/lib/connectionprogress.test.ts","./src/lib/connectionprogress.ts","./src/lib/directdns.test.ts","./src/lib/directdns.ts","./src/lib/editabletarget.test.ts","./src/lib/editabletarget.ts","./src/lib/failurereason.test.ts","./src/lib/failurereason.ts","./src/lib/formattraffic.test.ts","./src/lib/formattraffic.ts","./src/lib/hiddenhosts.test.ts","./src/lib/hiddenhosts.ts","./src/lib/host.test.ts","./src/lib/host.ts","./src/lib/lifecycle.test.ts","./src/lib/lifecycle.ts","./src/lib/navigation.ts","./src/lib/outbound.ts","./src/lib/presets.test.ts","./src/lib/presets.ts","./src/lib/settingsapply.test.ts","./src/lib/settingsapply.ts","./src/lib/sidetunnelconnect.test.ts","./src/lib/sidetunnelconnect.ts","./src/lib/tablesort.test.ts","./src/lib/tablesort.ts","./src/lib/uimode.ts","./src/lib/viewport.test.ts","./src/lib/viewport.ts","./src/lib/yamlhighlight.test.ts","./src/lib/yamlhighlight.ts","./src/store/app.test.ts","./src/store/app.ts","./src/test/fixtures.ts","./src/test/localstorage-shim.ts","./src/test/setup.ts"],"version":"5.8.3"}
5 changes: 3 additions & 2 deletions docs/adr/0123-publish-macos-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ version file still publishes, including a re-run. The macOS job runs
fallback, and the publish job uploads `BiFlow_<version>_aarch64.dmg` next to
the signed Linux and Windows packages.

The disk image is a manual download. It is not an updater target: `latest.json`
still lists only the signed Debian package, AppImage, and NSIS installer.
Apple Silicon in-app updates install this disk image.
[0126](./0126-macos-updater-platform.md) signs it and publishes the
`macos-aarch64` platform in `latest.json`.

The publish job creates the missing tag itself. A tag pushed with the
workflow token does not start another workflow, so the tag cannot be relied on
Expand Down
21 changes: 21 additions & 0 deletions docs/adr/0126-macos-updater-platform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ADR 0126: Publish the Apple Silicon disk image as an updater platform

Status: Accepted (6.2.75)

About → Check for updates on Apple Silicon asks the Tauri updater for platform
`macos-aarch64`. Release 6.2.74 uploaded `BiFlow_6.2.74_aarch64.dmg` but
`latest.json` listed only `linux-deb-x86_64`, `linux-x86_64`, and
`windows-x86_64`. The updater then failed with "the platform macos-aarch64 was
not found in the response platforms object" before the disk image could be
installed.

The macOS release job signs `artifacts/macos/BiFlow_<version>_aarch64.dmg`
with the same minisign key used for the Debian package and uploads the `.sig`
beside the disk image. `scripts/generate-latest-json.mjs` publishes that pair
as `platforms["macos-aarch64"]`. The key stays identical to
`signed_target(InstallKind::Dmg)` on `aarch64`. Intel Macs are still not an
updater target; this release builds only the Apple Silicon disk image.

An already published release cannot grow this platform after the fact: the
6.2.74 disk image has no signature. The next version bump publishes a manifest
that installed Apple Silicon builds can install.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ Keep this index current. Add a new ADR for each non-obvious change, or update th
| [0123](./0123-publish-macos-release.md) | Publish the macOS disk image when the version reaches main | Accepted |
| [0124](./0124-pr-validation-and-safe-archives.md) | Cross-platform PR validation and safe archive dependencies | Accepted |
| [0125](./0125-native-release-bootstrap.md) | Native release bootstrap on macOS and Windows | Accepted |
| [0126](./0126-macos-updater-platform.md) | Publish the Apple Silicon disk image as an updater platform | Accepted |
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "iran-split-desktop-workspace",
"version": "6.2.74",
"version": "6.2.75",
"private": true,
"packageManager": "pnpm@9.0.1",
"engines": {
Expand Down
3 changes: 3 additions & 0 deletions scripts/build-plan.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,10 @@ describe("release artifact names", () => {
assert.match(workflow, /platform: macos-15/);
assert.match(workflow, /\.\/build\.sh ci-macos/);
assert.match(workflow, /artifacts\/macos\/\*\.dmg/);
assert.match(workflow, /artifacts\/macos\/\*\.dmg\.sig/);
assert.match(workflow, /Sign macOS updater package/);
assert.match(workflow, /-name '\*\.dmg'/);
assert.match(workflow, /-name '\*\.dmg\.sig'/);
assert.match(workflow, /git push origin "refs\/tags\/\$\{tag\}"/);
assert.doesNotMatch(workflow, /ref_protected/);
assert.match(workflow, /--bundles deb,appimage/);
Expand Down
20 changes: 19 additions & 1 deletion scripts/generate-latest-json.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import { basename, join, relative } from "node:path";
import { fileURLToPath } from "node:url";

const PLATFORM_KEYS = ["linux-deb-x86_64", "linux-x86_64", "windows-x86_64"];
const PLATFORM_KEYS = [
"linux-deb-x86_64",
"linux-x86_64",
"windows-x86_64",
// Apple Silicon asks the updater for this key. Keep it identical to
// `signed_target` in src-tauri/src/github_update.rs.
"macos-aarch64",
];
const SEMVER =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;

Expand Down Expand Up @@ -38,6 +45,7 @@ export function buildLatestJson(repo, releaseTag, artifacts, options = {}) {
"linux-deb-x86_64": `BiFlow_${version}_amd64.deb`,
"linux-x86_64": `BiFlow_${version}_amd64.AppImage`,
"windows-x86_64": `BiFlow_${version}_x64-setup.exe`,
"macos-aarch64": `BiFlow_${version}_aarch64.dmg`,
};

for (const artifact of artifacts) {
Expand Down Expand Up @@ -193,6 +201,16 @@ export function discoverSignedArtifacts(directory) {
});
}

const dmg = findUniqueBundle(files, "_aarch64.dmg");
if (dmg) {
artifacts.push({
platform: "macos-aarch64",
bundlePath: dmg,
fileName: basename(dmg),
signature: readSignature(`${dmg}.sig`),
});
}

return artifacts;
}

Expand Down
Loading
Loading