docs: 精简架构文档并更新仓库规则
This commit is contained in:
1 parent
b70a621e93
commit
24757efeba
2 files changed
+39
-95
No files matched your search
@@ -1,30 +1,34 @@
|
||||
# XStats 仓库规则
|
||||
|
||||
## 技术栈(Tech Stack)
|
||||
|
||||
- macOS 客户端使用 Swift 6、AppKit 与 SwiftUI,包含 WidgetKit 扩展;目标为 macOS 14+、Apple Silicon(arm64)。
|
||||
- `project.yml` 由 XcodeGen 生成 Xcode 工程;可测试模块位于 Swift Package `Packages/XStatsKit`,构建任务定义在 `Taskfile.yml`。
|
||||
- 系统采集使用 IOKit / SMC;特权操作通过 `SMAppService` 注册的 XPC 辅助工具完成。本机历史和 AI 统计使用 SQLite,偏好设置使用 UserDefaults,凭据使用 Keychain。
|
||||
- 日历使用 Tyme4Swift;更新检查服务位于 `server/api`,使用 Go、Fiber v3、GORM 和 SQLite。依赖版本以 `Packages/XStatsKit/Package.swift` 和 `server/api/go.mod` 为准,构建与发布步骤见 [DEVELOPMENT.md](DEVELOPMENT.md)。
|
||||
|
||||
## 架构与代码质量
|
||||
|
||||
- macOS 客户端优先使用系统原生框架和现有模块;引入新的运行时、跨平台 UI 层、依赖或抽象前,先说明当前需求为何不能由现有机制满足。此约束不排斥已有的 Go 服务和必要的第三方库。
|
||||
- 新代码保持直接、可读:优先提前返回、清晰分支和有意义的局部变量,避免深层嵌套与层层转发的控制流。
|
||||
- 尽量少写嵌套函数;仅在框架回调要求,或局部闭包明显比新增符号更简单时使用。
|
||||
- 不要仅为缩短调用方,把只有一个调用点、又不表达稳定业务概念的逻辑抽成模块级辅助函数。可复用行为、接口或框架回调、导出 API、测试夹具,以及值得直接测试的复杂业务逻辑,可以独立成函数;保留单次使用的辅助函数时,名称应表达持久的领域概念,而不是机械步骤。
|
||||
|
||||
## 性能与资源
|
||||
|
||||
- 监控采样沿用现有需求驱动机制;新增后台任务应在功能未启用、界面不可见或系统休眠等不需要工作时停止或降频,避免无必要的常驻轮询、重复采集和主线程阻塞。
|
||||
- 处理日志、网络响应与历史数据时控制内存、磁盘 I/O 和刷新频率;长任务支持取消,并按实际风险验证耗电、内存及响应性,不为未出现的性能问题提前增加缓存或并发层。
|
||||
|
||||
## 国际化(i18n)
|
||||
|
||||
- 新增或修改用户可见文案时以简体中文为源文案,使用 `Packages/XStatsKit/Sources/Localization` 的 `tr(...)` 与现有翻译资源,同步补齐受影响语言的译文;不要只在某个视图中硬编码单一语言。系统名称、日志标识和协议字段不作为界面文案翻译。
|
||||
- 日期、数字、单位使用当前应用语言对应的 Locale;检查长译文、动态文本及阿拉伯语从右到左布局,避免靠固定宽度或字符串拼接假定中文语序。相关界面改动至少验证受影响语言和布局。
|
||||
|
||||
## 代码许可
|
||||
|
||||
- 本仓库自行编写的新增代码、测试及对上游代码的修改,采用 **AGPL-3.0-or-later**(GNU Affero General Public License 第 3 版或更新版本)。许可范围以 [LICENSING.md](LICENSING.md) 为准,完整许可见 [LICENSE](LICENSE)。
|
||||
- 本次确认的 XStats 版权所有者署名为 `ysicing`。新建源码和测试文件时,使用对应语言的注释语法添加 `Copyright (C) <首次发布年份> ysicing` 和 `SPDX-License-Identifier: AGPL-3.0-or-later`;当前年份为 2026。脚本保留首行 shebang,将声明放在其后。不支持注释的文件在许可说明中记录,不破坏文件格式。
|
||||
- 跨年发生实质修改时可扩展版权年份范围,不机械刷新年份。文件头不写应用版本号,不虚构邮箱;如需联系邮箱,使用用户确认的地址集中记录在项目文档。其他贡献者按真实归属署名,不将其贡献自动归给 ysicing。
|
||||
- 修改上游或第三方文件时,保留原有版权与许可声明,明确区分 XStats 修改部分的授权;禁止批量覆盖第三方许可证或将上游原始代码声称为 AGPL 独占授权。
|
||||
- 新建源码和测试文件在支持注释时标注 `SPDX-License-Identifier: AGPL-3.0-or-later`;脚本保留首行 shebang。不支持注释的文件在许可说明中记录,不破坏文件格式;文件头不写应用版本号或虚构邮箱。
|
||||
- 修改上游或第三方文件时,保留原有版权与许可声明,明确区分 XStats 修改部分的授权;其他贡献者按真实归属署名,不机械改写已有年份,也不将上游原始代码声称为 AGPL 独占授权。
|
||||
- OpenStats 原有 MIT 许可与版权声明保留在 [LICENSES/OpenStats-MIT.txt](LICENSES/OpenStats-MIT.txt),其他第三方声明见 [ThirdPartyNotices.md](ThirdPartyNotices.md)。
|
||||
- 引入第三方代码、依赖或素材时,核对许可兼容性并保留来源、版权及必要声明;不新增与本项目新增代码许可相冲突的默认 MIT 声明。
|
||||
- 调整许可相关内容时,同步核对 LICENSE、LICENSING.md、第三方声明及四语言 README。不得声称本次许可变更撤销了历史版本已有的 MIT 授权。
|
||||
|
||||
Swift 新文件示例:
|
||||
|
||||
```swift
|
||||
// Copyright (C) 2026 ysicing
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
```
|
||||
|
||||
含 OpenStats 上游代码及 XStats 修改的文件示例(仍需保留文件原有的其他声明):
|
||||
|
||||
```swift
|
||||
// Copyright (c) 2026 GiantAccel, LLC
|
||||
// XStats modifications Copyright (C) 2026 ysicing
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
|
||||
//
|
||||
// Upstream portions retain their MIT license.
|
||||
// XStats modifications are licensed under AGPL-3.0-or-later.
|
||||
// See LICENSE, LICENSING.md and LICENSES/OpenStats-MIT.txt.
|
||||
```
|
||||
+13
-73
@@ -1,68 +1,9 @@
|
||||
# XStats
|
||||
# XStats architecture
|
||||
|
||||
macOS 14+ menu-bar system monitor. Swift 6 language mode, AppKit status item and panels
|
||||
hosting SwiftUI, no third-party dependencies. The Xcode project is generated from
|
||||
`project.yml` by XcodeGen; everything testable lives in the local Swift package.
|
||||
|
||||
- `App/` — `main.swift` and the asset catalog.
|
||||
- `Widget/` — the sandboxed WidgetKit extension (“System overview”), embedded in `Contents/PlugIns`.
|
||||
It samples CPU, memory, disk and battery itself through `Metrics`, so it works without the app.
|
||||
- `Helper/` — the privileged helper (`XStatsHelper`) and its launchd plist, embedded in
|
||||
the app bundle for `SMAppService.daemon`.
|
||||
- `Packages/XStatsKit/Sources/Localization` — `tr(_:)`, language resolution and nine-language catalogs (see below).
|
||||
- `Packages/XStatsKit/Sources/SMC` — the AppleSMC user client, fan control, temperature
|
||||
key discovery.
|
||||
- `Packages/XStatsKit/Sources/Metrics` — one sampler per metric and `MetricsHub`; power and CPU
|
||||
frequency (`PowerSampler`), disk activity and NVMe SMART (`DiskSamplers`), Bluetooth battery,
|
||||
the SQLite history store.
|
||||
- `Packages/XStatsKit/Sources/AIUsage` — local usage models, two read-only log scanners and opt-in quota readers.
|
||||
Quota readers inspect CLI credentials read-only and never refresh or write them.
|
||||
- `Packages/XStatsKit/Sources/Cleaner` — cleanup rules, `SafetyGuard`, `CleanEngine`, the app
|
||||
uninstaller's leftover search and the launchd startup-item list.
|
||||
- `Packages/XStatsKit/Sources/Updates` — the update manifest and the download → verify →
|
||||
replace → relaunch steps.
|
||||
- `Packages/XStatsKit/Sources/HelperShared` — the XPC protocol and maintenance commands
|
||||
shared by the app and the helper.
|
||||
- `Packages/XStatsKit/Sources/WebDAVSync` — manual WebDAV GET/PUT and endpoint-specific Keychain passwords.
|
||||
- `Packages/XStatsKit/Sources/XStatsUI` — design tokens, panel pages, settings,
|
||||
menu-bar renderer, app controller, snapshot renderer.
|
||||
- `Packages/XStatsKit/Tests` — metrics, SMC decoding, cleanup safety, updates, UI logic and
|
||||
localization.
|
||||
- `server/api` — the Fiber/GORM update API, SQLite installation counters and server-rendered statistics dashboard.
|
||||
|
||||
## Build
|
||||
|
||||
**Xcode 26 or later is required** — CommandLineTools does not ship the SwiftUI macro plugins.
|
||||
|
||||
```bash
|
||||
brew install go-task xcodegen
|
||||
task build # Release build, then replace /Applications/XStats.app and relaunch
|
||||
task test # swift test in the package
|
||||
```
|
||||
|
||||
Versions read `1.0.0 (110)`: `MARKETING_VERSION` is the semver from the first release heading in
|
||||
`CHANGELOG.md`, while `CURRENT_PROJECT_VERSION` is an independent, monotonically increasing build
|
||||
number. `task build` advances only the build number (`BUMP=0` skips it); `task release` takes the
|
||||
public version from the changelog and advances the build number once. Every `task build` runs
|
||||
`scripts/install_local.sh`: it ends the running app (the helper restores fans and sleep when the
|
||||
connection drops), deletes the old `/Applications/XStats.app`, *moves* the new bundle there so no
|
||||
copy stays in the build folder, re-registers it with Launch Services, and relaunches. Only one
|
||||
XStats ever exists on the machine, so Spotlight and the widget gallery never show duplicates.
|
||||
`INSTALL=0` compiles without installing; `scripts/release.sh` uses it and installs the notarized
|
||||
build at the end.
|
||||
|
||||
`project.yml` defaults to ad-hoc signing so the project opens anywhere. The Taskfile.yml passes
|
||||
the first Developer ID Application identity from the keychain (and `--timestamp` for Release)
|
||||
when there is one. `task release` runs `scripts/release.sh`: build, verify team, timestamp and
|
||||
hardened runtime on both binaries, notarize and staple the app, build and notarize the DMG,
|
||||
write the online-update zip and `appcast.json`, and write a Homebrew cask (`auto_updates true`)
|
||||
whose URLs point at the object storage prefix `https://c.ysicing.net/oss/apps/macOS/XStats`.
|
||||
The release records the source commit plus version-file hashes in `dist/release-provenance.json`;
|
||||
publishing accepts only a clean descendant that changed release metadata, so the Git tag cannot
|
||||
silently include source different from the packaged binaries. `task release-all` runs tests, builds,
|
||||
commits and pushes only release metadata, then publishes every external target; `task publish`
|
||||
resumes the idempotent external half without rebuilding. The helper derives its client
|
||||
requirement from its own signing team at run time, so no team ID is hard-coded.
|
||||
hosting SwiftUI. The Xcode project is generated from `project.yml` by XcodeGen;
|
||||
the app's testable components live in the local Swift package. For directory layout, build, test,
|
||||
and release instructions, see [DEVELOPMENT.md](DEVELOPMENT.md).
|
||||
|
||||
## Sampling
|
||||
|
||||
@@ -72,6 +13,9 @@ samples only that: CPU always; memory and network cheaply; GPU, disk, processes,
|
||||
fans only when a visible surface needs them. Disk is read at most every 30 s, battery every
|
||||
10 s. The loop pauses on screen sleep, system sleep and session switch.
|
||||
|
||||
The sandboxed WidgetKit extension samples CPU, memory, disk, and battery through `Metrics`
|
||||
independently, so it remains useful when the app is not running.
|
||||
|
||||
Things that are easy to get wrong and are handled on purpose:
|
||||
|
||||
- `host_processor_info` returns kernel-allocated memory; it is `vm_deallocate`d every sample.
|
||||
@@ -107,8 +51,9 @@ verifying its version. Unregister/handshake failures leave privileged calls disa
|
||||
|
||||
Registered with `SMAppService.daemon`; `RunAtLoad` so that an unclean exit is repaired at
|
||||
boot. The XPC interface is a fixed list of operations — no arbitrary commands — and each
|
||||
connection gets `setCodeSigningRequirement`. State that must be undone (manual fans, disabled
|
||||
sleep) is persisted to `/Library/Application Support/XStats/helper-state.plist` and
|
||||
connection gets `setCodeSigningRequirement`, derived from the helper's own signing team rather
|
||||
than a hard-coded team ID. State that must be undone (manual fans, disabled sleep) is persisted
|
||||
to `/Library/Application Support/XStats/helper-state.plist` and
|
||||
reverted when the last client disconnects or at the next start. The helper exits after 30 s
|
||||
without clients.
|
||||
|
||||
@@ -335,9 +280,7 @@ the quota reader has no access to session-log content.
|
||||
service name and every address; without the helper, a one-off administrator prompt runs the
|
||||
same fixed command.
|
||||
|
||||
## Online updates
|
||||
|
||||
### Battery and Bluetooth
|
||||
## Battery and Bluetooth
|
||||
|
||||
The battery item reuses `BatterySampler` (IOKit power sources, sampled every 10 s while the item is in the
|
||||
menu bar or its popover / page is open). `BatteryPopover` is both the popover and the main-window page
|
||||
@@ -352,6 +295,8 @@ popover / page or the System page is open, every five minutes when the menu bar
|
||||
low-battery hint or a Mac without a battery), otherwise not at all. `AppModel.bluetoothDemand` derives
|
||||
that from the same visibility state as `demand`.
|
||||
|
||||
## Online updates
|
||||
|
||||
`UpdateController` posts the current version and a hashed random installation ID at launch and daily.
|
||||
China-region locales prefer `https://x-stats.china.12306.work/api/v1/update/check`; other locales prefer
|
||||
`https://xstats-apps.12306.work/api/v1/update/check`. Failures fall back serially to the other endpoint,
|
||||
@@ -380,11 +325,6 @@ that carries the dmg for manual downloads, and only then submits the generated a
|
||||
`scripts/publish_api.py` to both regional services. The manifest lands last, so an installed app never
|
||||
sees a version whose package is not yet in place.
|
||||
|
||||
`server/api/Dockerfile` cross-compiles a CGO-free binary for amd64 and arm64, then runs it as the
|
||||
distroless `nonroot` user with `/data` as the writable SQLite volume. When a branch push changes
|
||||
`server/**`, `.github/workflows/server-image.yml` builds both platforms and publishes
|
||||
`ghcr.io/<owner>/xstats-server:<sanitized-branch>-<full-commit-sha>`. Pull requests do not run this workflow.
|
||||
|
||||
## WebDAV settings sync
|
||||
|
||||
Account login, OAuth callbacks and the old account backend have been removed.
|
||||
|
||||
Reference in new issue
Block a user