支持 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:
1 parent
5de9f22221
commit
40b7e41cbf
16 files changed
+319
-172
No files matched your search
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.**
|
||||
|
||||
[](https://getopenstats.com/#download)
|
||||
[](https://getopenstats.com/#download)
|
||||
[](https://github.com/gentpan/OpenStats/stargazers)
|
||||
[](https://github.com/gentpan/OpenStats/commits/main)
|
||||
[](https://github.com/gentpan/OpenStats/graphs/commit-activity)
|
||||
[](https://github.com/gentpan/OpenStats/actions/workflows/ci.yml)
|
||||
[](https://getopenstats.com/#download)
|
||||
[](https://getopenstats.com/#download)
|
||||
[](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
@@ -6,12 +6,12 @@
|
||||
|
||||
**Mac 的状态,抬眼就看见——CPU、GPU、内存、网络与温度常驻菜单栏,还能调风扇、防休眠、一键清理、卸载应用,检测 IP 纯净度。**
|
||||
|
||||
[](https://getopenstats.com/#download)
|
||||
[](https://getopenstats.com/#download)
|
||||
[](https://github.com/gentpan/OpenStats/stargazers)
|
||||
[](https://github.com/gentpan/OpenStats/commits/main)
|
||||
[](https://github.com/gentpan/OpenStats/graphs/commit-activity)
|
||||
[](https://github.com/gentpan/OpenStats/actions/workflows/ci.yml)
|
||||
[](https://getopenstats.com/#download)
|
||||
[](https://getopenstats.com/#download)
|
||||
[](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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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); }
|
||||
}
|
||||
Reference in new issue
Block a user