From 40b7e41cbf3d5e4c7da306a333cdaf3ae99a5e9d Mon Sep 17 00:00:00 2001 From: gentpan Date: Tue, 15 Sep 2026 16:21:29 +0500 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=20Intel=20=E8=8A=AF=E7=89=87?= =?UTF-8?q?=EF=BC=9A=E5=88=86=E5=88=AB=E6=89=93=E5=8C=85=20Apple=20?= =?UTF-8?q?=E8=8A=AF=E7=89=87=E7=89=88=E4=B8=8E=20Intel=20=E7=89=88?= =?UTF-8?q?=EF=BC=8C=E5=AE=98=E7=BD=91=E6=8C=89=E8=8A=AF=E7=89=87=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E4=B8=A4=E4=B8=AA=E4=B8=8B=E8=BD=BD=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Makefile 支持 ARCH=arm64 / x86_64,release.sh 两种芯片各自构建、签名、公证,生成 DMG 与升级包 - 版本清单顶层仍是 Apple 芯片版(兼容 0.3.0),intel 字段放 Intel 版;应用按芯片挑包,安装前核对架构 - Homebrew cask 按 arch 选择安装包 - 官网下载区改为 Apple 芯片 / Intel 两张卡片,README 安装段落与常见问题同步 --- CHANGELOG.md | 9 + Makefile | 7 +- .../Sources/Localization/Translations.swift | 1 + .../OpenStatsUI/State/UpdateController.swift | 6 +- .../Sources/Updates/UpdateFeed.swift | 59 ++++++- .../Sources/Updates/UpdateInstaller.swift | 7 +- .../Tests/UpdatesTests/UpdateFeedTests.swift | 15 ++ README.md | 61 +++---- README.zh-CN.md | 58 +++---- Scripts/appcast.py | 22 ++- Scripts/install_local.sh | 4 +- Scripts/publish_release.sh | 36 ++-- Scripts/release.sh | 159 +++++++++++------- project.yml | 4 +- web/index.html | 35 +++- web/styles.css | 8 +- 16 files changed, 319 insertions(+), 172 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 497ac61..579f94c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ 记录 OpenStats 应用的功能新增、样式调整和问题修复,按版本与日期倒序排列。 官网、构建与发布脚本等不影响应用本身的改动不在此记录。 +## 0.3.1 · 2026-09-15 + +### 2026-09-15 + +#### 新增 + +- 支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。 +- 在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。 + ## 0.3.0 · 2026-09-15 ### 2026-09-15 diff --git a/Makefile b/Makefile index 88965a2..54e4fea 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,9 @@ PROJECT := OpenStats.xcodeproj SCHEME := OpenStats # 每次构建都会安装到 /Applications,默认用 Release CONFIG ?= Release -DERIVED := build/DerivedData +# ARCH=arm64 或 ARCH=x86_64 只编译一种芯片(发布脚本分别打 Apple 芯片版与 Intel 版);不设时只编译本机芯片 +ARCH ?= +DERIVED := build/DerivedData$(if $(ARCH),-$(ARCH)) APP := $(DERIVED)/Build/Products/$(CONFIG)/OpenStats.app LSREGISTER := /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister @@ -31,7 +33,8 @@ build: compile: generate xcodebuild -project $(PROJECT) -scheme $(SCHEME) -configuration $(CONFIG) \ - -derivedDataPath $(DERIVED) -destination 'platform=macOS' -quiet $(SIGN_FLAGS) build + -derivedDataPath $(DERIVED) $(if $(ARCH),-destination 'generic/platform=macOS' ARCHS=$(ARCH) ONLY_ACTIVE_ARCH=NO,-destination 'platform=macOS') \ + -quiet $(SIGN_FLAGS) build ## 与 build 相同,保留旧名字 run install: build diff --git a/Packages/OpenStatsKit/Sources/Localization/Translations.swift b/Packages/OpenStatsKit/Sources/Localization/Translations.swift index d498c57..296d67e 100644 --- a/Packages/OpenStatsKit/Sources/Localization/Translations.swift +++ b/Packages/OpenStatsKit/Sources/Localization/Translations.swift @@ -389,6 +389,7 @@ npm 缓存 npm Cache 安全温度 Safety Temperature 安装 Install 安装包 Installers +安装包不支持这台 Mac 的芯片 This update doesn't support this Mac's chip 安装包内容不正确:{} The update package is invalid: {} 安装包校验值与版本清单不一致,已放弃安装 The update's checksum doesn't match the manifest; installation stopped 安装失败:{} Install failed: {} diff --git a/Packages/OpenStatsKit/Sources/OpenStatsUI/State/UpdateController.swift b/Packages/OpenStatsKit/Sources/OpenStatsUI/State/UpdateController.swift index 6b30476..1644c08 100644 --- a/Packages/OpenStatsKit/Sources/OpenStatsUI/State/UpdateController.swift +++ b/Packages/OpenStatsKit/Sources/OpenStatsUI/State/UpdateController.swift @@ -79,10 +79,12 @@ public final class UpdateController { task = Task { let result = await Self.fetch() switch result { - case .success(let latest): + case .success(let feed): // 只有拿到清单才算检查过:登录时网络常常还没连上,失败后由每小时的定时器重试,而不是等一整天 lastChecked = Date() - guard UpdateFeed.isNewer(latest.version, than: currentVersion) else { + // 清单里没有这台 Mac 芯片的安装包时当作没有新版本 + guard let latest = UpdateFeed.release(feed, for: .current), + UpdateFeed.isNewer(latest.version, than: currentVersion) else { release = nil phase = .upToDate return diff --git a/Packages/OpenStatsKit/Sources/Updates/UpdateFeed.swift b/Packages/OpenStatsKit/Sources/Updates/UpdateFeed.swift index 0e7e06f..af79a96 100644 --- a/Packages/OpenStatsKit/Sources/Updates/UpdateFeed.swift +++ b/Packages/OpenStatsKit/Sources/Updates/UpdateFeed.swift @@ -15,9 +15,11 @@ public struct UpdateRelease: Codable, Sendable, Equatable { /// 最近更新的摘要,每条一句 public let notes: [String] public let changelog: URL? + /// Intel 版安装包。顶层的 url / sha256 / size / dmg 是 Apple 芯片版:0.3.0 只有 Apple 芯片版且只认顶层字段 + public let intel: UpdateAsset? public init(version: String, build: String, date: String, minimumSystem: String, url: URL, sha256: String, - size: Int64, dmg: URL?, notes: [String], changelog: URL?) { + size: Int64, dmg: URL?, notes: [String], changelog: URL?, intel: UpdateAsset? = nil) { self.version = version self.build = build self.date = date @@ -28,6 +30,47 @@ public struct UpdateRelease: Codable, Sendable, Equatable { self.dmg = dmg self.notes = notes self.changelog = changelog + self.intel = intel + } +} + +public struct UpdateAsset: Codable, Sendable, Equatable { + public let url: URL + public let sha256: String + public let size: Int64 + public let dmg: URL? + + public init(url: URL, sha256: String, size: Int64, dmg: URL?) { + self.url = url + self.sha256 = sha256 + self.size = size + self.dmg = dmg + } +} + +/// 安装包对应的芯片 +public enum UpdateArchitecture: Sendable, Equatable { + case appleSilicon + case intel + + /// 这台 Mac 的芯片。Intel 版在 Apple 芯片上经 Rosetta 运行时也算 Apple 芯片,升级时顺便换成原生版本 + public static var current: UpdateArchitecture { + #if arch(arm64) + return .appleSilicon + #else + var translated: Int32 = 0 + var size = MemoryLayout.size + guard sysctlbyname("sysctl.proc_translated", &translated, &size, nil, 0) == 0 else { return .intel } + return translated == 1 ? .appleSilicon : .intel + #endif + } + + /// Bundle.executableArchitectures 里对应的值 + var executableArchitecture: Int { + switch self { + case .appleSilicon: NSBundleExecutableArchitectureARM64 + case .intel: NSBundleExecutableArchitectureX86_64 + } } } @@ -41,6 +84,20 @@ public enum UpdateFeed { return release } + /// 换成这种芯片要下载的安装包;清单里没有这种芯片的安装包时返回 nil,不能拿另一种芯片的包来装 + public static func release(_ release: UpdateRelease, for architecture: UpdateArchitecture) -> UpdateRelease? { + switch architecture { + case .appleSilicon: + return release + case .intel: + guard let intel = release.intel, intel.sha256.count == 64, intel.url.scheme == "https" else { return nil } + return UpdateRelease(version: release.version, build: release.build, date: release.date, + minimumSystem: release.minimumSystem, url: intel.url, sha256: intel.sha256, + size: intel.size, dmg: intel.dmg, notes: release.notes, changelog: release.changelog, + intel: intel) + } + } + /// 按数字逐段比较版本号:“0.10.0” 比 “0.9.3” 新,“1.0” 与 “1.0.0” 相同 public static func isNewer(_ candidate: String, than current: String) -> Bool { let lhs = components(candidate), rhs = components(current) diff --git a/Packages/OpenStatsKit/Sources/Updates/UpdateInstaller.swift b/Packages/OpenStatsKit/Sources/Updates/UpdateInstaller.swift index a360563..87872cd 100644 --- a/Packages/OpenStatsKit/Sources/Updates/UpdateInstaller.swift +++ b/Packages/OpenStatsKit/Sources/Updates/UpdateInstaller.swift @@ -64,7 +64,8 @@ public enum UpdateInstaller { // MARK: 校验 /// 包名、版本号、签名团队必须与预期一致,并通过严格签名校验与 Gatekeeper(公证)检查 - public static func verify(_ app: URL, bundleIdentifier: String, version: String, teamIdentifier: String) throws { + public static func verify(_ app: URL, bundleIdentifier: String, version: String, teamIdentifier: String, + architecture: UpdateArchitecture = .current) throws { guard let bundle = Bundle(url: app) else { throw UpdateError.invalidBundle(tr("无法读取")) } guard bundle.bundleIdentifier == bundleIdentifier else { throw UpdateError.invalidBundle(tr("包名是 \(bundle.bundleIdentifier ?? tr("空"))")) @@ -73,6 +74,10 @@ public enum UpdateInstaller { guard bundleVersion == version else { throw UpdateError.invalidBundle(tr("版本是 \(bundleVersion ?? tr("空")),清单写的是 \(version)")) } + // Apple 芯片版与 Intel 版分开发布,装错了应用根本打不开 + guard bundle.executableArchitectures?.contains(where: { $0.intValue == architecture.executableArchitecture }) == true else { + throw UpdateError.invalidBundle(tr("安装包不支持这台 Mac 的芯片")) + } var staticCode: SecStaticCode? guard SecStaticCodeCreateWithPath(app as CFURL, [], &staticCode) == errSecSuccess, let code = staticCode else { diff --git a/Packages/OpenStatsKit/Tests/UpdatesTests/UpdateFeedTests.swift b/Packages/OpenStatsKit/Tests/UpdatesTests/UpdateFeedTests.swift index d62a724..af889d5 100644 --- a/Packages/OpenStatsKit/Tests/UpdatesTests/UpdateFeedTests.swift +++ b/Packages/OpenStatsKit/Tests/UpdatesTests/UpdateFeedTests.swift @@ -27,6 +27,21 @@ import Testing let badHash = good.replacingOccurrences(of: String(repeating: "a", count: 64), with: "abc") #expect(UpdateFeed.parse(Data(badHash.utf8)) == nil) } + + @Test func picksInstallerForChip() throws { + let hash = String(repeating: "a", count: 64), intelHash = String(repeating: "b", count: 64) + let base = #"{"version":"0.3.1","build":"51","date":"2026-09-16","minimumSystem":"14.0","url":"https://getopenstats.com/download/OpenStats-0.3.1-AppleSilicon.zip","sha256":"\#(hash)","size":1,"dmg":null,"notes":[],"changelog":null"# + // 0.3.0 那样只有 Apple 芯片版的清单:Intel 上不提供升级 + let appleOnly = try #require(UpdateFeed.parse(Data((base + "}").utf8))) + #expect(UpdateFeed.release(appleOnly, for: .appleSilicon) == appleOnly) + #expect(UpdateFeed.release(appleOnly, for: .intel) == nil) + + let both = try #require(UpdateFeed.parse(Data((base + #","intel":{"url":"https://getopenstats.com/download/OpenStats-0.3.1-Intel.zip","sha256":"\#(intelHash)","size":2,"dmg":"https://getopenstats.com/download/OpenStats-0.3.1-Intel.dmg"}}"#).utf8))) + #expect(UpdateFeed.release(both, for: .appleSilicon)?.sha256 == hash) + let intel = try #require(UpdateFeed.release(both, for: .intel)) + #expect(intel.url.lastPathComponent == "OpenStats-0.3.1-Intel.zip") + #expect(intel.sha256 == intelHash && intel.size == 2 && intel.version == "0.3.1") + } } @Suite struct UpdateInstallerTests { diff --git a/README.md b/README.md index d23835d..f2bf630 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ **Your Mac at a glance — CPU, GPU, memory, network and temperatures in the menu bar, with fan control, keep-awake, one-click cleanup, an app uninstaller and an IP cleanliness check.** -[![Release](https://img.shields.io/badge/release-0.3.0-6ee02b)](https://getopenstats.com/#download) +[![Release](https://img.shields.io/badge/release-0.3.1-6ee02b)](https://getopenstats.com/#download) [![Stars](https://img.shields.io/github/stars/gentpan/OpenStats?style=flat&color=f5c518&label=stars)](https://github.com/gentpan/OpenStats/stargazers) [![Last commit](https://img.shields.io/github/last-commit/gentpan/OpenStats?color=black&label=last%20commit)](https://github.com/gentpan/OpenStats/commits/main) [![Commit activity](https://img.shields.io/github/commit-activity/m/gentpan/OpenStats?color=black&label=commits)](https://github.com/gentpan/OpenStats/graphs/commit-activity) [![CI](https://github.com/gentpan/OpenStats/actions/workflows/ci.yml/badge.svg)](https://github.com/gentpan/OpenStats/actions/workflows/ci.yml) -[![macOS](https://img.shields.io/badge/macOS-14%2B-black)](https://getopenstats.com/#download) +[![macOS](https://img.shields.io/badge/macOS-14%2B%20%C2%B7%20Apple%20silicon%20%2F%20Intel-black)](https://getopenstats.com/#download) [![License](https://img.shields.io/badge/license-MIT-black)](LICENSE) OpenStats is a macOS menu-bar app that shows what your Mac is doing right now — per-core @@ -34,15 +34,24 @@ Everything is read on your own Mac, and there is no telemetry. An account is opt ## Install -Download [OpenStats 0.3.0](https://getopenstats.com/download/OpenStats-0.3.0.dmg) from the website -(signed with a Developer ID certificate and notarized by Apple), or install it with Homebrew: +Download OpenStats 0.3.1 from the website (signed with a Developer ID certificate and notarized by Apple) — +pick the build for your Mac's chip: + +| Chip | Download | +|---|---| +| Apple silicon (M1, M2, M3, M4, M5) | [OpenStats-0.3.1-AppleSilicon.dmg](https://getopenstats.com/download/OpenStats-0.3.1-AppleSilicon.dmg) | +| Intel | [OpenStats-0.3.1-Intel.dmg](https://getopenstats.com/download/OpenStats-0.3.1-Intel.dmg) | + +Not sure? Apple menu › About This Mac says "Chip Apple M…" on Apple silicon and "Processor Intel…" on Intel. +Homebrew picks the right build for you: ```bash brew install --cask gentpan/tap/openstats ``` -Requires macOS 14 (Sonoma) or later. -Developed and tested on Apple Silicon. The interface is in English and Simplified Chinese, following the +Requires macOS 14 (Sonoma) or later. Developed and tested mainly on Apple silicon; on Intel Macs, Apple +Intelligence process explanations are unavailable, CPU cores are not split into performance and efficiency +groups, and some power and frequency readings may be missing. The interface is in English and Simplified Chinese, following the system language by default; switch it in Settings → General → Language. ## Recent updates @@ -50,9 +59,19 @@ system language by default; switch it in Settings → General → Language. -Latest release **0.3.0** (2026-09-15) · [full changelog](CHANGELOG.md) (kept in Chinese) +Latest release **0.3.1** (2026-09-15) · [full changelog](CHANGELOG.md) (kept in Chinese)
+2026-09-15 · 0.3.1 · 2 added + +**Added** + +- 支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。 +- 在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。 + +
+ +
2026-09-15 · 0.3.0 · 8 added · 14 changed · 3 fixed **Added** @@ -110,34 +129,6 @@ Latest release **0.3.0** (2026-09-15) · [full changelog](CHANGELOG.md) (kept in
-
-2026-09-13 · 0.3.0 · 13 added · 4 changed - -**Added** - -- 在线升级:启动时和之后每天检查官网版本清单,发现新版本时弹出版本号与更新摘要,可一键安装、以后再说或跳过此版本;下载后依次核对 sha256、包名与版本、开发者签名团队和 Apple 公证,任何一项不通过都放弃安装;旧版先备份再原地替换,失败自动还原,装完自动重启到新版。「设置 · 关于」与应用菜单里可以手动检查更新。 -- 辅助工具版本检查:启动时核对正在运行的辅助工具版本;旧进程还在运行时先断开让它退出,由系统启动新版本,仍然旧时在风扇、合盖运行和辅助工具设置里提示“重新安装”,侧边栏对应项目显示提醒圆点;有新版本时“关于”旁同样显示圆点。 -- 导出诊断信息(设置 · 关于):把版本、系统与辅助工具状态、主要设置、最近 3 天的运行日志、清理记录和最近的崩溃报告打包成 zip,反馈问题时附上;自动去掉用户目录名、IP 与硬件地址,不含序列号。应用与辅助工具的关键操作和错误写入系统日志。 -- 系统通知(设置 · 通知):CPU 过热(持续 1 分钟高于设定温度)、内存压力严重(持续 30 秒)、磁盘空间不足(低于 10% 或 10 GB)、网络断开超过 20 秒(恢复后再提示)、电池最大容量低于 80%,逐项开关,默认关闭;同一状况持续期间只提醒一次并有冷却时间,点通知打开对应页面;可发送测试通知,通知被关闭时提示去系统设置打开。 -- 功耗与频率:“温度与风扇”页和温度详情新增功耗,显示整机功耗走势、电源输入、电池充放电功率和 GPU 功耗(读 SMC 与系统能耗统计,不需要辅助工具);CPU 详情的核心分工显示各类核心工作时的平均频率。读取能耗统计有一定开销,只在这些界面打开时每 2 秒读一次。 -- 磁盘页(主窗口侧边栏“磁盘”):启动磁盘容量、所有磁盘合计的实时读写速度与 60 秒走势、SSD 健康(通过系统自带的 NVMe SMART 接口读取剩余寿命、累计读写量、备用空间、温度、通电时间与次数、异常断电和介质错误,出现警告时提示备份)、读写磁盘最多的应用;磁盘空间不足的通知改为打开磁盘页。 -- 卸载应用(主窗口工具分组):列出“应用程序”里的第三方应用及其体积,选中或把应用拖进来后找出它留下的应用数据、缓存、偏好设置、沙盒容器、窗口状态、日志、网页数据和登录启动项,可逐项取消勾选;确认后连同应用一起移到废纸篓(可放回),并从程序坞移除图标。只匹配应用包名与同名目录,系统自带和 Apple 的应用不列出,正在运行时提示先退出。 -- 启动项(主窗口工具分组):列出当前用户、所有用户的 LaunchAgents 和系统 LaunchDaemons,显示所属应用、可执行文件、是否登录时运行 / 保持运行,以及运行中(PID)、已加载、已停用状态;当前用户的启动项可以直接停用或重新启用(写入 launchd 停用记录并卸载,不删除文件),其余只读并提供登录项设置入口。 -- 历史(主窗口监控分组):每分钟把 CPU 平均与峰值、内存与内存压力、网络上下行、GPU、CPU 最高温度和整机功耗写入本机 SQLite 数据库,保留 7 天;可回看最近 1 小时 / 24 小时 / 7 天,睡眠等没有记录的时段线条断开,内存压力严重的时段标红,鼠标移到图上查看那一刻的数值;可关闭记录或清除历史,数据不上传。 -- 蓝牙设备电量:本机信息页列出已连接的键盘、鼠标、触控板和耳机的电量(AirPods 分左耳、右耳、充电盒),低于 20% 标红;通知里新增“蓝牙设备电量低”(低于 15%)。 -- 全局快捷键(设置 · 通用):可为显示 / 隐藏主窗口、打开进程页、开关防休眠、释放内存录制快捷键,在任何应用中都能用,不需要辅助功能权限;被其他应用占用时提示更换。 -- 桌面小组件“系统概览”:小号显示 CPU、内存圆环与磁盘用量,中号显示 CPU、内存、磁盘、电池四个圆环和磁盘可用空间;在自己的沙盒进程里读取数据,主应用没开也能显示,刷新间隔由系统决定(几分钟一次)。 -- 英文界面(设置 · 通用 · 语言):跟随系统、简体中文、English 三选一,系统首选语言不是中文时默认显示英文;约 880 条界面文案、通知、错误提示和 Apple 智能解释的回答都有英文版本,日期按英文格式显示;切换后界面、菜单与菜单栏立即换成新语言,显示器、应用名称等由系统提供的文字在下次启动时切换。 - -**Changed** - -- 连接探测更省电:打开网络详情时按设置的间隔探测;详情关闭、只在菜单栏显示网速时改为每 10 秒低频探测,并允许系统合并唤醒,也可以在设置里关闭后台探测。 -- 进程页每 2 秒刷新一次(读取全系统进程需要启动 ps),其他页面仍为每秒。 -- 菜单栏图标用 ⌘ 拖动调整的顺序会被记住,重启应用或切换显示项目后保持不变。 -- 本机信息的电池卡片提供“电池设置”入口,用 macOS 自带的充电上限(80%–100%);新款机型不再开放第三方写入 SMC 充电控制键,因此不另做一套充电限制。 - -
- ## Activity diff --git a/README.zh-CN.md b/README.zh-CN.md index 9308db5..0ba2675 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,12 +6,12 @@ **Mac 的状态,抬眼就看见——CPU、GPU、内存、网络与温度常驻菜单栏,还能调风扇、防休眠、一键清理、卸载应用,检测 IP 纯净度。** -[![Release](https://img.shields.io/badge/%E7%89%88%E6%9C%AC-0.3.0-6ee02b)](https://getopenstats.com/#download) +[![Release](https://img.shields.io/badge/%E7%89%88%E6%9C%AC-0.3.1-6ee02b)](https://getopenstats.com/#download) [![Stars](https://img.shields.io/github/stars/gentpan/OpenStats?style=flat&color=f5c518&label=%E6%98%9F%E6%A0%87)](https://github.com/gentpan/OpenStats/stargazers) [![Last commit](https://img.shields.io/github/last-commit/gentpan/OpenStats?color=black&label=%E6%9C%80%E8%BF%91%E6%8F%90%E4%BA%A4)](https://github.com/gentpan/OpenStats/commits/main) [![Commit activity](https://img.shields.io/github/commit-activity/m/gentpan/OpenStats?color=black&label=%E6%8F%90%E4%BA%A4)](https://github.com/gentpan/OpenStats/graphs/commit-activity) [![CI](https://github.com/gentpan/OpenStats/actions/workflows/ci.yml/badge.svg)](https://github.com/gentpan/OpenStats/actions/workflows/ci.yml) -[![macOS](https://img.shields.io/badge/macOS-14%2B-black)](https://getopenstats.com/#download) +[![macOS](https://img.shields.io/badge/macOS-14%2B%20%C2%B7%20Apple%20%E8%8A%AF%E7%89%87%20%2F%20Intel-black)](https://getopenstats.com/#download) [![License](https://img.shields.io/badge/license-MIT-black)](LICENSE) OpenStats 是一款 macOS 菜单栏应用,实时显示 Mac 正在做什么:各核心 CPU 负载、GPU、内存压力、 @@ -32,14 +32,22 @@ OpenStats 是一款 macOS 菜单栏应用,实时显示 Mac 正在做什么: ## 安装 -从官网下载 [OpenStats 0.3.0](https://getopenstats.com/download/OpenStats-0.3.0.dmg)(Developer ID 签名、经过 Apple 公证), -或者用 Homebrew 安装: +从官网下载 OpenStats 0.3.1(Developer ID 签名、经过 Apple 公证),按 Mac 的芯片选择: + +| 芯片 | 下载 | +|---|---| +| Apple 芯片(M1、M2、M3、M4、M5 系列) | [OpenStats-0.3.1-AppleSilicon.dmg](https://getopenstats.com/download/OpenStats-0.3.1-AppleSilicon.dmg) | +| Intel 芯片 | [OpenStats-0.3.1-Intel.dmg](https://getopenstats.com/download/OpenStats-0.3.1-Intel.dmg) | + +不确定是哪种,看苹果菜单 › 关于本机:写着“芯片 Apple M…”是 Apple 芯片,写着“处理器 Intel…”是 Intel。 +也可以用 Homebrew 安装,会自动选对版本: ```bash brew install --cask gentpan/tap/openstats ``` -需要 macOS 14(Sonoma)或更高版本。在 Apple Silicon 上开发和测试。 +需要 macOS 14(Sonoma)或更高版本。主要在 Apple 芯片上开发和测试;Intel 机型上不能用 Apple 智能解释进程, +CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。 界面支持简体中文与 English,默认跟随系统语言,可在“设置 · 通用 · 语言”中切换。 ## 最近更新 @@ -47,9 +55,19 @@ brew install --cask gentpan/tap/openstats -最新版本 **0.3.0**(2026-09-15) · [完整更新日志](CHANGELOG.md) +最新版本 **0.3.1**(2026-09-15) · [完整更新日志](CHANGELOG.md)
+2026-09-15 · 0.3.1 · 新增 2 + +**新增** + +- 支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。 +- 在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。 + +
+ +
2026-09-15 · 0.3.0 · 新增 8 · 调整 14 · 修复 3 **新增** @@ -107,34 +125,6 @@ brew install --cask gentpan/tap/openstats
-
-2026-09-13 · 0.3.0 · 新增 13 · 调整 4 - -**新增** - -- 在线升级:启动时和之后每天检查官网版本清单,发现新版本时弹出版本号与更新摘要,可一键安装、以后再说或跳过此版本;下载后依次核对 sha256、包名与版本、开发者签名团队和 Apple 公证,任何一项不通过都放弃安装;旧版先备份再原地替换,失败自动还原,装完自动重启到新版。「设置 · 关于」与应用菜单里可以手动检查更新。 -- 辅助工具版本检查:启动时核对正在运行的辅助工具版本;旧进程还在运行时先断开让它退出,由系统启动新版本,仍然旧时在风扇、合盖运行和辅助工具设置里提示“重新安装”,侧边栏对应项目显示提醒圆点;有新版本时“关于”旁同样显示圆点。 -- 导出诊断信息(设置 · 关于):把版本、系统与辅助工具状态、主要设置、最近 3 天的运行日志、清理记录和最近的崩溃报告打包成 zip,反馈问题时附上;自动去掉用户目录名、IP 与硬件地址,不含序列号。应用与辅助工具的关键操作和错误写入系统日志。 -- 系统通知(设置 · 通知):CPU 过热(持续 1 分钟高于设定温度)、内存压力严重(持续 30 秒)、磁盘空间不足(低于 10% 或 10 GB)、网络断开超过 20 秒(恢复后再提示)、电池最大容量低于 80%,逐项开关,默认关闭;同一状况持续期间只提醒一次并有冷却时间,点通知打开对应页面;可发送测试通知,通知被关闭时提示去系统设置打开。 -- 功耗与频率:“温度与风扇”页和温度详情新增功耗,显示整机功耗走势、电源输入、电池充放电功率和 GPU 功耗(读 SMC 与系统能耗统计,不需要辅助工具);CPU 详情的核心分工显示各类核心工作时的平均频率。读取能耗统计有一定开销,只在这些界面打开时每 2 秒读一次。 -- 磁盘页(主窗口侧边栏“磁盘”):启动磁盘容量、所有磁盘合计的实时读写速度与 60 秒走势、SSD 健康(通过系统自带的 NVMe SMART 接口读取剩余寿命、累计读写量、备用空间、温度、通电时间与次数、异常断电和介质错误,出现警告时提示备份)、读写磁盘最多的应用;磁盘空间不足的通知改为打开磁盘页。 -- 卸载应用(主窗口工具分组):列出“应用程序”里的第三方应用及其体积,选中或把应用拖进来后找出它留下的应用数据、缓存、偏好设置、沙盒容器、窗口状态、日志、网页数据和登录启动项,可逐项取消勾选;确认后连同应用一起移到废纸篓(可放回),并从程序坞移除图标。只匹配应用包名与同名目录,系统自带和 Apple 的应用不列出,正在运行时提示先退出。 -- 启动项(主窗口工具分组):列出当前用户、所有用户的 LaunchAgents 和系统 LaunchDaemons,显示所属应用、可执行文件、是否登录时运行 / 保持运行,以及运行中(PID)、已加载、已停用状态;当前用户的启动项可以直接停用或重新启用(写入 launchd 停用记录并卸载,不删除文件),其余只读并提供登录项设置入口。 -- 历史(主窗口监控分组):每分钟把 CPU 平均与峰值、内存与内存压力、网络上下行、GPU、CPU 最高温度和整机功耗写入本机 SQLite 数据库,保留 7 天;可回看最近 1 小时 / 24 小时 / 7 天,睡眠等没有记录的时段线条断开,内存压力严重的时段标红,鼠标移到图上查看那一刻的数值;可关闭记录或清除历史,数据不上传。 -- 蓝牙设备电量:本机信息页列出已连接的键盘、鼠标、触控板和耳机的电量(AirPods 分左耳、右耳、充电盒),低于 20% 标红;通知里新增“蓝牙设备电量低”(低于 15%)。 -- 全局快捷键(设置 · 通用):可为显示 / 隐藏主窗口、打开进程页、开关防休眠、释放内存录制快捷键,在任何应用中都能用,不需要辅助功能权限;被其他应用占用时提示更换。 -- 桌面小组件“系统概览”:小号显示 CPU、内存圆环与磁盘用量,中号显示 CPU、内存、磁盘、电池四个圆环和磁盘可用空间;在自己的沙盒进程里读取数据,主应用没开也能显示,刷新间隔由系统决定(几分钟一次)。 -- 英文界面(设置 · 通用 · 语言):跟随系统、简体中文、English 三选一,系统首选语言不是中文时默认显示英文;约 880 条界面文案、通知、错误提示和 Apple 智能解释的回答都有英文版本,日期按英文格式显示;切换后界面、菜单与菜单栏立即换成新语言,显示器、应用名称等由系统提供的文字在下次启动时切换。 - -**调整** - -- 连接探测更省电:打开网络详情时按设置的间隔探测;详情关闭、只在菜单栏显示网速时改为每 10 秒低频探测,并允许系统合并唤醒,也可以在设置里关闭后台探测。 -- 进程页每 2 秒刷新一次(读取全系统进程需要启动 ps),其他页面仍为每秒。 -- 菜单栏图标用 ⌘ 拖动调整的顺序会被记住,重启应用或切换显示项目后保持不变。 -- 本机信息的电池卡片提供“电池设置”入口,用 macOS 自带的充电上限(80%–100%);新款机型不再开放第三方写入 SMC 充电控制键,因此不另做一套充电限制。 - -
- ## 活跃度 diff --git a/Scripts/appcast.py b/Scripts/appcast.py index 1bc0c14..864dee9 100755 --- a/Scripts/appcast.py +++ b/Scripts/appcast.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 """生成应用内在线升级读取的版本清单 appcast.json。 - Scripts/appcast.py <版本> <下载地址前缀> > dist/appcast.json + Scripts/appcast.py <版本> <下载地址前缀> > dist/appcast.json + +顶层的 url / sha256 / size / dmg 是 Apple 芯片版:0.3.0 只有 Apple 芯片版,也只认顶层字段。 +Intel 版放在 intel 字段里,0.3.1 起的 Intel 版应用读它。 更新摘要取自 CHANGELOG.md 中该版本的条目:每条取冒号或句号之前的部分(过短时带上冒号后的内容),最多 10 条。 """ @@ -50,8 +53,17 @@ def sha256(path: str) -> str: return digest.hexdigest() +def asset(base: str, zip_path: str, dmg_path: str) -> dict: + return { + "url": f"{base}/{os.path.basename(zip_path)}", + "sha256": sha256(zip_path), + "size": os.path.getsize(zip_path), + "dmg": f"{base}/{os.path.basename(dmg_path)}", + } + + def main() -> None: - version, build, zip_path, dmg_path, base = sys.argv[1:6] + version, build, base, arm_zip, arm_dmg, intel_zip, intel_dmg = sys.argv[1:8] root = os.path.join(os.path.dirname(__file__), "..") with open(os.path.join(root, "CHANGELOG.md"), encoding="utf-8") as handle: date, notes = release_notes(handle.read(), version) @@ -60,12 +72,10 @@ def main() -> None: "build": build, "date": date, "minimumSystem": "14.0", - "url": f"{base}/{os.path.basename(zip_path)}", - "sha256": sha256(zip_path), - "size": os.path.getsize(zip_path), - "dmg": f"{base}/{os.path.basename(dmg_path)}", + **asset(base, arm_zip, arm_dmg), "notes": notes, "changelog": "https://getopenstats.com/#changelog", + "intel": asset(base, intel_zip, intel_dmg), } json.dump(feed, sys.stdout, ensure_ascii=False, indent=2) sys.stdout.write("\n") diff --git a/Scripts/install_local.sh b/Scripts/install_local.sh index 54d885a..cf9de6f 100755 --- a/Scripts/install_local.sh +++ b/Scripts/install_local.sh @@ -28,8 +28,8 @@ fi mv "$SOURCE" "$TARGET" "$LSREGISTER" -f "$TARGET" -# 编译目录里残留的旧产物(Debug / Release 另一种配置)也清掉 -for stale in build/DerivedData/Build/Products/*/OpenStats.app; do +# 编译目录里残留的旧产物(Debug / Release 另一种配置、发布时另一种芯片的版本)也清掉 +for stale in build/DerivedData*/Build/Products/*/OpenStats.app; do [ -d "$stale" ] || continue "$LSREGISTER" -u "$stale" >/dev/null 2>&1 || true rm -rf "$stale" diff --git a/Scripts/publish_release.sh b/Scripts/publish_release.sh index acfde68..e715134 100755 --- a/Scripts/publish_release.sh +++ b/Scripts/publish_release.sh @@ -1,5 +1,5 @@ #!/bin/bash -# 发布已公证的版本:DMG 与在线升级包上传到官网 /download/,最后更新版本清单 appcast.json(已安装的应用据此提示升级), +# 发布已公证的版本:Apple 芯片版与 Intel 版的 DMG、在线升级包上传到官网 /download/,最后更新版本清单 appcast.json(已安装的应用据此提示升级), # cask 提交到 gentpan/homebrew-tap,并校验线上文件。 # # ./Scripts/release.sh # 先打包、公证 @@ -12,25 +12,35 @@ KEY="${SITE_KEY:-$HOME/.ssh/gentpan.pem}" ROOT="${SITE_ROOT:-/var/www/getopenstats.com}" TAP="${TAP:-gentpan/homebrew-tap}" VERSION="$(sed -nE 's/^ *MARKETING_VERSION: *"?([0-9.]+)"?.*/\1/p' project.yml | head -1)" -DMG="dist/OpenStats-${VERSION}.dmg" -ZIP="dist/OpenStats-${VERSION}.zip" +CHIPS=(AppleSilicon Intel) APPCAST="dist/appcast.json" CASK="dist/openstats.rb" SSH=(ssh -i "$KEY" -o BatchMode=yes "$HOST") -for file in "$DMG" "$ZIP" "$APPCAST" "$CASK"; do +for chip in "${CHIPS[@]}"; do + for ext in dmg zip; do + file="dist/OpenStats-${VERSION}-${chip}.${ext}" + [ -f "$file" ] || { echo "缺少 $file,先运行 ./Scripts/release.sh" >&2; exit 1; } + done +done +for file in "$APPCAST" "$CASK"; do [ -f "$file" ] || { echo "缺少 $file,先运行 ./Scripts/release.sh" >&2; exit 1; } done -python3 - "$APPCAST" "$VERSION" "$ZIP" <<'PY' || { echo "$APPCAST 与 $ZIP 不一致" >&2; exit 1; } +python3 - "$APPCAST" "$VERSION" "dist/OpenStats-${VERSION}-AppleSilicon.zip" "dist/OpenStats-${VERSION}-Intel.zip" <<'PY' \ + || { echo "$APPCAST 与升级包不一致" >&2; exit 1; } import hashlib, json, sys feed = json.load(open(sys.argv[1])) -digest = hashlib.sha256(open(sys.argv[3], "rb").read()).hexdigest() -assert feed["version"] == sys.argv[2] and feed["sha256"] == digest and feed["notes"], feed +digest = lambda path: hashlib.sha256(open(path, "rb").read()).hexdigest() +assert feed["version"] == sys.argv[2] and feed["notes"], feed +assert feed["sha256"] == digest(sys.argv[3]) and feed["url"].endswith("-AppleSilicon.zip"), feed +assert feed["intel"]["sha256"] == digest(sys.argv[4]) and feed["intel"]["url"].endswith("-Intel.zip"), feed PY grep -q "version \"${VERSION}\"" "$CASK" || { echo "$CASK 的版本不是 ${VERSION}" >&2; exit 1; } -xcrun stapler validate "$DMG" >/dev/null || { echo "$DMG 没有装订公证票据" >&2; exit 1; } -SHA="$(shasum -a 256 "$DMG" | cut -d' ' -f1)" -grep -q "$SHA" "$CASK" || { echo "$CASK 里的 sha256 与 DMG 不一致" >&2; exit 1; } +for chip in "${CHIPS[@]}"; do + dmg="dist/OpenStats-${VERSION}-${chip}.dmg" + xcrun stapler validate "$dmg" >/dev/null || { echo "$dmg 没有装订公证票据" >&2; exit 1; } + grep -q "$(shasum -a 256 "$dmg" | cut -d' ' -f1)" "$CASK" || { echo "$CASK 里的 sha256 与 $dmg 不一致" >&2; exit 1; } +done # 上传:先传临时名再改名,下载中途不会拿到半个文件 "${SSH[@]}" "sudo install -d -o \$(id -un) -m 755 $ROOT/download" @@ -44,8 +54,10 @@ upload() { [ "$online" = "$(shasum -a 256 "$1" | cut -d' ' -f1)" ] || { echo "线上 ${name} 校验不一致:$online" >&2; exit 1; } echo "✅ https://getopenstats.com/download/${name}" } -upload "$DMG" -upload "$ZIP" +for chip in "${CHIPS[@]}"; do + upload "dist/OpenStats-${VERSION}-${chip}.dmg" + upload "dist/OpenStats-${VERSION}-${chip}.zip" +done # 版本清单最后发布:安装包都已就位后,已安装的应用才会看到新版本 upload "$APPCAST" diff --git a/Scripts/release.sh b/Scripts/release.sh index a1d3897..8f22735 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -1,5 +1,6 @@ #!/bin/bash -# 发布一个可分发的版本:Developer ID 签名 → 公证 → 装订 → DMG(签名、公证、装订)→ 在线升级包与版本清单 → Homebrew cask。 +# 发布一个可分发的版本:Apple 芯片版与 Intel 版各自 Developer ID 签名 → 公证 → 装订 → DMG(签名、公证、装订) +# → 在线升级包 → 版本清单 → Homebrew cask。 # # ./Scripts/release.sh # @@ -12,16 +13,17 @@ set -euo pipefail cd "$(dirname "$0")/.." -# 安装包放在官网(仓库是私有的,GitHub Release 无法匿名下载) +# 安装包放在官网 DOWNLOAD_BASE="${DOWNLOAD_BASE:-https://getopenstats.com/download}" DIST="${DIST:-dist}" NOTARY_PROFILE="${NOTARY_PROFILE:-QuotaBar}" SIGN_ID="${SIGN_ID:-$(security find-identity -v -p codesigning 2>/dev/null \ | grep 'Developer ID Application' | head -1 | sed -E 's/.*"(.*)".*/\1/' || true)}" VERSION="$(sed -nE 's/^ *MARKETING_VERSION: *"?([0-9.]+)"?.*/\1/p' project.yml | head -1)" -APP="build/DerivedData/Build/Products/Release/OpenStats.app" -DMG_NAME="OpenStats-${VERSION}.dmg" -ZIP_NAME="OpenStats-${VERSION}.zip" +# 编译架构与文件名里的芯片名:OpenStats-0.3.1-AppleSilicon.dmg、OpenStats-0.3.1-Intel.dmg +ARCHS=(arm64 x86_64) +chip() { [ "$1" = arm64 ] && echo AppleSilicon || echo Intel; } +app_for() { echo "build/DerivedData-$1/Build/Products/Release/OpenStats.app"; } # 在线升级的更新摘要取自该版本的更新日志:发版前把 “## 未发布” 改成 “## 版本 · 日期” grep -q "^## ${VERSION} · " CHANGELOG.md \ @@ -34,78 +36,102 @@ fi TEAM_ID="$(echo "$SIGN_ID" | sed -nE 's/.*\(([A-Z0-9]+)\)$/\1/p')" echo "版本 ${VERSION} · 签名身份:${SIGN_ID}" -# ---- 构建 -------------------------------------------------------------------- - -make build CONFIG=Release INSTALL=0 SIGN_ID="$SIGN_ID" -# make build 会把构建号加一,构建完再读,版本清单里的 build 才与安装包一致 -BUILD="$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: *"?([0-9]+)"?.*/\1/p' project.yml | head -1)" - -codesign --verify --deep --strict --verbose=2 "$APP" -for binary in "$APP" "$APP/Contents/MacOS/OpenStatsHelper" "$APP/Contents/PlugIns/OpenStatsWidget.appex"; do - details="$(codesign -dvv "$binary" 2>&1)" - echo "$details" | grep -q "TeamIdentifier=${TEAM_ID}" \ - || { echo "error: $binary 未使用团队 ${TEAM_ID} 签名" >&2; exit 1; } - echo "$details" | grep -q "Timestamp=" \ - || { echo "error: $binary 缺少安全时间戳" >&2; exit 1; } - echo "$details" | grep -Eq "flags=.*runtime" \ - || { echo "error: $binary 未启用 Hardened Runtime" >&2; exit 1; } -done - notarize() { echo "提交公证:$(basename "$1")(通常需要几分钟)…" xcrun notarytool submit "$1" --keychain-profile "$NOTARY_PROFILE" --wait } -# ---- 公证 App ---------------------------------------------------------------- - WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT - -if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then - # ditto 而不是 zip:保留包内的符号链接与扩展属性 - ditto -c -k --keepParent "$APP" "$WORK/OpenStats.zip" - notarize "$WORK/OpenStats.zip" - xcrun stapler staple "$APP" - xcrun stapler validate "$APP" - # 拒绝发布 Gatekeeper 在用户机器上仍会拒绝的包 - if ! spctl -a -vv "$APP" 2>&1 | grep -q accepted; then - echo "error: Gatekeeper 仍然拒绝该 App,停止发布。" >&2 - spctl -a -vv "$APP" || true - exit 1 - fi -fi - -# ---- DMG --------------------------------------------------------------------- - rm -rf "$DIST" -mkdir -p "$DIST" "$WORK/dmg" -ditto "$APP" "$WORK/dmg/OpenStats.app" -ln -s /Applications "$WORK/dmg/Applications" -hdiutil create -volname "OpenStats ${VERSION}" -srcfolder "$WORK/dmg" -ov -format UDZO "$DIST/$DMG_NAME" >/dev/null -codesign --force --timestamp --sign "$SIGN_ID" "$DIST/$DMG_NAME" +mkdir -p "$DIST" -if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then - notarize "$DIST/$DMG_NAME" - xcrun stapler staple "$DIST/$DMG_NAME" - spctl -a -t open --context context:primary-signature -vv "$DIST/$DMG_NAME" -fi +# 两种芯片共用一个构建号:先加一次,各自构建时不再加 +./Scripts/version.sh build >/dev/null +BUILD="$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: *"?([0-9]+)"?.*/\1/p' project.yml | head -1)" -SHA256="$(shasum -a 256 "$DIST/$DMG_NAME" | cut -d' ' -f1)" +for arch in "${ARCHS[@]}"; do + APP="$(app_for "$arch")" + NAME="OpenStats-${VERSION}-$(chip "$arch")" + echo + echo "==== $(chip "$arch")(${arch})====" -# ---- 在线升级 ------------------------------------------------------------------ + # ---- 构建 ------------------------------------------------------------------ -# 应用内升级下载已装订票据的 .app 压缩包,版本清单里带 sha256 与更新摘要 -ditto -c -k --keepParent "$APP" "$DIST/$ZIP_NAME" -python3 Scripts/appcast.py "$VERSION" "$BUILD" "$DIST/$ZIP_NAME" "$DIST/$DMG_NAME" "$DOWNLOAD_BASE" > "$DIST/appcast.json" + rm -rf "$APP" + make build CONFIG=Release INSTALL=0 BUMP=0 ARCH="$arch" SIGN_ID="$SIGN_ID" + + for binary in "$APP/Contents/MacOS/OpenStats" "$APP/Contents/MacOS/OpenStatsHelper" \ + "$APP/Contents/PlugIns/OpenStatsWidget.appex/Contents/MacOS/OpenStatsWidget"; do + [ "$(lipo -archs "$binary")" = "$arch" ] \ + || { echo "error: $binary 的架构是 $(lipo -archs "$binary"),应当只有 $arch" >&2; exit 1; } + done + codesign --verify --deep --strict --verbose=2 "$APP" + for binary in "$APP" "$APP/Contents/MacOS/OpenStatsHelper" "$APP/Contents/PlugIns/OpenStatsWidget.appex"; do + details="$(codesign -dvv "$binary" 2>&1)" + echo "$details" | grep -q "TeamIdentifier=${TEAM_ID}" \ + || { echo "error: $binary 未使用团队 ${TEAM_ID} 签名" >&2; exit 1; } + echo "$details" | grep -q "Timestamp=" \ + || { echo "error: $binary 缺少安全时间戳" >&2; exit 1; } + echo "$details" | grep -Eq "flags=.*runtime" \ + || { echo "error: $binary 未启用 Hardened Runtime" >&2; exit 1; } + done + + # ---- 公证 App -------------------------------------------------------------- + + if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then + # ditto 而不是 zip:保留包内的符号链接与扩展属性 + ditto -c -k --keepParent "$APP" "$WORK/$NAME-notarize.zip" + notarize "$WORK/$NAME-notarize.zip" + xcrun stapler staple "$APP" + xcrun stapler validate "$APP" + # 拒绝发布 Gatekeeper 在用户机器上仍会拒绝的包(Intel 版的签名与公证票据同样能在本机校验) + if ! spctl -a -vv "$APP" 2>&1 | grep -q accepted; then + echo "error: Gatekeeper 仍然拒绝该 App,停止发布。" >&2 + spctl -a -vv "$APP" || true + exit 1 + fi + fi + + # ---- DMG ------------------------------------------------------------------- + + rm -rf "$WORK/dmg" + mkdir -p "$WORK/dmg" + ditto "$APP" "$WORK/dmg/OpenStats.app" + ln -s /Applications "$WORK/dmg/Applications" + hdiutil create -volname "OpenStats ${VERSION}" -srcfolder "$WORK/dmg" -ov -format UDZO "$DIST/$NAME.dmg" >/dev/null + codesign --force --timestamp --sign "$SIGN_ID" "$DIST/$NAME.dmg" + + if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then + notarize "$DIST/$NAME.dmg" + xcrun stapler staple "$DIST/$NAME.dmg" + spctl -a -t open --context context:primary-signature -vv "$DIST/$NAME.dmg" + fi + + # ---- 在线升级 ---------------------------------------------------------------- + + # 应用内升级下载已装订票据的 .app 压缩包 + ditto -c -k --keepParent "$APP" "$DIST/$NAME.zip" +done + +# 版本清单:顶层是 Apple 芯片版(0.3.0 只认顶层字段),intel 字段是 Intel 版 +python3 Scripts/appcast.py "$VERSION" "$BUILD" "$DOWNLOAD_BASE" \ + "$DIST/OpenStats-${VERSION}-AppleSilicon.zip" "$DIST/OpenStats-${VERSION}-AppleSilicon.dmg" \ + "$DIST/OpenStats-${VERSION}-Intel.zip" "$DIST/OpenStats-${VERSION}-Intel.dmg" > "$DIST/appcast.json" # ---- Homebrew cask ------------------------------------------------------------ +SHA_ARM="$(shasum -a 256 "$DIST/OpenStats-${VERSION}-AppleSilicon.dmg" | cut -d' ' -f1)" +SHA_INTEL="$(shasum -a 256 "$DIST/OpenStats-${VERSION}-Intel.dmg" | cut -d' ' -f1)" cat > "$DIST/openstats.rb" <下载 for Mac 更新日志 -

macOS 14 及以上 · Apple Silicon · 已签名并经 Apple 公证

+

macOS 14 及以上 · Apple 芯片与 Intel 芯片 · 已签名并经 Apple 公证

点右上角菜单栏里的 CPU、内存或网速,看看每一项的详情 ↗

@@ -337,11 +337,20 @@

下载 OpenStats

使用 Developer ID 签名并经过 Apple 公证,打开时不会遇到安全拦截。

- -

macOS 14 Sonoma 及以上 · 简体中文界面

+
    +
  • +

    Apple 芯片

    +

    M1、M2、M3、M4、M5 系列的 Mac

    + 下载 Apple 芯片版 +
  • +
  • +

    Intel 芯片

    +

    能升级到 macOS 14 的 Intel Mac(大多为 2018 年及以后的机型)

    + 下载 Intel 版 +
  • +
+

不确定是哪种?打开苹果菜单 › 关于本机,看“芯片”或“处理器”那一行。

+

版本 0.3.1 · macOS 14 Sonoma 及以上 · 简体中文与 English 界面 · 这个版本更新了什么

也可以用 Homebrew 安装:brew install --cask gentpan/tap/openstats

@@ -355,6 +364,14 @@
+
+
0.3.1
+

新增 2

+
    +
  • 新增支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
  • +
  • 新增在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。
  • +
+
0.3.0

新增 8 · 调整 14 · 修复 3

@@ -401,6 +418,8 @@
  • 调整主窗口的 CPU 页:走势线加高并带 25% / 50% / 75% 参考线,核心分工与排队程度并排显示,不再整页单列拉长。
  • +
    + 更早的更新(2 天)
    0.3.0

    新增 13 · 调整 4

    @@ -429,8 +448,6 @@
    -
    - 更早的更新(1 天)
    0.2.0

    新增 30 · 样式 12 · 修复 6

    @@ -529,7 +546,7 @@
    支持 Intel Mac 吗? -

    目前在 Apple Silicon 上开发和测试,Intel 机型的风扇与温度读取尚未验证。

    +

    支持,在上方下载 Intel 版即可,需要 macOS 14 Sonoma 或更高版本。用 Apple 智能解释进程只能在 Apple 芯片上使用;Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数也可能不显示。开发主要在 Apple 芯片上进行,Intel 机型遇到风扇或温度读数不对,欢迎到 GitHub 反馈。

    diff --git a/web/styles.css b/web/styles.css index 42be18a..3ddc41d 100644 --- a/web/styles.css +++ b/web/styles.css @@ -231,6 +231,12 @@ p { margin: 0; } .download { text-align: center; display: flex; flex-direction: column; align-items: center; gap: var(--space-4); } .download h2 { font-size: var(--text-2xl); } .download p { color: var(--color-text-secondary); } +.download__options { list-style: none; margin: 0; padding: 0; width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); } +.download__option { padding: var(--space-6); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); display: flex; flex-direction: column; align-items: center; gap: var(--space-2); } +.download__option h3 { font-size: var(--text-lg); } +.download__option p { font-size: var(--text-sm); flex: 1; } +.download__option .btn { margin-top: var(--space-2); } +.download__hint { font-size: var(--text-sm); } .download__brew { font-size: var(--text-sm); } .download__brew code, .faq code { font-family: var(--font-mono); font-size: var(--text-xs); padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); background: var(--color-neutral-100); color: var(--color-text); } :root[data-theme="dark"] .download__brew code, :root[data-theme="dark"] .faq code { background: #25282d; } @@ -275,7 +281,7 @@ p { margin: 0; } @media (max-width: 600px) { .band { padding: var(--space-12) 0; } - .feature-grid, .style-preview { grid-template-columns: 1fr; } + .feature-grid, .style-preview, .download__options { grid-template-columns: 1fr; } .section-head h2, .split__text h2, .download h2 { font-size: var(--text-xl); } .fact-list li { grid-template-columns: 1fr; gap: var(--space-1); } }