diff --git a/.github/workflows/publish-macos.yml b/.github/workflows/publish-macos.yml new file mode 100644 index 00000000..bd56677e --- /dev/null +++ b/.github/workflows/publish-macos.yml @@ -0,0 +1,63 @@ +name: Publish macOS Packages + +on: + workflow_dispatch: + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: macos-15-intel + - arch: arm64 + runner: macos-15 + + runs-on: ${{ matrix.runner }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install Wails CLI + run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0 + + - name: Add Go bin to PATH + run: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Build unsigned macOS package + run: bash publish/mac/publish-mac.sh --arch ${{ matrix.arch }} + + - name: Validate app bundle + run: | + set -euo pipefail + app_path="$(ls -1d publish/output/AntBrowser-*-macos-${{ matrix.arch }}.app | head -n 1)" + zip_path="$(ls -1 publish/output/AntBrowser-*-macos-${{ matrix.arch }}.zip | head -n 1)" + echo "Validating: $app_path" + test -x "$app_path/Contents/MacOS/ant-chrome" + test -x "$app_path/Contents/MacOS/bin/xray" + test -x "$app_path/Contents/MacOS/bin/sing-box" + plutil -lint "$app_path/Contents/Info.plist" + ditto -x -k "$zip_path" /tmp/ant-browser-macos-validate + test -x "/tmp/ant-browser-macos-validate/$(basename "$app_path")/Contents/MacOS/ant-chrome" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ant-browser-macos-${{ matrix.arch }} + path: | + publish/output/AntBrowser-*-macos-${{ matrix.arch }}.app + publish/output/AntBrowser-*-macos-${{ matrix.arch }}.zip diff --git a/.gitignore b/.gitignore index 90b276f7..0e99307f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,12 @@ chrome/* !/bin/linux-arm64/ !/bin/linux-arm64/xray !/bin/linux-arm64/sing-box +!/bin/darwin-amd64/ +!/bin/darwin-amd64/xray +!/bin/darwin-amd64/sing-box +!/bin/darwin-arm64/ +!/bin/darwin-arm64/xray +!/bin/darwin-arm64/sing-box # Publish artifacts publish/staging/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a6c655..66dcb55a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,6 @@ ## 1.1.0 - 2026-03-19 - 完善 Linux 支持:补齐 Linux 环境下的开发、打包、安装、启动与运行链路,并持续修复安装版启动与退出稳定性问题。 +- 补齐 macOS unsigned 内测支持:新增原生 macOS `.app` / `.zip` 打包、Darwin 运行时校验、Application Support 用户状态目录和 macOS 发布工作流。 - 新增 SOCKS 代理测试支持:SOCKS 代理能力已进入测试阶段,后续会继续验证稳定性与兼容性。 - 实验性支持接口触发浏览器:支持通过接口启动浏览器实例,便于后续接入自动化流程。 diff --git a/README.md b/README.md index 6c1a61ab..5d6835e1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Ant Browser -> 面向多账号隔离、代理绑定和本地环境管理的桌面浏览器工具(Windows / Linux)。 +> 面向多账号隔离、代理绑定和本地环境管理的桌面浏览器工具(Windows / Linux / macOS unsigned)。 [![Release](https://img.shields.io/github/v/release/black-ant/Ant-Browser?sort=semver)](https://github.com/black-ant/Ant-Browser/releases) -[![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux-blue)](https://github.com/black-ant/Ant-Browser/releases) +[![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-blue)](https://github.com/black-ant/Ant-Browser/releases) [![Issues](https://img.shields.io/github/issues/black-ant/Ant-Browser)](https://github.com/black-ant/Ant-Browser/issues) ## 推荐内核项目 @@ -54,6 +54,7 @@ Ant Browser 适合以下场景: ### 1.1.0 · 2026-03-19 - 完善 Linux 支持:补齐 Linux 环境下的开发、打包、安装、启动与运行链路,并持续修复安装版启动与退出稳定性问题 +- 补齐 macOS unsigned 内测构建链路:支持在原生 macOS 主机上打包 `.app` / `.zip`,并将用户状态目录放到 `~/Library/Application Support/ant-browser` - 新增 SOCKS 代理测试支持:SOCKS 代理能力已进入测试阶段,后续会继续验证稳定性与兼容性 - 实验性支持接口触发浏览器:支持通过接口启动浏览器实例,便于后续接入自动化流程 @@ -126,6 +127,7 @@ Ant Browser 适合以下场景: - 操作系统: - Windows 10 / 11(64 位) - Linux(amd64 / arm64) + - macOS(amd64 / arm64,当前为 unsigned 内测包) - 建议内存:8 GB 及以上 - 建议磁盘空间:2 GB 以上 @@ -135,15 +137,16 @@ Ant Browser 适合以下场景: 2. 安装版直接运行 `AntBrowser-Setup-*.exe` 3. 便携版解压后运行 `ant-chrome.exe` 4. Linux 包下载后可直接安装 `ant-browser__.deb`,或解压 `tar.gz` 后运行 `ant-chrome` +5. macOS unsigned 包解压后运行 `AntBrowser--macos-.app`;如被 Gatekeeper 拦截,请对本机测试包执行 `xattr -dr com.apple.quarantine ` 后再打开 ### 从源码运行 1. 开发默认使用 `master` 分支;该分支不带测试用户数据,适合作为日常开发基线。 2. 如需带测试库的演示环境,请切换到 `user_data` 分支。 3. Windows 统一执行 `bat\dev.bat`;默认是稳定模式,如需前端 HMR 联调使用 `bat\dev.bat live`,如需受限内存复现使用 `bat\dev.bat limited`。 -4. Windows 运行时使用 `bin/xray.exe`、`bin/sing-box.exe`;Linux 运行时使用 `bin/linux-/xray`、`bin/linux-/sing-box`。 +4. Windows 运行时使用 `bin/xray.exe`、`bin/sing-box.exe`;Linux 运行时使用 `bin/linux-/xray`、`bin/linux-/sing-box`;macOS 运行时使用 `bin/darwin-/xray`、`bin/darwin-/sing-box`。 5. 运行时文件采用“仓库固定 + 哈希校验”,校验清单在 `publish/runtime-manifest.json`,固定来源清单在 `publish/runtime-sources.json`。 -6. 如需刷新 Linux 运行时,执行 `python3 tools/runtime/sync-runtime.py`(会按固定来源下载、校验归档并更新 manifest)。 +6. 如需刷新 Linux / macOS 运行时,执行 `python3 tools/runtime/sync-runtime.py --target `(会按固定来源下载、校验归档并更新 manifest)。 开发模式说明: @@ -163,6 +166,17 @@ bash publish/linux/publish-linux.sh --arch arm64 详细说明见 [publish/linux/README.md](publish/linux/README.md)。 +### macOS unsigned 发布打包(源码) + +macOS 发布脚本位于 `publish/mac/`,必须在原生 macOS 主机上执行,且目标架构需与主机架构一致。 + +```bash +bash publish/mac/publish-mac.sh --arch amd64 +bash publish/mac/publish-mac.sh --arch arm64 +``` + +脚本会生成 unsigned `.app` 和 `.zip`,适合 PR 验证与内部测试。详细说明见 [publish/mac/README.md](publish/mac/README.md)。 + ### 准备浏览器内核 代理运行时已经随仓库提供,你只需要准备浏览器内核。 diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 2e00454d..2ec7ed10 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + goruntime "runtime" "strings" "gopkg.in/yaml.v3" @@ -406,7 +407,7 @@ func DefaultConfig() *Config { }, Browser: BrowserConfig{ UserDataRoot: "data", - DefaultFingerprintArgs: []string{"--fingerprint-brand=Chrome", "--fingerprint-platform=windows"}, + DefaultFingerprintArgs: defaultFingerprintArgsForOS(goruntime.GOOS), DefaultLaunchArgs: []string{"--disable-sync", "--no-first-run"}, DefaultProxy: "", StartReadyTimeoutMs: 3000, @@ -445,6 +446,17 @@ func DefaultConfig() *Config { } } +func defaultFingerprintArgsForOS(goos string) []string { + platform := "windows" + switch strings.ToLower(strings.TrimSpace(goos)) { + case "darwin": + platform = "mac" + case "linux": + platform = "linux" + } + return []string{"--fingerprint-brand=Chrome", "--fingerprint-platform=" + platform} +} + // Save 保存配置到文件 func (c *Config) Save(configPath string) error { data, err := yaml.Marshal(c) diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index 3eca9f18..b0400463 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -3,6 +3,7 @@ package config import ( "os" "path/filepath" + "runtime" "testing" ) @@ -74,6 +75,42 @@ browser: {} } } +func TestDefaultFingerprintArgsForOS(t *testing.T) { + t.Parallel() + + tests := map[string]string{ + "windows": "--fingerprint-platform=windows", + "linux": "--fingerprint-platform=linux", + "darwin": "--fingerprint-platform=mac", + "freebsd": "--fingerprint-platform=windows", + } + + for goos, want := range tests { + got := defaultFingerprintArgsForOS(goos) + if len(got) != 2 { + t.Fatalf("%s: unexpected args length: got=%v", goos, got) + } + if got[1] != want { + t.Fatalf("%s: platform arg mismatch: got=%q want=%q", goos, got[1], want) + } + } +} + +func TestDefaultConfigUsesCurrentOSFingerprintPlatform(t *testing.T) { + t.Parallel() + + cfg := DefaultConfig() + want := defaultFingerprintArgsForOS(runtime.GOOS) + if len(cfg.Browser.DefaultFingerprintArgs) != len(want) { + t.Fatalf("默认指纹参数数量不符: got=%v want=%v", cfg.Browser.DefaultFingerprintArgs, want) + } + for i := range want { + if cfg.Browser.DefaultFingerprintArgs[i] != want[i] { + t.Fatalf("默认指纹参数不符: got=%v want=%v", cfg.Browser.DefaultFingerprintArgs, want) + } + } +} + func TestLoadPreservesExplicitConfig(t *testing.T) { t.Parallel() diff --git a/bin/README.md b/bin/README.md index 2b2aa9e4..afabfcbf 100644 --- a/bin/README.md +++ b/bin/README.md @@ -12,6 +12,13 @@ Linux (new): - `bin/linux-arm64/xray` - `bin/linux-arm64/sing-box` +macOS (unsigned internal builds): + +- `bin/darwin-amd64/xray` +- `bin/darwin-amd64/sing-box` +- `bin/darwin-arm64/xray` +- `bin/darwin-arm64/sing-box` + Runtime hashes are pinned in `publish/runtime-manifest.json`. Pinned upstream archive sources are tracked in `publish/runtime-sources.json`. -Use `python3 tools/runtime/sync-runtime.py` to refresh Linux runtimes safely. +Use `python3 tools/runtime/sync-runtime.py --target ` to refresh runtime files safely. diff --git a/build/darwin/Info.plist b/build/darwin/Info.plist index d17a7475..69696ab4 100644 --- a/build/darwin/Info.plist +++ b/build/darwin/Info.plist @@ -8,7 +8,7 @@ CFBundleExecutable {{.OutputFilename}} CFBundleIdentifier - com.wails.{{.Name}} + com.antbrowser.desktop CFBundleVersion {{.Info.ProductVersion}} CFBundleGetInfoString diff --git a/chrome/README.md b/chrome/README.md new file mode 100644 index 00000000..17eca5e8 --- /dev/null +++ b/chrome/README.md @@ -0,0 +1,5 @@ +# Browser Core Placeholder + +Place downloaded browser cores under this directory when running from source. + +Published macOS and Linux packages include this placeholder only. Actual browser core binaries are user-managed and should live under the writable `chrome/` directory in the runtime state root. diff --git a/publish/mac/README.md b/publish/mac/README.md index 4b4c70d0..661c0bb7 100644 --- a/publish/mac/README.md +++ b/publish/mac/README.md @@ -1,360 +1,127 @@ -# macOS Publish Plan +# macOS Publish -## Purpose +This directory contains the unsigned macOS packaging flow for Ant Browser. -This document defines the macOS packaging plan for Ant Browser. +The macOS package is intended for internal testing and PR validation first. It deliberately does not perform Apple Developer ID signing or notarization. -The goal is to turn the current codebase into a macOS build that can: +## Targets -- build on a native macOS machine -- launch from `/Applications` -- keep user-writable state outside the `.app` bundle -- bundle required proxy runtime binaries -- avoid breaking existing Windows and Linux packaging flows +- `darwin/amd64` +- `darwin/arm64` -## Current Entry Command +Output artifacts: -The initial internal-build script can be invoked on a real Mac with: +- `publish/output/AntBrowser--macos-.app` +- `publish/output/AntBrowser--macos-.zip` + +## Runtime Policy + +macOS publish uses repository-pinned runtime files and hash verification. + +Pinned upstream source lock file: + +- `publish/runtime-sources.json` + +Required files: + +- `bin/darwin-amd64/xray` +- `bin/darwin-amd64/sing-box` +- `bin/darwin-arm64/xray` +- `bin/darwin-arm64/sing-box` + +Hashes are validated by: + +- `tools/runtime/verify-runtime.sh` +- `publish/runtime-manifest.json` + +Recommended way to refresh runtime files: ```bash +python3 tools/runtime/sync-runtime.py --target darwin-amd64 +python3 tools/runtime/sync-runtime.py --target darwin-arm64 +``` + +If you replace runtime files manually, update manifest hashes: + +```bash +python3 tools/runtime/update-runtime-manifest.py --target darwin-amd64 +python3 tools/runtime/update-runtime-manifest.py --target darwin-arm64 +``` + +If runtime file or hash is missing/mismatched, publish will fail. + +## Commands + +Build on a native macOS host that matches the target architecture: + +```bash +bash publish/mac/publish-mac.sh --arch amd64 bash publish/mac/publish-mac.sh --arch arm64 ``` -For the first iteration, `arm64` is the recommended target. +The first command is for Intel Macs. The second command is for Apple Silicon Macs. -This is a plan document only. It does not mean macOS packaging is already implemented. +The script: -## Current Status +1. verifies the host OS and architecture +2. verifies pinned Darwin runtime hashes +3. installs frontend dependencies +4. builds frontend assets +5. runs `wails build -platform darwin/` +6. assembles an unsigned `.app` +7. bundles `xray` and `sing-box` under `Contents/MacOS/bin` +8. writes a `.zip` artifact with the app bundle as the top-level item -The repository already has: +## Writable State Layout -- Windows packaging flow -- Linux packaging flow -- partial Darwin runtime compatibility in backend code -- initial `publish/mac/publish-mac.sh` scaffold for internal test builds -- initial `publish/config.init.mac.yaml` template -- committed Darwin runtime binaries under `bin/darwin-amd64/` and `bin/darwin-arm64/` -- Darwin runtime entries in `publish/runtime-manifest.json` -- Darwin runtime source lock entries in `publish/runtime-sources.json` +Installed `.app` bundles should be treated as read-only. When Ant Browser runs from: -The repository does not yet have: +- `Ant Browser.app/Contents/MacOS` +- `Ant Browser.app/Contents/Resources` -- signing / notarization flow - -The repository now includes the first macOS writable-state implementation for app bundle roots: - -- when the app root is inside `.app/Contents/MacOS` or `.app/Contents/Resources` -- writable state is redirected to `~/Library/Application Support/ant-browser` -- `bin/` stays in the app bundle -- config, chrome, and data move to the user state root - -## Current Implementation Note - -The current initial macOS packaging scaffold intentionally places helper binaries and seed files under: - -- `Ant Browser.app/Contents/MacOS/bin` -- `Ant Browser.app/Contents/MacOS/config.yaml` -- `Ant Browser.app/Contents/MacOS/chrome/README.md` - -This is not the prettiest final bundle layout, but it matches the current runtime path resolution and avoids a larger refactor in Phase 1. - -After the first internal build is stable, the bundle layout can be reviewed and moved toward `Contents/Resources` if needed. - -## Why macOS Looks More Complex - -macOS is not difficult because of Wails alone. The real complexity comes from four areas: - -1. Installed `.app` bundles under `/Applications` should be treated as read-only. -2. User data must not be written inside the `.app` bundle. -3. External helper binaries such as `xray` and `sing-box` must exist for Darwin and must be bundled correctly. -4. Public distribution usually requires code signing and notarization, otherwise Gatekeeper may block launch. - -## Recommended Scope - -### Phase 1: Internal Test Build - -Target: - -- `darwin/arm64` first -- output `.app` and `.zip` -- unsigned build is acceptable for internal testing - -Why: - -- Apple Silicon is the mainstream macOS target now -- it keeps the first version smaller and easier to verify -- it avoids spending time on Intel support before the runtime path is stable - -### Phase 2: Public Distribution Build - -Target: - -- signed `.app` -- notarized `.zip` or `.dmg` -- optional `darwin/amd64` or universal build - -Why: - -- end users expect double-click install and normal launch -- unsigned apps and embedded helper binaries are more likely to be blocked - -## Recommended Runtime Layout - -### App Bundle - -Recommended structure inside the built app: - -- `Ant Browser.app/Contents/MacOS/ant-chrome` -- `Ant Browser.app/Contents/Resources/bin/xray` -- `Ant Browser.app/Contents/Resources/bin/sing-box` -- optional placeholder `chrome/README.md` if you want to keep behavior aligned with Linux - -### User-Writable State - -Recommended macOS state root: +runtime state is redirected to: - `~/Library/Application Support/ant-browser` -Recommended contents under the state root: +Bundled runtime files remain in the app bundle: + +- `Ant Browser.app/Contents/MacOS/bin/xray` +- `Ant Browser.app/Contents/MacOS/bin/sing-box` + +Writable files live in the user state root: - `config.yaml` -- `proxies.yaml` +- `proxies.yaml` if present - `data/` - `chrome/` -Rule: +## Local Launch -- runtime binaries stay in the app bundle -- config, database, browser cores, logs, and profile data stay in the user state root +After a successful build: -## Code Changes Required +```bash +open "publish/output/AntBrowser--macos-.app" +``` -### 1. Add macOS Writable State Handling +For an unsigned local build copied to `/Applications`, remove quarantine if macOS blocks launch: -Current Linux detached-state logic only activates on Linux: +```bash +xattr -dr com.apple.quarantine "/Applications/Ant Browser.app" +open "/Applications/Ant Browser.app" +``` -- `backend/internal/apppath/apppath.go` - -Required change: - -- extend path detection so installed macOS apps also use a detached writable state root -- recommended trigger: when `GOOS=darwin` and app root is not writable, or when running from an `.app` bundle - -Expected result: - -- app launch from `/Applications` does not try to write config/data into the bundle - -### 2. Add Darwin Runtime Binaries - -Current runtime manifest has Windows and Linux only: - -- `publish/runtime-manifest.json` - -Required additions: - -- `bin/darwin-arm64/xray` -- `bin/darwin-arm64/sing-box` -- optional `bin/darwin-amd64/xray` -- optional `bin/darwin-amd64/sing-box` -- manifest hash entries for the new targets - -Status: - -- implemented for both `darwin-arm64` and `darwin-amd64` -- files are committed into the repository -- runtime manifest verification now works for both Darwin targets - -Related scripts to extend: - -- `tools/runtime/sync-runtime.py` -- `tools/runtime/update-runtime-manifest.py` -- `tools/runtime/verify-runtime.sh` - -### 3. Add macOS Publish Script - -New file to add: - -- `publish/mac/publish-mac.sh` - -Recommended responsibilities: - -1. verify host is macOS -2. verify target arch (`arm64` first) -3. install frontend dependencies -4. build frontend -5. run `wails build -platform darwin/arm64` -6. place runtime binaries into the app bundle -7. optionally archive to `.zip` -8. optionally sign and notarize when environment variables are provided - -Current scaffold status: - -- implemented as an unsigned internal-build script -- outputs `.app` plus `.zip` -- requires a native macOS host -- intentionally does not attempt notarization yet - -### 4. Add macOS Runtime Placement Logic - -The app currently resolves most paths through shared runtime helpers, which is good. - -Files likely involved: - -- `backend/app.go` -- `backend/app_paths.go` -- `backend/app_utils.go` -- `backend/internal/browser/types.go` -- `backend/internal/proxy/xray.go` -- `backend/internal/proxy/singbox.go` -- `main.go` - -Goal: - -- all writable files go to the user state root -- helper binaries continue to resolve from the app bundle - -### 5. Signing and Notarization - -This is not required for a first internal test build, but is required for a serious public release. - -Needed later: - -- Apple Developer certificate -- `codesign` -- `notarytool` -- entitlements if runtime behavior requires them - -Typical flow: - -1. sign helper binaries -2. sign the `.app` -3. zip or build dmg -4. notarize -5. staple - -## Recommended Implementation Order - -1. Deliver `darwin/arm64` internal test build only. -2. Add macOS detached state root. -3. Add Darwin runtime binaries and manifest support. -4. Add `publish/mac/publish-mac.sh`. -5. Verify launch from `/Applications`. -6. Verify browser core placement under user state root. -7. Verify proxy runtime launch on macOS. -8. Add signing and notarization only after the unsigned build is stable. -9. Decide whether `darwin/amd64` is worth supporting. +Unsigned builds are not suitable for public distribution. A public macOS release still needs Developer ID signing, helper binary signing, notarization, and staple. ## Validation Checklist -The macOS work should not be considered complete until all items below are verified on a real Mac. - -### Packaging - - build completes on native macOS - output `.app` exists -- output `.zip` or `.dmg` exists -- bundled helper binaries are executable - -### First Launch - -- app launches from Finder -- app launches after copying to `/Applications` +- output `.zip` exists +- `Contents/MacOS/ant-chrome` is executable +- bundled `xray` and `sing-box` are executable +- app launches from Finder or `open` - first launch creates `~/Library/Application Support/ant-browser` -- `config.yaml` is seeded correctly -- database and `data/` are created under the user state root - -### Browser Core - -- manually placed browser core can be detected -- browser core path persists in config or database -- browser instance can actually start - -### Proxy Runtime - -- `xray` can be launched from the app bundle -- `sing-box` can be launched from the app bundle -- work directories are created under the user state root - -### Exit Behavior - -- window close works -- explicit quit works -- no stuck background process remains after quit - -### Regression Safety - -- Windows packaging still builds -- Linux packaging still builds -- Linux detached state behavior still works - -## Difficulty Assessment - -### Internal Test Build - -Difficulty: medium - -Main blockers: - -- mac runtime binaries -- detached writable state -- mac packaging script - -### Public Release Build - -Difficulty: medium-high - -Main blockers: - -- signing -- notarization -- quarantine / Gatekeeper behavior -- helper binary signing order - -## Suggested First Deliverable - -The safest first milestone is: - -- macOS `arm64` -- native build on a real Mac -- unsigned `.app` -- zipped artifact for internal testing -- detached writable state under `~/Library/Application Support/ant-browser` -- bundled `xray` and `sing-box` - -Do not start with: - -- universal binary -- dmg beautification -- public distribution -- Intel support - -Those can come after the app is proven stable on one Mac target first. - -## Files Expected To Be Added Or Updated - -Likely new files: - -- `publish/mac/publish-mac.sh` -- `publish/mac/README.md` - -Likely updated files: - -- `backend/internal/apppath/apppath.go` -- `backend/internal/apppath/apppath_test.go` -- `backend/runtime_paths.go` -- `backend/app.go` -- `main.go` -- `publish/runtime-manifest.json` -- `publish/runtime-sources.json` -- `tools/runtime/sync-runtime.py` -- `tools/runtime/update-runtime-manifest.py` -- `tools/runtime/verify-runtime.sh` - -## Decision Record - -Current recommendation: - -- do macOS `arm64` first -- solve writable state before touching signing -- keep Windows and Linux publish flows unchanged unless shared runtime code needs extension -- treat public notarized distribution as Phase 2, not Phase 1 +- `config.yaml` is seeded with `--fingerprint-platform=mac` +- SQLite database and `data/` are created under the user state root +- browser core detection works for macOS `.app` cores +- proxy runtime binaries can be launched from the app bundle