feat: 添加 WireGuard 设备管理并完善客户端分流

实现独立加密设备身份、网关 agent、持久生命周期任务和 Surge/FlClash 专属订阅凭证。

完善 Shadowrocket 规则转换与 Clash AI DNS,增加 Surge FFSB 适配、规则快照、运维工具及验收文档。
This commit is contained in:
YANG JIANKUAN
2026-11-02 22:44:23 +08:00
parent f741aeeccd
commit a5d980a5e9
80 changed files with 4777 additions and 51 deletions

View File

@@ -1,4 +1,5 @@
import type { ProfileIR } from '../ir.js';
import { SHADOWROCKET_LAN_RULES } from './shadowrocket-lan.js';
/**
* ShadowRocket 的 conf 语法没有引号机制:策略名一律裸写,含空格/emoji 均可;
@@ -58,7 +59,7 @@ export interface ShadowrocketConfOutput {
* 策略组收节点只能靠 `policy-regex-filter` 从 App 内全部节点里筛选,
* 因此订阅在 App 里叫什么名字都可以。
*/
export function generateShadowrocketConf(ir: ProfileIR): ShadowrocketConfOutput {
export function generateShadowrocketConf(ir: ProfileIR, opts: { convertBaseUrl?: string } = {}): ShadowrocketConfOutput {
const lines: string[] = [];
lines.push('# 由 Proxy Station 生成的 ShadowRocket 配置(策略组 + 规则)');
@@ -78,8 +79,8 @@ export function generateShadowrocketConf(ir: ProfileIR): ShadowrocketConfOutput
if (g.type === 'url-test' && g.tolerance !== null) parts.push(`tolerance=${g.tolerance}`);
}
// policy-regex-filter 从 App 内全部节点里筛选;「收纳全部节点」用全匹配正则
if (g.filterRegex) parts.push(`policy-regex-filter=${g.filterRegex}`);
else if (g.includeAllNodes) parts.push('policy-regex-filter=.*');
const filter = g.filterRegex || (g.includeAllNodes ? '.*' : null);
if (filter) parts.push(`policy-regex-filter=${filter}`);
lines.push(`${policyName(g.name)} = ${parts.join(', ')}`);
}
@@ -90,6 +91,20 @@ export function generateShadowrocketConf(ir: ProfileIR): ShadowrocketConfOutput
lines.push(['FINAL', policyName(r.policy)].join(','));
continue;
}
if (r.type === 'DOMAIN-SET' && r.ruleset?.clashBehavior === 'domain' && opts.convertBaseUrl) {
const url = `${opts.convertBaseUrl}/ruleset/${encodeURIComponent(r.ruleset.id)}.list`;
lines.push(['RULE-SET', url, policyName(r.policy), ...r.params].join(','));
continue;
}
if (r.type === 'RULE-SET' && !r.ruleset && r.value === 'LAN') {
lines.push(...SHADOWROCKET_LAN_RULES.map((rule) => [rule, policyName(r.policy), ...r.params].join(',')));
continue;
}
// An absolute macOS app-bundle path cannot identify an iOS application.
if (r.type === 'PROCESS-NAME' && /^\/Applications\/.*\.app(?:\/|$)/.test(r.value ?? '')) {
skippedRules.push(`${r.type},${r.value},${r.policy}`);
continue;
}
if (SR_UNSUPPORTED_RULE_TYPES.has(r.type)) {
skippedRules.push(`${r.type},${r.ruleset ? r.ruleset.name : r.value},${r.policy}`);
continue;