支持 Intel 芯片:分别打包 Apple 芯片版与 Intel 版,官网按芯片提供两个下载按钮

- Makefile 支持 ARCH=arm64 / x86_64,release.sh 两种芯片各自构建、签名、公证,生成 DMG 与升级包
- 版本清单顶层仍是 Apple 芯片版(兼容 0.3.0),intel 字段放 Intel 版;应用按芯片挑包,安装前核对架构
- Homebrew cask 按 arch 选择安装包
- 官网下载区改为 Apple 芯片 / Intel 两张卡片,README 安装段落与常见问题同步
This commit is contained in:
gentpan committed 2026-09-15 16:21:29 +05:00
1 parent 5de9f22221
commit 40b7e41cbf
16 files changed
+319 -172

No files matched your search

+9
View File
@@ -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
+5 -2
View File
@@ -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
@@ -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: {}
@@ -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
@@ -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<Int32>.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)
@@ -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 {
@@ -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 {
+26 -35
View File
@@ -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.
<!-- changelog:start -->
<!-- Generated from CHANGELOG.md by Scripts/sync_changelog.py. Do not edit by hand. -->
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)
<details open>
<summary><b>2026-09-15</b> · 0.3.1 · 2 added</summary>
**Added**
- 支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
- 在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。
</details>
<details>
<summary><b>2026-09-15</b> · 0.3.0 · 8 added · 14 changed · 3 fixed</summary>
**Added**
@@ -110,34 +129,6 @@ Latest release **0.3.0** (2026-09-15) · [full changelog](CHANGELOG.md) (kept in
</details>
<details>
<summary><b>2026-09-13</b> · 0.3.0 · 13 added · 4 changed</summary>
**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 充电控制键,因此不另做一套充电限制。
</details>
<!-- changelog:end -->
## Activity
+24 -34
View File
@@ -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
<!-- changelog:start -->
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
最新版本 **0.3.0**(2026-09-15) · [完整更新日志](CHANGELOG.md)
最新版本 **0.3.1**(2026-09-15) · [完整更新日志](CHANGELOG.md)
<details open>
<summary><b>2026-09-15</b> · 0.3.1 · 新增 2</summary>
**新增**
- 支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。
- 在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。
</details>
<details>
<summary><b>2026-09-15</b> · 0.3.0 · 新增 8 · 调整 14 · 修复 3</summary>
**新增**
@@ -107,34 +125,6 @@ brew install --cask gentpan/tap/openstats
</details>
<details>
<summary><b>2026-09-13</b> · 0.3.0 · 新增 13 · 调整 4</summary>
**新增**
- 在线升级:启动时和之后每天检查官网版本清单,发现新版本时弹出版本号与更新摘要,可一键安装、以后再说或跳过此版本;下载后依次核对 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 充电控制键,因此不另做一套充电限制。
</details>
<!-- changelog:end -->
## 活跃度
+16 -6
View File
@@ -1,7 +1,10 @@
#!/usr/bin/env python3
"""生成应用内在线升级读取的版本清单 appcast.json。
Scripts/appcast.py <版本> <build> <zip 路径> <dmg 路径> <下载地址前缀> > dist/appcast.json
Scripts/appcast.py <版本> <build> <下载地址前缀> <Apple 芯片 zip> <Apple 芯片 dmg> <Intel zip> <Intel dmg> > 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")
+2 -2
View File
@@ -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"
+24 -12
View File
@@ -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"
+94 -65
View File
@@ -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" <<CASK
cask "openstats" do
version "${VERSION}"
sha256 "${SHA256}"
arch arm: "AppleSilicon", intel: "Intel"
url "${DOWNLOAD_BASE}/OpenStats-#{version}.dmg"
version "${VERSION}"
sha256 arm: "${SHA_ARM}",
intel: "${SHA_INTEL}"
url "${DOWNLOAD_BASE}/OpenStats-#{version}-#{arch}.dmg"
name "OpenStats"
desc "Menu bar system monitor with fan control, keep-awake and cleanup"
homepage "https://getopenstats.com"
@@ -123,16 +149,19 @@ cask "openstats" do
end
CASK
# 本机只保留一份:把刚公证的发布版装到 /Applications,编译目录里不留副本
./Scripts/install_local.sh "$APP"
# 本机只保留一份:把本机芯片的发布版装到 /Applications,另一种芯片的编译产物由安装脚本一并清掉
native="$(uname -m)"
./Scripts/install_local.sh "$(app_for "$native")"
echo
echo "✅ ${DIST}/${DMG_NAME}"
echo " SHA-256 ${SHA256}"
echo " 在线升级:${DIST}/${ZIP_NAME} · ${DIST}/appcast.json"
for arch in "${ARCHS[@]}"; do
NAME="OpenStats-${VERSION}-$(chip "$arch")"
echo "✅ ${DIST}/${NAME}.dmg SHA-256 $(shasum -a 256 "$DIST/$NAME.dmg" | cut -d' ' -f1)"
done
echo " 在线升级:${DIST}/*.zip · ${DIST}/appcast.json"
echo " Homebrew cask:${DIST}/openstats.rb"
if [ "${SKIP_NOTARIZE:-0}" = "1" ]; then
echo "⚠️ 未公证,仅供本机测试。"
else
echo "下一步:./Scripts/publish_release.sh 上传 DMG 到官网并更新 gentpan/homebrew-tap。"
echo "下一步:./Scripts/publish_release.sh 上传安装包到官网并更新 gentpan/homebrew-tap。"
fi
+2 -2
View File
@@ -11,9 +11,9 @@ options:
settings:
base:
SWIFT_VERSION: "6.0"
MARKETING_VERSION: "0.3.0"
MARKETING_VERSION: "0.3.1"
# 构建号四位数字,每次 make build 加一;营销版本只在发布时用 Scripts/version.sh patch / minor 修改
CURRENT_PROJECT_VERSION: "0050"
CURRENT_PROJECT_VERSION: "0051"
MACOSX_DEPLOYMENT_TARGET: "14.0"
# 本地开发使用 ad-hoc 签名;发布时改为 Developer ID(见 docs/PLAN.md)
CODE_SIGN_STYLE: Manual
+26 -9
View File
@@ -155,7 +155,7 @@
<a class="btn btn--primary" href="#download">下载 for Mac</a>
<a class="btn btn--secondary" href="#changelog">更新日志</a>
</div>
<p class="hero__meta">macOS 14 及以上 · Apple Silicon · 已签名并经 Apple 公证</p>
<p class="hero__meta">macOS 14 及以上 · Apple 芯片与 Intel 芯片 · 已签名并经 Apple 公证</p>
<p class="hero__hint">点右上角菜单栏里的 CPU、内存或网速,看看每一项的详情 ↗</p>
<button class="hero__link" id="heroOpenWindow" type="button">或者打开 OpenStats 主窗口</button>
</div>
@@ -337,11 +337,20 @@
<img src="assets/icon.png?v=de171dd3" alt="" width="96" height="96">
<h2>下载 OpenStats</h2>
<p>使用 Developer ID 签名并经过 Apple 公证,打开时不会遇到安全拦截。</p>
<div class="hero__actions">
<a class="btn btn--primary" href="download/OpenStats-0.3.0.dmg">下载 OpenStats 0.3.0</a>
<a class="btn btn--secondary" href="#changelog">这个版本更新了什么</a>
</div>
<p class="hero__meta">macOS 14 Sonoma 及以上 · 简体中文界面</p>
<ul class="download__options">
<li class="download__option">
<h3>Apple 芯片</h3>
<p>M1、M2、M3、M4、M5 系列的 Mac</p>
<a class="btn btn--primary" href="download/OpenStats-0.3.1-AppleSilicon.dmg">下载 Apple 芯片版</a>
</li>
<li class="download__option">
<h3>Intel 芯片</h3>
<p>能升级到 macOS 14 的 Intel Mac(大多为 2018 年及以后的机型)</p>
<a class="btn btn--primary" href="download/OpenStats-0.3.1-Intel.dmg">下载 Intel 版</a>
</li>
</ul>
<p class="download__hint">不确定是哪种?打开苹果菜单 › 关于本机,看“芯片”或“处理器”那一行。</p>
<p class="hero__meta">版本 0.3.1 · macOS 14 Sonoma 及以上 · 简体中文与 English 界面 · <a href="#changelog">这个版本更新了什么</a></p>
<p class="download__brew">也可以用 Homebrew 安装:<code>brew install --cask gentpan/tap/openstats</code></p>
</div>
</section>
@@ -355,6 +364,14 @@
<!-- changelog:start -->
<!-- 由 Scripts/sync_changelog.py 从 CHANGELOG.md 生成,请勿手改。 -->
<div class="log">
<article class="log__day">
<div class="log__head"><time datetime="2026-09-15">2026-09-15</time><span class="log__tag">0.3.1</span></div>
<p class="log__counts">新增 2</p>
<ul class="log__list">
<li><span class="log__kind">新增</span>支持 Intel 芯片的 Mac:分别发布 Apple 芯片版与 Intel 版安装包,官网与 Homebrew 按芯片提供下载(Homebrew 自动选择)。Intel 机型需要 macOS 14 Sonoma 或更高版本;用 Apple 智能解释进程只能在 Apple 芯片上使用,Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数可能不显示。</li>
<li><span class="log__kind">新增</span>在线升级按芯片下载对应的安装包,并在安装前核对安装包支持这台 Mac 的芯片;在 Apple 芯片上经 Rosetta 运行的 Intel 版会升级为 Apple 芯片版。</li>
</ul>
</article>
<article class="log__day">
<div class="log__head"><time datetime="2026-09-15">2026-09-15</time><span class="log__tag">0.3.0</span></div>
<p class="log__counts">新增 8 · 调整 14 · 修复 3</p>
@@ -401,6 +418,8 @@
<li><span class="log__kind">调整</span>主窗口的 CPU 页:走势线加高并带 25% / 50% / 75% 参考线,核心分工与排队程度并排显示,不再整页单列拉长。</li>
</ul>
</article>
<details class="log__older">
<summary>更早的更新(2 天)</summary>
<article class="log__day">
<div class="log__head"><time datetime="2026-09-13">2026-09-13</time><span class="log__tag">0.3.0</span></div>
<p class="log__counts">新增 13 · 调整 4</p>
@@ -429,8 +448,6 @@
</ul>
</details>
</article>
<details class="log__older">
<summary>更早的更新(1 天)</summary>
<article class="log__day">
<div class="log__head"><time datetime="2026-09-13">2026-09-13</time><span class="log__tag">0.2.0</span></div>
<p class="log__counts">新增 30 · 样式 12 · 修复 6</p>
@@ -529,7 +546,7 @@
</details>
<details class="faq">
<summary>支持 Intel Mac 吗?</summary>
<p>目前在 Apple Silicon 上开发和测试,Intel 机型的风扇与温度读取尚未验证。</p>
<p>支持,在上方下载 Intel 版即可,需要 macOS 14 Sonoma 或更高版本。用 Apple 智能解释进程只能在 Apple 芯片上使用;Intel 机型的 CPU 不分性能核与能效核,部分功耗与频率读数也可能不显示。开发主要在 Apple 芯片上进行,Intel 机型遇到风扇或温度读数不对,欢迎到 GitHub 反馈。</p>
</details>
</div>
</section>
+7 -1
View File
@@ -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); }
}