jkooe
|
e1e35266dc
|
docs(troubleshoot): 修正 dmg 卷名(实际带版本号 Only in One 0.2.0-arm64)
|
2026-10-27 00:05:51 +08:00 |
|
jkooe
|
927486f2dd
|
feat(pack): dmg 卷内自带《首次打开必读.txt》+ 修正已失效的 Gatekeeper 放行指引
用户在真机上撞到「未打开"Only in One.app",因其包含恶意软件」,
但实测 `open -a` 能正常启动(四进程齐全、settings.json 正常写入)。
定性结论:ad-hoc 签名 + 未公证 → `spctl -a -t exec` 必然 rejected,
`codesign --verify --deep --strict` 通过 ⇒ 包没坏、只是没公证。问题只在交付体验。
产品改进:让包自己会说
- electron-builder.yml 加 dmg.contents:卷内恰好 3 个条目(app + Applications 软链 + 中文说明)
- 说明源文件放 launcher/resources/ 而非 build/(~/.gitignore_global 忽略 build/,
放那儿会「本地能打、别人 clone 下来打不出」)
- 刻意不放 .command 脚本:脚本自身也带 quarantine,双击同样被拦,反而多一层困惑
- verify:pack 第 5 步新增两项硬校验:说明文件非空、卷内恰好 3 个可见条目
文档纠错(三处已失效/会误导的指导)
- 「访达 右键 → 打开 → 再点打开」:macOS 15(Sequoia)起 Apple 已移除该绕过路径,
右键菜单里没有那个「打开」项 → 改为「系统设置 → 隐私与安全性 → 仍要打开」+ `open -a` 应急
- 「xattr -dr com.apple.quarantine」:本包全树无 quarantine(只有 com.apple.provenance),
该命令是空操作 → 明确标注无效,并给出 `codesign`/`spctl` 自证判据
- 老文档把「已损坏,无法打开」列为"最常见形态":ad-hoc 签名自洽时系统不会这么说,
该文案只出现在签名真坏时 → 改为独立分支处理
- 新增「为什么每次重新打包又会拦一次」:放行记录绑 cdhash,ad-hoc 每次构建都变
- troubleshooting.md 新增第 19 条(现象/原因/排查/解决 + 判据表 + 三条「不要做」)
|
2026-10-27 00:05:03 +08:00 |
|
jkooe
|
410b5234f9
|
docs(pack): T-04 打包/排障/README 同步交付物与覆盖安装(+2 条排障)
## docs/packaging.md(重写交付相关章节,保留代码规范与 Linux 边界两节)
- 产物表:真实文件名 + **实测字节数**(dmg 94,534,426 B / zip 107,114,242 B);
- 打包命令:`dist:mac/win/all` + `verify:pack`,并解释 `dist:all` 为什么是两条调用;
- **`verify:pack` 八步判据表**(含"哪一步是无 Windows 机器时的核心验证");
- 覆盖安装:macOS / Windows 各一张验收清单 + **本次实测记录**(0.1.0 → 0.2.0 覆盖后
仍是 1 个 app、数据目录 1.8MB 条目零变化、find 仍 2 条);
- 数据目录:为什么"只建一个文件夹"有保证(三层锁 + 三字段各管什么 + 为什么缓存叫
`app-cache` 而不是 `cache`);
- 未签名首启:两种文案 + 右键打开的处置 + ad-hoc 兜底命令;
- 分发预留:`publish` 注释位 + **两条硬约束**(mac 自动更新要签名+公证才可能;
Windows zip 不支持自动安装);
- 新增 §7 子节说明 ad-hoc 签名为什么必须由 `after-pack.js` 做(附实测 codesign 输出);
- 新增 §11 Plan B(Windows 打包挪 CI)备案,本次不实施。
## docs/troubleshooting.md(16 条 → 18 条)
- **#17 覆盖安装后还是旧版本 / 出现两个 app**:托盘常驻导致替换失败、
LaunchServices 记住 `/Volumes/...` 那份、`productName`/`appId` 被改的后果;
排查含 `pgrep`、`ls -d /Applications/Only*`、`plutil -p` 版本、`app.asar` mtime、
`lsregister -dump` 计数;解决含 `lsregister -f` 重注册与 SHA256 对账。
- **#18 应用到底建了哪些文件夹 / 数据能不能搬 / 怎么彻底卸载**:一张目录表
(唯一数据目录 + plist,其余"本来就不会建")、`find … -iname "*onlyinone*"` 期望 2 条、
跨机搬数据(`credential.bin` 换机器解不开 = 需重新登录,属设计)、
macOS/Windows 彻底卸载命令、只清缓存的正确范围。
## README.md
- 文档索引两行同步(18 条排障 / packaging.md 新内容);
- 新增「安装包与覆盖安装」小节:产物表 + 出包自检命令 + **覆盖安装三步** + 数据目录一句话;
- 未签名提示补两处:「已损坏」文案不等于真的损坏、ad-hoc 兜底命令(不需要 Apple 账号)、
以及"mac 端自动更新永久不可用 / zip 无自动安装"的结论;
- 目录结构里的两个数字同步(test 31 文件 / 524 例;补 `launcher/scripts/`)。
## docs/packaging-plan.md
架构方案的原始文档一并入库(决策证据),并在开头加「实施回执」:标注实施中修正的
两处判断(ad-hoc 签名、`dist:all` 架构参数),避免后来者按错的结论行事。
|
2026-10-26 21:38:38 +08:00 |
|
jkooe
|
b6161b41d6
|
docs: 新增附录 A 测试反模式清单(6 条);fix(certs): syncAndPrune 单次读取 deviceId
- certs.ts: deviceId 只在方法入口读一次并下传,避免换 id 瞬间 sync 用新值、
prune 用旧值导致期望集合错配(与 dirRes 一次 tick 只解析一次同原则)
- troubleshooting: 附录 A 收录本项目真实踩过的 6 条测试反模式
(upsert 只覆盖 UPDATE 分支 / 空集合循环恒真 / 假 fs 证明不了大小写不敏感 /
白名单常量自证 / 目录夹具与正则不匹配导致空转 / 测试绿但编译不过)
|
2026-10-26 03:06:47 +08:00 |
|
jkooe
|
8903871919
|
feat(launcher): R10 客户端自愈——device_id 被降级为 null 时换新 id(免 PIN 闭环)
## 缺陷(QA 观察 2,用户可感知的那一半)
服务端已把「500 + 永久丢设备」消灭了,但客户端从不读响应里的 `device_id`:
`agent.ts` 的心跳只取 `result.data.serverTime`。后果链:
1. 两台机器共用 settings.json → 撞 `idx_devices_uuid`;
2. 服务端把后到的那台降级为 `device_id = null`(不 500,在线状态保住)✅;
3. 客户端毫无反应 → 下一轮继续上报**同一个被占用的 id** → 永远 `null`;
4. 本机证书永远进不了对端 Sunshine 目录 → **每次串流都要手输 PIN,无自愈路径**。
免 PIN 是本项目核心目标,所以这条必须补。
## 修法
- `core/config.ts` 新增 `regenerateDeviceId()`:**无条件**换一个新 id 并持久化。
与 `ensureDeviceId()`(保证有 id、绝不覆盖)**职责严格分开** —— 给后者加
`force` 也能 work,但等于在唯一一个「不该覆盖」的入口上开一个能覆盖的开关。
- `agent.ts`:
- `AgentDeps` 新增 `regenerateDeviceId?()` 与 `readDeviceId?()`;
- 内部 `deviceId` 由「构造时快照」改为可变字段,心跳请求体用它;
- 心跳成功后调 `healDeviceIdIfSuppressed(result.data?.device_id)`;
- 判据严格 `=== null`:`undefined` 表示老服务端根本不回该字段,与「被降级」无关,
误触发会导致「每轮换一个 id、永远撞不完」;
- 换完立即持久化,下一轮心跳(≤30s)生效;不做忙等重试,新 id 若又撞则下一轮自然再换;
- 生成器未注入 / 返回同值 / 抛异常时**都不打挂心跳**(就地捕获 + warn)。
- `certs.ts`:`CertServiceDeps.deviceId` 允许传 `() => string`,每次调用现取。
否则换 id 后 `skipped_self` 仍用旧 id 比对,会把**自己的**证书当 peer 写进本机
Sunshine 目录,留下一个永远「受管」的自我证书文件(prune 清不掉)。
- `main.ts`:`currentDeviceId` 改为可变局部变量,向下传工厂函数而非快照。
## 测试(launcher 493 → 502)
`test/agent.test.ts` 新增 6 例:
1. `响应 device_id === null → 生成新 id 并持久化,且与旧 id 不同`
2. `下一轮心跳上报的是新 id(旧 id 不再出现在请求体里)`(断言第二次调用入参)
3. `响应是正常 id → 绝不触发重新生成(防「每轮换一个 id」的抖动)`
4. `响应没有 device_id 字段(老服务端兼容)→ 同样不触发重新生成`
5. `未注入 regenerateDeviceId → 只记 warn,不抛异常、心跳仍算成功`
6. `生成器返回与旧值相同的 id → 不切换(否则就是「假自愈」)`
`test/config.test.ts` 新增 3 例:真的写回设置文件、换完后 `ensureDeviceId` 返回新值、
原本无 id 时也能换。
## 顺带|观察 1 的口径统一(一行)
`insertDevicePresence` 第 ③ 层由无条件 `null` 改为
`racedAfterRetry.device_id ?? null`(保留原值),与 `existing` 分支
`deviceId ?? fallbackId` 的口径一致;避免将来多 worker 共享 DB 时把并发请求刚写入的
合法 id 抹掉一个 tick。
注释里补上 QA 指出的事实:**①③ 在单进程下不可达**(`node:sqlite` 是同步 API,
handler 内无 `await`,两次查询之间插不进别的请求)—— 这正好解释了旧代码为什么
**必然** 500(旧 catch 只有「重查同名」一条路,而它恒走不到)。保留为多 worker 预留。
## 文档
- `docs/troubleshooting.md` §16:新增「两台机器共用同一 `device_id` ⇒ 一个心跳周期内
自动恢复」的首条处置(含两条 warn 的原文与判据),并把
`本机 device_id 已被他人占用,已换新 id` 加入「现象」(注明是自愈动作不是故障)。
- `docs/api.md` R10 段:补「客户端必须读这个 `null` 并换 id,否则降级等于永久无 id」,
含 `=== null` 与 `undefined` 的区别。
- README 数字同步。
## 记录澄清
QA 指出 `30fc269` 的 message 把 `QA-R10f/g/h/i` 记成「新增」,实际是 QA 写在工作区、
我提交时一并收录。归属记错一格,在此说明;断言与代码无误,不改历史。
## 一并入库
`server/test/qa-r10-concurrency.test.mjs`(QA 第 3 轮并发对抗用例 6 例)。
验证:server 82/82(覆盖率 90.18%,smoke 通过);launcher 502/502
(typecheck / eslint / prettier 全绿,覆盖率 91.89%);gitleaks protect --staged 干净。
|
2026-10-26 03:02:43 +08:00 |
|
jkooe
|
5f551b1ea1
|
docs: 记录 device_id 仅大小写不同的理论边界(D4 残余,L5 兜住不误删)
|
2026-10-26 02:51:33 +08:00 |
|
jkooe
|
30fc269943
|
fix(devices): R10 日志脱敏为 deviceId8,并补强 R10 冲突用例
- 日志不再输出完整 device_id,只留前 8 位(与 security.md 脱敏清单同源)
- 去掉 QA-R10 系列的 [BUG-OPEN] 标记(缺陷已修复)
- 新增 QA-R10f/g/h/i:反复上报、降级行为真行、写语句不污染、唯一约束是 per-user
- 新增 R10 INSERT 分支四例:降级为 null / 行确实创建 / last_seen 刷新 / 可自愈
|
2026-10-26 02:49:28 +08:00 |
|
jkooe
|
111b442ef8
|
docs: D3/D4 文档同步(api.md 文件名键语义 / troubleshooting 第 16 条 / README 数字)
## docs/api.md
- heartbeat 的 `device_id` 一行:把 `invalid_name` 更正为 `invalid_device_id`
并写清 `missing_device_id` 与 `invalid_device_id` 的区别(前者=没上报、
后者=格式不合法,排障含义完全不同);补「与 launcher `DEVICE_ID_PATTERN` 逐字一致」。
- 「插入/更新语义」新增 R10 说明:`device_id` 撞 `idx_devices_uuid` 时**降级写回
`null`** 而不是 500(保住在线状态,本轮丢文件名键,下轮自愈)。
- heartbeat 200 响应补一句:响应里的 `device_id` 是**本次实际写入库的值**
(正常=请求值;撞约束=`null`;未带=库里上一次的值)。
- `POST /certs` 的 `device_id`:原文「服务端忽略」容易误读成「传了也没用」,
改为「忽略**本请求体**里的值:证书条目的 `device_id` 取自 device 行(由 heartbeat
写入),所以客户端要让它有值必须先在 heartbeat 里上报」。
- `GET /certs` 新增「客户端如何消费这份快照」表:①跳过自己→⑥原子写,六个 reason
全列;并写明为什么文件名主体必须是 `device_id`(两套白名单会错位、大小写不敏感 FS
会互相覆盖)以及 L5 不变量为何让**升级免迁移脚本**。
- §7 客户端约定新增第 3 条:落盘文件名只由 `device_id` 决定,含空/畸形/self-skip 三条派生行为。
- curl 示例的本地路径由 `moonlight-kou-mbp.pem` 改为 `moonlight-<device_id>.pem`。
## docs/troubleshooting.md
- 新增**第 16 条「对端证书不落盘 / 凭据目录里的文件『只增不减』」**(四段式):
四个 reason(missing_device_id / invalid_device_id / expected_missing / io_error)
的成因、四条排查命令、以及「升级后新旧两种文件名并存是正常的、不需要迁移脚本」
的明确结论。
- 文首条目数 15 → 16,并指明第 16 条由本次 D3/D4 引入。
- 全文**没有**「设备名不要仅大小写不同」这类警告 —— §13.6 的兜底文案是
「若最终决定不修才写」,本轮决定修且已修,故不写入(避免留下已失效的限制说明)。
## README.md
- server 52 → 58 例、行覆盖 89.29% → 89.51%(branch 70.88% / funcs 88.73%);
- launcher 451 → 474 例、行覆盖 91.67% → 91.87%(branch 83.74% / funcs 85.5%)。
验证:gitleaks protect --staged 干净(无 .gitleaksignore、无 --no-verify);
双端测试全绿;README 里每个数字都是本轮实测值。
|
2026-10-26 02:26:35 +08:00 |
|
jkooe
|
e6541ab751
|
docs(T-4): API 契约 / 安全 / 排障 / README 同步(不改源码)
四份交付物,全部以代码为唯一依据逐条核对,未改动任何源码、测试或配置。
新增 `docs/api.md`(566 行)
- 端点清单与代码逐条对齐:`server.js` 共 **10 条路由注册 = 9 个逻辑端点 +
`/api/health` 兼容别名**(复核命令已写进文档)
- 每个端点:方法/路径/鉴权/请求体字段(类型·必填·校验规则)/成功与失败响应/
全部错误码/curl 示例
- 写清 6 个易错点:`/api` → `/api/v1` 的 **308**(含 `/api/health` 豁免与
「中间件在 express.json 之前」)、access 2h / refresh 30d + jti 吊销、
refresh **一次性轮换**、heartbeat 的 `device_id` 等 4 个字段「非法则忽略并保留
上次值」的老数据兜底、重复注册 **409 USER_EXISTS**、安全响应头 + 64KB 请求体上限
- 错误码总表 **15 条**(与 `errors.js#ERROR_TABLE` 逐条对齐,附复核命令)+
端点 × 错误码矩阵;并说明 `FORBIDDEN` / `INVALID_ARGUMENT` 当前无业务触发点
(保留是为了客户端映射表 100% 覆盖)
新增 `docs/security.md`(283 行)
- 信任模型:云是盲协调器(数据面不上云、只存公钥),但**盲 ≠ 不校验** ——
服务端强制自算 SHA-256(忽略客户端 fingerprint)+ 客户端下载后**必须本地重算比对**
- 私钥边界四层(发现/上传前/入库前/日志),双端同一规则
- `JWT_SECRET` 轮换 6 步(含影响面:所有 token 立刻失效)+ 准入条件
- 日志脱敏清单:四类输入(敏感键名 / 私钥块 / JWT 字面量 / 超长截断)+
指纹只留前 8 位 + 递归深度与非预期类型兜底
- **三条真实踩坑**:① gitleaks 8.30.1 **会先 base64 解码再匹配**(所以 base64 存
假密钥无效,切块会被 generic-api-key 打回)→ 正确姿势是「PEM 标记运行期拼装 +
载荷 base64 单独成行 + import 期 SELF_CHECK」;② 测试造敏感载荷用
`fakeSecrets.mjs`(服务端)/ `pemBlock()`+`fakeJwt()`(启动器);
③ `~/.gitignore_global` 的 `*.pem` 会让 fixture 静默进不了库 → 用 `.mjs` 导出
- **`TRUST_PROXY` 部署警告**:反向代理后不设 `TRUST_PROXY=1` 会让全团队共享一个
IP 计数器(一人失败 5 次锁死所有人)
- 已知安全边界 8 项(双向证书、服务端证书区分、TLS 终止、refresh 绑设备、签名…)
新增 `docs/troubleshooting.md`(636 行,**15 条**四段式)
- 开头用醒目框写「**迁移备份只复制主 db 文件**」的未来 WAL 风险(第 10 条正文给出
`VACUUM INTO` / `PRAGMA wal_checkpoint(TRUNCATE)` 两种改法)
- 覆盖:JWT_SECRET 被拒(含报错原文 + 生成命令)、`node:sqlite`
`ExperimentalWarning`(正常)、`node --test <目录>` 报 Cannot find module、
clone 后 fixture ENOENT、gitleaks 阻断、设备一直离线(90s 阈值 / 30s 心跳)、
换地址后打到旧地址(设置项优先级 > 环境变量,308 只改路径不改 host)、
限流自锁(含三条 SQL 解锁)、迁移失败回滚与手工恢复(备份只留 3 份)、
WAL 备份风险、升级后同机两行设备(含合并 SQL)、USB `UNSUPPORTED_ON_PLATFORM`
(附 8 位 × 3 端能力矩阵)、usbip 提权、SMB 打不开、Linux 边界
修改 `README.md`(92 → 310 行,R-P2-10)
- 阶段映射表全部换成与代码一致的状态(✅/⏳),**删掉所有过时 TODO**
- 补:Node **>=22.5(推荐 ≥24)**、`.env` 现在靠 `--env-file-if-exists=.env`
真正加载(并说明旧 README 的「放好即可」为何不准确)
- 补:双端完整命令表(`smoke` / `lint` / `format:check` / `typecheck` /
`test:coverage`)+ 覆盖率门禁口径与实测值(server **75.31%** / launcher **91.06%**)
- 补:平台能力矩阵、Linux 支持边界、P2 13 条状态表、文档索引
- 补:未签名应用首次打开提示(macOS 右键→打开 / Windows SmartScreen 仍要运行 +
核对 SHA-256),避免用户误以为中毒
- 补:**如何新增平台分支**(能力表 + 实现文件 + 单向依赖 + 中文 reason +
禁业务代码写 `process.platform` + `PlatformAdapter` 只增不改)
修改 `docs/packaging.md`(121 → 149 行)
- 按架构 §11.7 第 13 步新增「Linux 支持边界与未来纳入需补什么」9 条清单
(README / security / troubleshooting 三处引用它,避免指向不存在的章节)
⚠️ 文档自身也遵守密钥扫描规则:写本文时实测撞了一次 gitleaks ——
`docs/security.md` 的 curl 示例与 `docs/troubleshooting.md` 引用的扫描输出里
原本含 PEM 字面量,已改成「用变量替换 / 占位描述」,**没有**加 `.gitleaksignore`、
**没有** `--no-verify`。
验证:文档内所有数字均为实测 —— 端点 10/9、错误码 15、troubleshooting 15 条、
smoke 13 步、server 43 例 / 75.31%、launcher 390 例 / 91.06%。
**未改动任何源码**(QA 的 `server/test/qa-security.test.mjs` 与
`launcher/test/qa-contract-e2e.test.ts` 未纳入本次提交)。
|
2026-10-26 01:40:02 +08:00 |
|