- 账号与同步:GitHub / Google / Apple 登录后把菜单栏项目与风格等偏好同步到 getopenstats.com,换一台 Mac 登录即恢复(AccountSync 模块、SyncController、SettingsDocument、账号设置页,openstats:// 回调) - 磁盘页成为工具页:已用 / 可清除 / 可用分段容量条、空间分析、文件系统检查、本地快照清理(辅助工具协议 v4)、其他磁盘推出 - 公网 IP:归属地数据源可选 CleanIP.io / ipapi.is / DB-IP / ipinfo.io / 本地 GeoLite2,每台 Mac 自己查、本地缓存 7 天;IPv4 / IPv6 双栈各自查询与切换;弹窗显示 ASN、运营方、原生 / 住宅等徽章与纯净度评分色带;接口区块可重置上传 / 下载统计 - 弹窗:右上角按钮改为该指标自己的图标 + 总设置;主窗口页面标题栏的开关点不动已修复;滚动条改为悬浮式,内容不再偏左;网络弹窗卡片被标题行撑宽的问题已修复 - 设置:⌘ 拖动排序说明;每个菜单栏项目单独选风格并带实时预览;网络与磁盘的进程榜单固定 5 行并平滑排序;CPU 持续高负载通知 - 设计系统:分段条、评分色带、标签徽章、胶囊切换、刷新按钮等组件与徽章配色 token - 更新日志、架构说明、README 与译文同步
65 lines
3.4 KiB
Swift
65 lines
3.4 KiB
Swift
import Foundation
|
||
import Security
|
||
|
||
public enum HelperConstants {
|
||
public static let machServiceName = "com.openstats.helper"
|
||
public static let launchdPlistName = "com.openstats.helper.plist"
|
||
public static let appBundleIdentifier = "com.openstats.app"
|
||
/// 与 App 版本同步;App 发现辅助工具版本不一致时提示重新安装
|
||
public static let protocolVersion = 4
|
||
|
||
/// 辅助工具对调用方的签名要求:与辅助工具自身同一团队签名的 OpenStats。
|
||
/// ad-hoc 签名的开发构建没有团队,只能校验 bundle identifier。
|
||
public static func clientRequirement(teamIdentifier: String?) -> String {
|
||
let identifier = "identifier \"\(appBundleIdentifier)\""
|
||
guard let teamIdentifier, !teamIdentifier.isEmpty else { return identifier }
|
||
return identifier + " and anchor apple generic and certificate leaf[subject.OU] = \"\(teamIdentifier)\""
|
||
}
|
||
}
|
||
|
||
public enum CodeSigningInfo {
|
||
/// 当前进程签名所属的 Team ID;ad-hoc 签名时为 nil
|
||
public static func currentTeamIdentifier() -> String? {
|
||
var code: SecCode?
|
||
guard SecCodeCopySelf([], &code) == errSecSuccess, let code else { return nil }
|
||
var staticCode: SecStaticCode?
|
||
guard SecCodeCopyStaticCode(code, [], &staticCode) == errSecSuccess, let staticCode else { return nil }
|
||
var information: CFDictionary?
|
||
guard SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &information) == errSecSuccess,
|
||
let dictionary = information as? [String: Any] else { return nil }
|
||
return dictionary[kSecCodeInfoTeamIdentifier as String] as? String
|
||
}
|
||
}
|
||
|
||
/// 辅助工具以 root 运行,只暴露固定的少量操作,不提供任意命令执行能力。
|
||
/// 所有回调中的 `String?` 为错误描述,nil 表示成功。
|
||
@objc public protocol OpenStatsHelperProtocol {
|
||
func protocolVersion(reply: @escaping @Sendable (Int) -> Void)
|
||
func setFanTarget(fan: Int, rpm: Double, reply: @escaping @Sendable (String?) -> Void)
|
||
func setFanAutomatic(fan: Int, reply: @escaping @Sendable (String?) -> Void)
|
||
func resetAllFans(reply: @escaping @Sendable (String?) -> Void)
|
||
func setSleepDisabled(_ disabled: Bool, reply: @escaping @Sendable (String?) -> Void)
|
||
func flushDNSCache(reply: @escaping @Sendable (String?) -> Void)
|
||
func purgeMemory(reply: @escaping @Sendable (String?) -> Void)
|
||
/// 为指定网络服务设置 DNS;`servers` 为空表示恢复自动获取。辅助工具会校验服务名与每个地址
|
||
func setDNSServers(service: String, servers: [String], reply: @escaping @Sendable (String?) -> Void)
|
||
/// 删除 Time Machine 本地快照;每个标识都按固定格式校验,逐个执行
|
||
func deleteLocalSnapshots(identifiers: [String], reply: @escaping @Sendable (String?) -> Void)
|
||
}
|
||
|
||
/// 需要管理员权限的系统维护命令。辅助工具与未安装辅助工具时的授权回退共用同一份固定命令
|
||
public enum MaintenanceCommand: String, Sendable, CaseIterable {
|
||
case flushDNS, purgeMemory
|
||
|
||
public var steps: [(path: String, arguments: [String])] {
|
||
switch self {
|
||
case .flushDNS: [("/usr/bin/dscacheutil", ["-flushcache"]), ("/usr/bin/killall", ["-HUP", "mDNSResponder"])]
|
||
case .purgeMemory: [("/usr/sbin/purge", [])]
|
||
}
|
||
}
|
||
|
||
public var shellCommand: String {
|
||
steps.map { ([$0.path] + $0.arguments).joined(separator: " ") }.joined(separator: "; ")
|
||
}
|
||
}
|