Files
obsidian-vault/docs/MonoProxy订阅信息获取工作流.md
T
yuxuanhui 91861565bb feat: add frontend development guidelines and structure documentation
- Introduced API guidelines for interface contracts and request handling.
- Added design tokens usage guidelines for consistent styling across the project.
- Established DTO guidelines for defining request parameters and response data types.
- Created frontend structure guidelines to clarify directory organization and code placement rules.
- Compiled a comprehensive frontend development guideline document covering various aspects of the development process.
- Implemented quality guidelines to ensure code maintainability and adherence to best practices.
2026-07-25 22:20:25 +08:00

332 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MonoProxy 订阅信息获取工作流
本文记录如何从 macOS 版 MonoProxy 的本地配置中获取自有账户的节点信息,并生成不包含节点密码的 Clash YAML 列表。
## 适用范围
- 应用路径:`/Applications/MonoProxyMac.app`
- 配置路径:`~/Library/Application Support/MonoProxy/config.json`
- 已验证日期:2026-07-24
- 输出字段:`name`、`server`、`port`、`type`、`cipher`、`udp`
- 明确排除:节点 `password`、登录令牌、刷新令牌和账户信息
仅应处理自己拥有或获授权访问的账户与配置。不要上传或公开分享原始 `config.json`,其中还包含账户令牌和加密后的节点密码。
## 结论
MonoProxy 将节点数组保存在 `config.json` 的 `mn_service_<service-id>_servers` 字段中。该字段是 Base64 字符串,解码后的数据布局为:
```text
salt(16 字节)
+ IV(16 字节)
+ HMAC-SHA256(32 字节)
+ AES-256-CBC ciphertext(剩余字节)
```
密钥派生参数:
```text
算法:PBKDF2-HMAC-SHA256
迭代次数:100000
密钥长度:32 字节
配置封装口令:MonoProxyMac.MNLocalManager.Services.v1
```
这里的“配置封装口令”是应用二进制中用于保护本地配置结构的固定值,不是 Shadowsocks 节点的 `password`。
解密流程必须先验证 HMAC,再执行 AES 解密。HMAC 不匹配时应立即停止,不能忽略校验继续处理。
## 步骤一:让 MonoProxy 刷新本地配置
1. 启动 MonoProxy 并登录自己的账户。
2. 等待节点列表完成刷新;是否开启系统代理不影响离线读取,但刷新过程需要网络。
3. 检查配置文件是否刚刚更新:
```bash
stat -f '%N | modified=%Sm | size=%z' \
-t '%Y-%m-%d %H:%M:%S %z' \
"$HOME/Library/Application Support/MonoProxy/config.json"
```
如果文件不存在,先确认应用是否已登录并成功获取服务信息。
## 步骤二:确认节点字段
只查看字段名称,不输出令牌或节点密码:
```bash
jq -r 'keys[] | select(test("^mn_service_.*_servers$"))' \
"$HOME/Library/Application Support/MonoProxy/config.json"
```
正常情况下会得到类似:
```text
mn_service_2462_servers
```
服务 ID 可能随账户或后端迁移而变化,因此提取脚本不应硬编码数字部分。
## 步骤三:使用离线脚本生成无密码 YAML
下面的脚本仅使用 Node.js 内置的 `fs` 和 `crypto` 模块,不需要安装第三方依赖,也不会联网。脚本会:
1. 自动查找 `mn_service_<id>_servers` 字段;
2. Base64 解码数据;
3. 使用 PBKDF2-SHA256 派生密钥;
4. 验证 HMAC-SHA256;
5. 使用 AES-256-CBC 解密节点 JSON;
6. 输出不含 `password` 的 Clash YAML。
保存为 `extract-monoproxy.js`:
```javascript
const fs = require("fs");
const crypto = require("crypto");
const configPath =
process.argv[2] ||
`${process.env.HOME}/Library/Application Support/MonoProxy/config.json`;
const configEnvelopePassword =
"MonoProxyMac.MNLocalManager.Services.v1";
/**
* 使用 YAML 单引号格式转义字符串,避免节点名称中的特殊字符破坏 YAML。
* @param {unknown} value 需要编码的值。
* @returns {string} 可安全写入 YAML 的单引号字符串。
*/
function yamlString(value) {
return `'${String(value).replaceAll("'", "''")}'`;
}
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
const serverKey = Object.keys(config).find((key) =>
/^mn_service_.*_servers$/.test(key),
);
if (!serverKey) {
throw new Error("未找到 mn_service_<id>_servers 字段");
}
const envelope = Buffer.from(config[serverKey], "base64");
if (envelope.length <= 64) {
throw new Error("节点密文长度异常");
}
const salt = envelope.subarray(0, 16);
const iv = envelope.subarray(16, 32);
const storedHmac = envelope.subarray(32, 64);
const ciphertext = envelope.subarray(64);
const key = crypto.pbkdf2Sync(
configEnvelopePassword,
salt,
100000,
32,
"sha256",
);
const calculatedHmac = crypto
.createHmac("sha256", key)
.update(Buffer.concat([salt, iv, ciphertext]))
.digest();
if (
storedHmac.length !== calculatedHmac.length ||
!crypto.timingSafeEqual(storedHmac, calculatedHmac)
) {
throw new Error(
"HMAC 校验失败:配置可能损坏,或 MonoProxy 已更改加密格式",
);
}
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]).toString("utf8");
const nodes = JSON.parse(plaintext);
if (!Array.isArray(nodes)) {
throw new Error("解密结果不是节点数组");
}
console.log("proxies:");
for (const node of nodes) {
if (!node.alias || !node.hostname || !node.port || !node.encryption) {
throw new Error("节点缺少 alias/hostname/port/encryption 字段");
}
console.log(` - name: ${yamlString(node.alias)}`);
console.log(` server: ${yamlString(node.hostname)}`);
console.log(` port: ${Number(node.port)}`);
console.log(" type: ss");
console.log(` cipher: ${yamlString(node.encryption)}`);
console.log(" udp: true");
}
```
执行:
```bash
node extract-monoproxy.js \
"$HOME/Library/Application Support/MonoProxy/config.json" \
> monoproxy-subscription-without-password.yaml
```
检查输出中没有密码字段:
```bash
rg -n 'password|access_token|refresh_token' \
monoproxy-subscription-without-password.yaml
```
正常结果应无任何输出。再检查 YAML 的节点数量:
```bash
rg -c '^ - name:' monoproxy-subscription-without-password.yaml
```
当前快照应输出 `15`。
## 字段映射
| MonoProxy 节点字段 | Clash YAML 字段 | 说明 |
|---|---|---|
| `alias` | `name` | 节点显示名称 |
| `hostname` | `server` | 节点域名或地址 |
| `port` | `port` | 节点端口 |
| 服务类型 Shadowsocks | `type: ss` | `type` 不在每个节点对象中单独存储 |
| `encryption` | `cipher` | 当前均为 `chacha20-ietf-poly1305` |
| 未单独存储 | `udp: true` | 按现有 Clash Shadowsocks 配置补充 |
| `password` | 不输出 | 本工作流明确排除 |
## 当前完整 YAML 快照(不含 password)
数据来自 2026-07-24 15:03:55 更新的本地 `config.json`。HMAC 校验、AES 解密及 JSON 解析均已通过。
```yaml
proxies:
- name: 'Relay-HK1'
server: 'scott.mydarkcloud.info'
port: 1904
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK2'
server: 'andrew.mydarkcloud.info'
port: 2004
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK3'
server: 'ethan.mydarkcloud.info'
port: 3204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-HK4'
server: 'lucas.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-SG1'
server: 'tyler.mydarkcloud.info'
port: 2604
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-SG2'
server: 'tyler.mydarkcloud.info'
port: 2704
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-JP1'
server: 'patrick.mydarkcloud.info'
port: 1504
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-JP2'
server: 'ava.mydarkcloud.info'
port: 995
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-TW1'
server: 'kevin.mydarkcloud.info'
port: 2104
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-TW2'
server: 'kevin.mydarkcloud.info'
port: 2204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-US1'
server: 'nathan.mydarkcloud.info'
port: 1204
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'Relay-US2'
server: 'nathan.mydarkcloud.info'
port: 1304
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'JP3'
server: 'noah.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'TW1'
server: 'tw1.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
- name: 'TR1'
server: 'tr1.mydarkcloud.info'
port: 999
type: ss
cipher: 'chacha20-ietf-poly1305'
udp: true
```
## 验证与故障处理
### HMAC 校验失败
不要跳过校验。常见原因:
- `config.json` 正在被应用写入,读取到了不完整内容;
- MonoProxy 更新后更改了封装口令、迭代次数或加密格式;
- 读取了其他应用或旧版本生成的配置文件。
先等待应用完成刷新并重新执行。如果仍失败,需要重新检查当前二进制中的:
- `MNLocalManager -_encryptedJSONObjectForKey:`
- `Ctor +d:p:e:`
- `CCKeyDerivationPBKDF` 参数
- `AES256CBCDecryptData:key:iv:error:`
- `HMACSHA256WithData:key:`
### 输出节点为空或字段缺失
- 确认账户仍有有效服务;
- 确认找到的是当前 `mn_service_<id>_servers` 字段;
- 不要把旧版 `~/Library/Preferences/com.MonoCloud.MonoProxyMac.plist` 当作最新数据源;
- 优先以刚刷新过的 `~/Library/Application Support/MonoProxy/config.json` 为准。
### YAML 无法直接连接
本文输出刻意删除了 `password`,因此它是用于审阅、比对和更新 `server/port` 的安全快照,并不是可直接连接的完整凭据文件。需要实际连接时,应在本地私密环境中补回自己已有的密码,且不要提交到 Git 或同步到公开笔记库。