@morndream/mihomo-tui 0.5.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +462 -0
  4. package/bin/mihomo-tui +34 -0
  5. package/dist/App.js +161 -0
  6. package/dist/App.js.map +1 -0
  7. package/dist/api/client.js +239 -0
  8. package/dist/api/client.js.map +1 -0
  9. package/dist/api/status.js +43 -0
  10. package/dist/api/status.js.map +1 -0
  11. package/dist/api/stream.js +139 -0
  12. package/dist/api/stream.js.map +1 -0
  13. package/dist/api/types.js +6 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/cli.js +221 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/commands/conn.js +122 -0
  18. package/dist/commands/conn.js.map +1 -0
  19. package/dist/commands/logs.js +99 -0
  20. package/dist/commands/logs.js.map +1 -0
  21. package/dist/commands/output.js +177 -0
  22. package/dist/commands/output.js.map +1 -0
  23. package/dist/commands/provider.js +140 -0
  24. package/dist/commands/provider.js.map +1 -0
  25. package/dist/commands/proxy.js +161 -0
  26. package/dist/commands/proxy.js.map +1 -0
  27. package/dist/commands/proxyEnv.js +193 -0
  28. package/dist/commands/proxyEnv.js.map +1 -0
  29. package/dist/commands/rules.js +87 -0
  30. package/dist/commands/rules.js.map +1 -0
  31. package/dist/commands/status.js +75 -0
  32. package/dist/commands/status.js.map +1 -0
  33. package/dist/components/ConfirmDialog.js +31 -0
  34. package/dist/components/ConfirmDialog.js.map +1 -0
  35. package/dist/components/DelayBadge.js +27 -0
  36. package/dist/components/DelayBadge.js.map +1 -0
  37. package/dist/components/InputDialog.js +325 -0
  38. package/dist/components/InputDialog.js.map +1 -0
  39. package/dist/components/ListDialog.js +86 -0
  40. package/dist/components/ListDialog.js.map +1 -0
  41. package/dist/components/ProgressDialog.js +34 -0
  42. package/dist/components/ProgressDialog.js.map +1 -0
  43. package/dist/components/ScrollList.js +26 -0
  44. package/dist/components/ScrollList.js.map +1 -0
  45. package/dist/components/StatusBar.js +58 -0
  46. package/dist/components/StatusBar.js.map +1 -0
  47. package/dist/components/TextEditor.js +112 -0
  48. package/dist/components/TextEditor.js.map +1 -0
  49. package/dist/components/textEditorModel.js +154 -0
  50. package/dist/components/textEditorModel.js.map +1 -0
  51. package/dist/config/manager.js +296 -0
  52. package/dist/config/manager.js.map +1 -0
  53. package/dist/config/preserveGroups.js +9 -0
  54. package/dist/config/preserveGroups.js.map +1 -0
  55. package/dist/config/ruleDocument.js +88 -0
  56. package/dist/config/ruleDocument.js.map +1 -0
  57. package/dist/config/ruleService.js +256 -0
  58. package/dist/config/ruleService.js.map +1 -0
  59. package/dist/config/ruleValidation.js +98 -0
  60. package/dist/config/ruleValidation.js.map +1 -0
  61. package/dist/config/service.js +63 -0
  62. package/dist/config/service.js.map +1 -0
  63. package/dist/config/settingsService.js +162 -0
  64. package/dist/config/settingsService.js.map +1 -0
  65. package/dist/config/skeleton.js +324 -0
  66. package/dist/config/skeleton.js.map +1 -0
  67. package/dist/config/subscriptionService.js +284 -0
  68. package/dist/config/subscriptionService.js.map +1 -0
  69. package/dist/config/subscriptions.js +200 -0
  70. package/dist/config/subscriptions.js.map +1 -0
  71. package/dist/config/types.js +5 -0
  72. package/dist/config/types.js.map +1 -0
  73. package/dist/config.js +101 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/hooks/useProviders.js +138 -0
  76. package/dist/hooks/useProviders.js.map +1 -0
  77. package/dist/hooks/useProxies.js +298 -0
  78. package/dist/hooks/useProxies.js.map +1 -0
  79. package/dist/hooks/useStream.js +127 -0
  80. package/dist/hooks/useStream.js.map +1 -0
  81. package/dist/kernel/installer.js +317 -0
  82. package/dist/kernel/installer.js.map +1 -0
  83. package/dist/kernel/releases.js +111 -0
  84. package/dist/kernel/releases.js.map +1 -0
  85. package/dist/kernel/sources.js +98 -0
  86. package/dist/kernel/sources.js.map +1 -0
  87. package/dist/rules/editor.js +181 -0
  88. package/dist/rules/editor.js.map +1 -0
  89. package/dist/rules/matcher.js +83 -0
  90. package/dist/rules/matcher.js.map +1 -0
  91. package/dist/rules/model.js +25 -0
  92. package/dist/rules/model.js.map +1 -0
  93. package/dist/rules/runtime.js +46 -0
  94. package/dist/rules/runtime.js.map +1 -0
  95. package/dist/rules/types.js +10 -0
  96. package/dist/rules/types.js.map +1 -0
  97. package/dist/ui/FooterLine.js +62 -0
  98. package/dist/ui/FooterLine.js.map +1 -0
  99. package/dist/ui/Panel.js +47 -0
  100. package/dist/ui/Panel.js.map +1 -0
  101. package/dist/ui/RowIndicator.js +25 -0
  102. package/dist/ui/RowIndicator.js.map +1 -0
  103. package/dist/ui/SectionHeader.js +14 -0
  104. package/dist/ui/SectionHeader.js.map +1 -0
  105. package/dist/ui/StatusDot.js +15 -0
  106. package/dist/ui/StatusDot.js.map +1 -0
  107. package/dist/ui/TopBar.js +81 -0
  108. package/dist/ui/TopBar.js.map +1 -0
  109. package/dist/ui/exitGuard.js +20 -0
  110. package/dist/ui/exitGuard.js.map +1 -0
  111. package/dist/ui/keyCapture.js +28 -0
  112. package/dist/ui/keyCapture.js.map +1 -0
  113. package/dist/ui/layout.js +32 -0
  114. package/dist/ui/layout.js.map +1 -0
  115. package/dist/ui/rowIndicator.js +42 -0
  116. package/dist/ui/rowIndicator.js.map +1 -0
  117. package/dist/ui/tableHeader.js +33 -0
  118. package/dist/ui/tableHeader.js.map +1 -0
  119. package/dist/ui/theme.js +47 -0
  120. package/dist/ui/theme.js.map +1 -0
  121. package/dist/ui/tone.js +131 -0
  122. package/dist/ui/tone.js.map +1 -0
  123. package/dist/version.js +27 -0
  124. package/dist/version.js.map +1 -0
  125. package/dist/views/Conns.js +107 -0
  126. package/dist/views/Conns.js.map +1 -0
  127. package/dist/views/Logs.js +89 -0
  128. package/dist/views/Logs.js.map +1 -0
  129. package/dist/views/Providers.js +392 -0
  130. package/dist/views/Providers.js.map +1 -0
  131. package/dist/views/Proxies.js +233 -0
  132. package/dist/views/Proxies.js.map +1 -0
  133. package/dist/views/RuleEditor.js +356 -0
  134. package/dist/views/RuleEditor.js.map +1 -0
  135. package/dist/views/Rules.js +286 -0
  136. package/dist/views/Rules.js.map +1 -0
  137. package/dist/views/Settings.js +734 -0
  138. package/dist/views/Settings.js.map +1 -0
  139. package/dist/views/groupDisplay.js +53 -0
  140. package/dist/views/groupDisplay.js.map +1 -0
  141. package/dist/views/subscriptionFields.js +59 -0
  142. package/dist/views/subscriptionFields.js.map +1 -0
  143. package/dist/views/yamlFile.js +32 -0
  144. package/dist/views/yamlFile.js.map +1 -0
  145. package/package.json +66 -0
@@ -0,0 +1,256 @@
1
+ /** The only orchestration path for persistent rule edits. UI never writes configuration. */
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { isIP } from 'node:net';
5
+ import { draftErrors, isDirty, parseForm } from '../rules/editor.js';
6
+ import { ConfigManager } from './manager.js';
7
+ import { assertSnapshot, digest, readRuleSnapshot, renderRuleCandidate } from './ruleDocument.js';
8
+ import { safeDiagnostic, validationSecrets } from './ruleValidation.js';
9
+ import { SUBS_PATH } from './subscriptions.js';
10
+ let transactionActive = false;
11
+ export function localController(controller) {
12
+ try {
13
+ const url = new URL(controller);
14
+ return ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password &&
15
+ (url.hostname === 'localhost' || url.hostname === '[::1]' || /^127\.(\d{1,3}\.){2}\d{1,3}$/.test(url.hostname));
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ /** Compare only observable, empirically mapped fields. Flags/complex syntax remain kernel-validated. */
22
+ export function runtimeMatches(draft, rules) {
23
+ if (!Array.isArray(rules) || rules.length !== draft.rows.length)
24
+ return false;
25
+ return draft.rows.every((row, i) => {
26
+ const form = parseForm(row.raw.split(',').map(s => s.trim()).join(','));
27
+ if (!form)
28
+ return true;
29
+ const rule = rules[i];
30
+ if (!rule || typeof rule.type !== 'string' || typeof rule.proxy !== 'string')
31
+ return false;
32
+ const type = form.type === 'IP-CIDR6' ? 'IP-CIDR' : form.type;
33
+ const canonicalType = (s) => s.replace(/[-_]/g, '').toLowerCase();
34
+ const payload = (value) => {
35
+ if (form.type.startsWith('DOMAIN'))
36
+ return value.toLowerCase();
37
+ if (form.type.includes('CIDR')) {
38
+ const [address = '', prefix = ''] = value.split('/');
39
+ if (!isIP(address))
40
+ return value;
41
+ return (isIP(address) === 6 ? new URL('http://[' + address + ']').hostname : address) + '/' + Number(prefix);
42
+ }
43
+ return value;
44
+ };
45
+ return canonicalType(rule.type) === canonicalType(type) && rule.proxy === form.target &&
46
+ payload(rule.payload) === payload(form.value);
47
+ });
48
+ }
49
+ export class RuleService {
50
+ options;
51
+ pending;
52
+ recoveryRequired = false;
53
+ wait;
54
+ constructor(options) {
55
+ this.options = options;
56
+ this.wait = options.wait ?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
57
+ }
58
+ open() {
59
+ return readRuleSnapshot(this.options.manager.configPath, this.options.subscriptionsPath ?? SUBS_PATH);
60
+ }
61
+ diagnostic(error, snapshot) {
62
+ return safeDiagnostic(error, [this.options.secret ?? '', ...validationSecrets(snapshot.bytes.toString('utf8'))]);
63
+ }
64
+ result(tx, status, phase, disk, kernel, message, rules) {
65
+ if (status === 'recovery-required')
66
+ this.recoveryRequired = true;
67
+ return { status, phase, disk, kernel, message, backupPath: tx.backupPath, rules };
68
+ }
69
+ progress(phase) { this.options.onProgress?.(phase); }
70
+ async readableRules() {
71
+ const rules = await this.options.client.rules();
72
+ if (!Array.isArray(rules) || rules.some(r => !r || typeof r.type !== 'string' || typeof r.payload !== 'string' || typeof r.proxy !== 'string'))
73
+ throw new Error('无效规则响应');
74
+ return rules;
75
+ }
76
+ async readRuntime(draft) {
77
+ let mismatches = 0;
78
+ for (let attempt = 0; attempt < 3; attempt++) {
79
+ if (attempt)
80
+ await this.wait(500);
81
+ try {
82
+ const rules = await this.readableRules();
83
+ if (runtimeMatches(draft, rules))
84
+ return { kind: 'match', rules };
85
+ mismatches++;
86
+ }
87
+ catch { /* A failed read is not evidence of a mismatch. */ }
88
+ }
89
+ return { kind: mismatches === 3 ? 'mismatch' : 'unreadable' };
90
+ }
91
+ async verify(tx) {
92
+ this.progress('verify');
93
+ const read = await this.readRuntime(tx.draft);
94
+ try {
95
+ assertSnapshot(tx.snapshot, digest(tx.candidate));
96
+ }
97
+ catch (err) {
98
+ return this.result(tx, 'recovery-required', 'verify', 'external', 'unknown', this.diagnostic(err, tx.snapshot));
99
+ }
100
+ if (read.kind === 'match') {
101
+ this.pending = undefined;
102
+ try {
103
+ this.options.manager.pruneBackups();
104
+ }
105
+ catch { /* Cleanup never changes transaction success. */ }
106
+ return this.result(tx, 'confirmed', 'verify', 'candidate', 'confirmed', '已保存并回读确认;复杂规则与附加参数仅经内核校验,未逐项核对', read.rules);
107
+ }
108
+ if (read.kind === 'unreadable') {
109
+ this.pending = tx;
110
+ return this.result(tx, 'pending', 'verify', 'candidate', 'reload-accepted', '配置已保存,重载已接受,运行规则读取未确认;仅可重新读取');
111
+ }
112
+ return this.recover(tx, 'verify', '运行规则持续不一致');
113
+ }
114
+ async recover(tx, phase, cause) {
115
+ this.pending = undefined;
116
+ this.progress('recover');
117
+ try {
118
+ assertSnapshot(tx.snapshot, digest(tx.candidate));
119
+ }
120
+ catch (err) {
121
+ return this.result(tx, 'recovery-required', phase, 'external', 'unknown', cause + ';已停止自动恢复:' + this.diagnostic(err, tx.snapshot));
122
+ }
123
+ try {
124
+ if (!tx.backupPath || digest(readFileSync(tx.backupPath)) !== tx.snapshot.hash)
125
+ throw new Error('备份内容不匹配');
126
+ this.options.manager.writeRuleBytes(tx.snapshot.bytes, tx.snapshot.attributes);
127
+ assertSnapshot(tx.snapshot);
128
+ }
129
+ catch (err) {
130
+ return this.result(tx, 'recovery-required', 'recover', 'unknown', 'unknown', '恢复文件失败:' + this.diagnostic(err, tx.snapshot));
131
+ }
132
+ if (!tx.reloadAttempted)
133
+ return this.result(tx, 'restored', phase, 'original', 'unchanged', cause + ';原文件已恢复,未执行重载');
134
+ try {
135
+ await this.options.client.reload(tx.snapshot.path);
136
+ assertSnapshot(tx.snapshot);
137
+ const read = await this.readRuntime(tx.snapshot.draft);
138
+ assertSnapshot(tx.snapshot);
139
+ if (read.kind !== 'match')
140
+ return this.result(tx, 'recovery-required', 'recover', 'original', 'reload-accepted', '文件已恢复,恢复重载已接受,运行状态未确认');
141
+ return this.result(tx, 'restored', phase, 'original', 'original-confirmed', cause + ';原文件和运行规则已恢复', read.rules);
142
+ }
143
+ catch (err) {
144
+ let disk = 'original';
145
+ try {
146
+ assertSnapshot(tx.snapshot);
147
+ }
148
+ catch {
149
+ disk = 'external';
150
+ }
151
+ return this.result(tx, 'recovery-required', 'recover', disk, 'unknown', '恢复未完成:' + this.diagnostic(err, tx.snapshot));
152
+ }
153
+ }
154
+ async reread() {
155
+ if (!this.pending || transactionActive || this.recoveryRequired)
156
+ throw new Error('当前不能重新读取');
157
+ transactionActive = true;
158
+ try {
159
+ return await this.verify(this.pending);
160
+ }
161
+ finally {
162
+ transactionActive = false;
163
+ }
164
+ }
165
+ async save(snapshot, draft, signal) {
166
+ let phase = 'prepare';
167
+ const tx = { snapshot, draft, candidate: Buffer.alloc(0), reloadAttempted: false };
168
+ if (transactionActive || this.pending || this.recoveryRequired)
169
+ return this.result(tx, 'not-applied', phase, 'unknown', 'unknown', '存在进行中、待确认或恢复未完成的事务,禁止再次保存');
170
+ transactionActive = true;
171
+ let wrote = false;
172
+ const step = (next) => { phase = next; this.progress(next); };
173
+ try {
174
+ step('prepare');
175
+ if (!localController(this.options.controller))
176
+ throw new Error('仅支持本机回环控制器写入');
177
+ if (resolve(this.options.manager.configPath) !== snapshot.path)
178
+ throw new Error('配置路径上下文已变化');
179
+ // Guard metadata is derived from the snapshot, never accepted from callers.
180
+ tx.draft = { ...snapshot.draft, rows: draft.rows, moved: draft.moved, moveOrigins: draft.moveOrigins };
181
+ const errors = draftErrors(tx.draft);
182
+ if (errors.length)
183
+ throw new Error(errors.join(';'));
184
+ if (!isDirty(tx.draft))
185
+ throw new Error('没有待保存修改');
186
+ tx.candidate = Buffer.from(renderRuleCandidate(snapshot, tx.draft));
187
+ step('conflict');
188
+ assertSnapshot(snapshot);
189
+ // Refuse an unreachable/incompatible controller before creating a backup or changing disk.
190
+ await this.readableRules();
191
+ if (signal?.aborted)
192
+ throw new Error('保存准备已取消');
193
+ step('validate');
194
+ const pre = await this.options.manager.validateRuleCandidate(tx.candidate.toString('utf8'), signal);
195
+ if (!pre.ok)
196
+ throw new Error(pre.output);
197
+ if (signal?.aborted)
198
+ throw new Error('校验已取消');
199
+ step('backup');
200
+ assertSnapshot(snapshot);
201
+ tx.backupPath = this.options.manager.backupRuleBytes(readFileSync(snapshot.path), snapshot.attributes);
202
+ if (digest(readFileSync(tx.backupPath)) !== snapshot.hash)
203
+ throw new Error('备份内容与快照不一致');
204
+ assertSnapshot(snapshot);
205
+ if (signal?.aborted)
206
+ throw new Error('校验已取消');
207
+ step('write');
208
+ assertSnapshot(snapshot);
209
+ this.options.manager.writeRuleBytes(tx.candidate, snapshot.attributes);
210
+ wrote = true;
211
+ step('postvalidate');
212
+ assertSnapshot(snapshot, digest(tx.candidate));
213
+ const post = await this.options.manager.validateRuleCandidate(readFileSync(snapshot.path).toString('utf8'));
214
+ if (!post.ok)
215
+ throw new Error(post.output);
216
+ assertSnapshot(snapshot, digest(tx.candidate));
217
+ step('reload');
218
+ assertSnapshot(snapshot, digest(tx.candidate));
219
+ tx.reloadAttempted = true;
220
+ await this.options.client.reload(snapshot.path);
221
+ return await this.verify(tx);
222
+ }
223
+ catch (err) {
224
+ const message = this.diagnostic(err, snapshot);
225
+ // A writer may have replaced the file before reporting an error (e.g. cleanup failure).
226
+ if (!wrote && phase === 'write') {
227
+ try {
228
+ assertSnapshot(snapshot, digest(tx.candidate));
229
+ wrote = true;
230
+ }
231
+ catch {
232
+ try {
233
+ assertSnapshot(snapshot);
234
+ }
235
+ catch {
236
+ return this.result(tx, 'recovery-required', phase, 'external', 'unchanged', message + ';写入期间文件发生外部变化,已停止自动恢复');
237
+ }
238
+ }
239
+ }
240
+ if (wrote)
241
+ return await this.recover(tx, phase, message);
242
+ let disk = 'original';
243
+ try {
244
+ assertSnapshot(snapshot);
245
+ }
246
+ catch {
247
+ disk = 'external';
248
+ }
249
+ return this.result(tx, 'not-applied', phase, disk, 'unchanged', message);
250
+ }
251
+ finally {
252
+ transactionActive = false;
253
+ }
254
+ }
255
+ }
256
+ //# sourceMappingURL=ruleService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruleService.js","sourceRoot":"","sources":["../../src/config/ruleService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /** Explicit isolated resources for rule validation. Never run -t in the live directory. */
2
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, realpathSync } from 'node:fs';
3
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
4
+ import YAML from 'yaml';
5
+ export function safeDiagnostic(value, secrets = []) {
6
+ let text = value instanceof Error ? value.message : String(value);
7
+ for (const secret of secrets.filter(Boolean).sort((a, b) => b.length - a.length))
8
+ text = text.split(secret).join('<REDACTED>');
9
+ return text.replace(/https?:\/\/[^\s"'<>]+/gi, '<URL>')
10
+ .replace(/(secret|password|token|authorization|uuid|private-key)\s*[:=]\s*[^\s,}"']+/gi, '$1=<REDACTED>')
11
+ .replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '').slice(0, 4000);
12
+ }
13
+ export function validationSecrets(text) {
14
+ const result = [];
15
+ const walk = (v, key = '') => {
16
+ if (typeof v === 'string' && /secret|password|token|uuid|private-key|authorization/i.test(key))
17
+ result.push(v);
18
+ if (v && typeof v === 'object')
19
+ for (const [k, child] of Object.entries(v))
20
+ walk(child, k);
21
+ };
22
+ try {
23
+ walk(YAML.parse(text));
24
+ }
25
+ catch { /* Invalid YAML is handled by validation. */ }
26
+ return result;
27
+ }
28
+ export function prepareRuleValidation(text, sourceDir, targetDir) {
29
+ const root = realpathSync(sourceDir);
30
+ const copy = (path) => {
31
+ if (isAbsolute(path) || /^[A-Za-z]:/.test(path))
32
+ throw new Error('不支持外部绝对资源路径:' + path);
33
+ const source = resolve(root, path);
34
+ const rel = relative(root, source);
35
+ if (!rel || rel === '..' || rel.startsWith('..' + sep))
36
+ throw new Error('资源路径逃逸数据目录:' + path);
37
+ if (!existsSync(source))
38
+ throw new Error('缺少校验资源:' + path);
39
+ const realRel = relative(root, realpathSync(source));
40
+ if (realRel === '..' || realRel.startsWith('..' + sep) || !lstatSync(source).isFile())
41
+ throw new Error('资源无法隔离:' + path);
42
+ const destination = resolve(targetDir, rel);
43
+ if (rel === 'config.yaml')
44
+ throw new Error('资源路径与校验配置冲突');
45
+ mkdirSync(dirname(destination), { recursive: true });
46
+ copyFileSync(source, destination);
47
+ };
48
+ const config = YAML.parse(text);
49
+ if (!config || typeof config !== 'object' || Array.isArray(config))
50
+ throw new Error('配置顶层必须是映射');
51
+ // These resource-bearing options are outside the supported isolated context.
52
+ const reject = new Set(['external-ui', 'external-ui-path', 'certificate', 'private-key', 'ca', 'ca-cert', 'client-cert', 'client-key', 'script']);
53
+ const walk = (value) => {
54
+ if (!value || typeof value !== 'object')
55
+ return;
56
+ for (const [key, child] of Object.entries(value)) {
57
+ if (reject.has(key) && child)
58
+ throw new Error('无法隔离的配置依赖:' + key);
59
+ if (key === 'path' && typeof child === 'string')
60
+ copy(child);
61
+ else
62
+ walk(child);
63
+ }
64
+ };
65
+ walk(config);
66
+ // Cached geodata is copied at the standard paths recognized by mihomo
67
+ // (dat/mmdb plus the metadb default for geoip).
68
+ const geodata = ['geoip.dat', 'geoip.metadb', 'geosite.dat', 'country.mmdb', 'GeoLite2-ASN.mmdb'];
69
+ for (const name of geodata)
70
+ if (existsSync(resolve(root, name)))
71
+ copy(name);
72
+ const serialized = JSON.stringify(config);
73
+ if (/GEOSITE|geosite:/i.test(serialized) && !existsSync(resolve(targetDir, 'geosite.dat')))
74
+ throw new Error('缺少 geosite.dat');
75
+ if (/GEOIP|geoip:/i.test(serialized)) {
76
+ if (config['geodata-mode'] === true) {
77
+ if (!existsSync(resolve(targetDir, 'geoip.dat')))
78
+ throw new Error('缺少 geoip.dat(geodata-mode 已开启)');
79
+ }
80
+ else if (!existsSync(resolve(targetDir, 'country.mmdb')) && !existsSync(resolve(targetDir, 'geoip.metadb'))) {
81
+ throw new Error('缺少 country.mmdb 或 geoip.metadb(内核默认 geoip 数据库)');
82
+ }
83
+ }
84
+ if (/IP-ASN/i.test(serialized) && !existsSync(resolve(targetDir, 'GeoLite2-ASN.mmdb')))
85
+ throw new Error('缺少 GeoLite2-ASN.mmdb');
86
+ for (const key of ['proxy-providers', 'rule-providers']) {
87
+ const providers = config[key];
88
+ if (providers && typeof providers === 'object')
89
+ for (const provider of Object.values(providers)) {
90
+ // http 型 provider 的缓存路径由内核自行派生,无法预先隔离复制,交给 -t 给出真实结论
91
+ if (provider && typeof provider === 'object' && provider.type !== 'inline' && provider.type !== 'http' &&
92
+ typeof provider.path !== 'string') {
93
+ throw new Error('provider 缺少可隔离的相对缓存路径');
94
+ }
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=ruleValidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruleValidation.js","sourceRoot":"","sources":["../../src/config/ruleValidation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * systemd 用户服务操作(设计稿 2.2:服务名固定 mihomo,Linux + systemd only)。
3
+ *
4
+ * systemctlBin 可注入:测试传一个 stub 脚本路径即可,物理上不可能碰到真实服务。
5
+ */
6
+ import { execFile } from 'node:child_process';
7
+ import { promisify } from 'node:util';
8
+ const execFileAsync = promisify(execFile);
9
+ const SYSTEMCTL_TIMEOUT_MS = 10_000;
10
+ function describe(err) {
11
+ const e = err;
12
+ return `${e.stderr ?? ''}${e.stdout ?? ''}`.trim() || e.message || String(err);
13
+ }
14
+ export class ServiceManager {
15
+ serviceName;
16
+ systemctlBin;
17
+ constructor(serviceName = 'mihomo', systemctlBin = 'systemctl') {
18
+ this.serviceName = serviceName;
19
+ this.systemctlBin = systemctlBin;
20
+ }
21
+ /** 服务是否处于 active;查询失败一律视为未运行 */
22
+ async isActive() {
23
+ try {
24
+ const { stdout } = await execFileAsync(this.systemctlBin, ['--user', 'is-active', this.serviceName], { timeout: SYSTEMCTL_TIMEOUT_MS });
25
+ return stdout.trim() === 'active';
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ /** 启动服务(proxy on --start 用)。失败抛错并携带完整 stderr。 */
32
+ async start() {
33
+ try {
34
+ await execFileAsync(this.systemctlBin, ['--user', 'start', this.serviceName], { timeout: SYSTEMCTL_TIMEOUT_MS });
35
+ }
36
+ catch (err) {
37
+ throw new Error(`启动 ${this.serviceName} 服务失败:${describe(err)}`);
38
+ }
39
+ }
40
+ /** 重启服务。失败抛错并携带完整 stderr,供上层回滚后展示。 */
41
+ async restart() {
42
+ try {
43
+ await execFileAsync(this.systemctlBin, ['--user', 'restart', this.serviceName], { timeout: SYSTEMCTL_TIMEOUT_MS });
44
+ }
45
+ catch (err) {
46
+ throw new Error(`重启 ${this.serviceName} 服务失败:${describe(err)}`);
47
+ }
48
+ }
49
+ /**
50
+ * 完整 status 输出。注意:systemctl status 对非 active 服务以非零码退出,
51
+ * 但输出仍是诊断信息,所以失败时也把 stdout/stderr 返回而不是抛错。
52
+ */
53
+ async status() {
54
+ try {
55
+ const { stdout } = await execFileAsync(this.systemctlBin, ['--user', 'status', this.serviceName], { timeout: SYSTEMCTL_TIMEOUT_MS });
56
+ return stdout;
57
+ }
58
+ catch (err) {
59
+ return describe(err);
60
+ }
61
+ }
62
+ }
63
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/config/service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;AAEzC,MAAM,oBAAoB,GAAG,MAAM,CAAA;AAEnC,SAAS,QAAQ,CAAC,GAAY;IAC5B,MAAM,CAAC,GAAG,GAA6D,CAAA;IACvE,OAAO,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,OAAO,cAAc;IAEd;IACA;IAFX,YACW,cAAsB,QAAQ,EAC9B,eAAuB,WAAW;QADlC,gBAAW,GAAX,WAAW,CAAmB;QAC9B,iBAAY,GAAZ,YAAY,CAAsB;IAC1C,CAAC;IAEJ,gCAAgC;IAChC,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,IAAI,CAAC,YAAY,EACjB,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,EACzC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAClC,CAAA;YACD,OAAO,MAAM,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,iDAAiD;IACjD,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,aAAa,CACjB,IAAI,CAAC,YAAY,EACjB,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,EACrC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAClC,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,WAAW,SAAS,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjE,CAAC;IACH,CAAC;IAED,sCAAsC;IACtC,KAAK,CAAC,OAAO;QACX,IAAI,CAAC;YACH,MAAM,aAAa,CACjB,IAAI,CAAC,YAAY,EACjB,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,EACvC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAClC,CAAA;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,WAAW,SAAS,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjE,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM;QACV,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,IAAI,CAAC,YAAY,EACjB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,EACtC,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAClC,CAAA;YACD,OAAO,MAAM,CAAA;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAA;QACtB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * 设置事务(spec 2026-09-13 settings-kernel §3.2):把设置页的修改写入
3
+ * config.yaml 并重启服务生效。与订阅事务同一套骨架——备份 → mihomo -t 校验
4
+ * → 原子写 → 写后复验 → 重启 → 确认——但不触碰订阅清单。
5
+ *
6
+ * 回滚表同设计稿 6.1:动了配置就从备份还原并再重启;回滚本身失败时把
7
+ * 原错误与回滚错误一起上抛,绝不静默。
8
+ *
9
+ * 变更字段全部是 buildSkeleton 的 pick* 管辖字段:改完写回 oldConfig 再
10
+ * 重新生成骨架即可持久化(后续增删订阅不会丢)。dns.enable 由骨架从旧配置
11
+ * 派生(本模块直接写 dns 段),因此同样持久。
12
+ */
13
+ import YAML from 'yaml';
14
+ import { ConfigManager } from './manager.js';
15
+ import { ServiceManager } from './service.js';
16
+ import { buildSkeleton } from './skeleton.js';
17
+ import { loadSubscriptions } from './subscriptions.js';
18
+ export const SETTINGS_STEPS = [
19
+ { key: 'read', label: '读取配置' },
20
+ { key: 'generate', label: '生成新配置' },
21
+ { key: 'backup', label: '备份旧配置' },
22
+ { key: 'write', label: '校验并写入' },
23
+ { key: 'restart', label: '重启服务' },
24
+ { key: 'verify', label: '确认服务状态' },
25
+ ];
26
+ export class SettingsError extends Error {
27
+ step;
28
+ detail;
29
+ constructor(message, step, detail) {
30
+ super(message);
31
+ this.step = step;
32
+ this.detail = detail;
33
+ this.name = 'SettingsError';
34
+ }
35
+ }
36
+ /** 从应用配置构造事务依赖;测试通过 overrides 注入临时目录与 stub 二进制 */
37
+ export function settingsDepsFromAppConfig(config, overrides = {}) {
38
+ return {
39
+ manager: new ConfigManager(config.mihomoDir, config.mihomoBin),
40
+ service: new ServiceManager(),
41
+ ...overrides,
42
+ };
43
+ }
44
+ export const LOG_LEVELS = ['debug', 'info', 'warning', 'error', 'silent'];
45
+ function msg(err) {
46
+ return err instanceof Error ? err.message : String(err);
47
+ }
48
+ function isRecord(value) {
49
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
50
+ }
51
+ /** 变更合法性在改动任何文件之前完成;非法输入抛 SettingsError(step 'validate') */
52
+ export function validateChanges(changes) {
53
+ const port = changes['mixed-port'];
54
+ if (port !== undefined && (!Number.isInteger(port) || port < 1024 || port > 65535)) {
55
+ throw new SettingsError(`端口必须是 1024–65535 的整数,收到 ${port}`, 'validate');
56
+ }
57
+ const level = changes['log-level'];
58
+ if (level !== undefined && !LOG_LEVELS.includes(level)) {
59
+ throw new SettingsError(`日志级别必须是 ${LOG_LEVELS.join('/')},收到 ${level}`, 'validate');
60
+ }
61
+ }
62
+ /** 把变更套到已解析的 config.yaml 对象上(就地修改) */
63
+ export function applyChangesToConfig(old, changes) {
64
+ if (changes['mixed-port'] !== undefined)
65
+ old['mixed-port'] = changes['mixed-port'];
66
+ if (changes['allow-lan'] !== undefined)
67
+ old['allow-lan'] = changes['allow-lan'];
68
+ if (changes['ipv6'] !== undefined)
69
+ old['ipv6'] = changes['ipv6'];
70
+ if (changes['unified-delay'] !== undefined)
71
+ old['unified-delay'] = changes['unified-delay'];
72
+ if (changes['tcp-concurrent'] !== undefined)
73
+ old['tcp-concurrent'] = changes['tcp-concurrent'];
74
+ if (changes['log-level'] !== undefined)
75
+ old['log-level'] = changes['log-level'];
76
+ if (changes.dnsEnable !== undefined) {
77
+ const dns = isRecord(old['dns']) ? old['dns'] : (old['dns'] = {});
78
+ dns['enable'] = changes.dnsEnable;
79
+ }
80
+ }
81
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
82
+ /** 应用设置变更:写 config.yaml 并重启服务。进度经 onProgress 上报(步骤引用取自 SETTINGS_STEPS)。 */
83
+ export async function applySettings(changes, deps = {}) {
84
+ validateChanges(changes);
85
+ // 全空变更不动文件也不重启(防御;正常调用方总是带一个明确变更)
86
+ if (Object.values(changes).every((value) => value === undefined)) {
87
+ return { warnings: [] };
88
+ }
89
+ const manager = deps.manager ?? new ConfigManager();
90
+ const service = deps.service ?? new ServiceManager();
91
+ const sleep = deps.sleep ?? defaultSleep;
92
+ const graceMs = deps.graceMs ?? 1500;
93
+ const onProgress = deps.onProgress;
94
+ const completed = [];
95
+ const begin = (step) => onProgress?.(step, [...completed]);
96
+ const finish = (step) => {
97
+ completed.push(step);
98
+ };
99
+ const stepOf = (key) => {
100
+ const found = SETTINGS_STEPS.find((candidate) => candidate.key === key);
101
+ if (!found)
102
+ throw new SettingsError(`未知步骤 ${key}`, key);
103
+ return found;
104
+ };
105
+ let backupPath;
106
+ let configWritten = false;
107
+ try {
108
+ begin(stepOf('read'));
109
+ const subs = loadSubscriptions(deps.subscriptionsPath);
110
+ const old = manager.loadConfig();
111
+ finish(stepOf('read'));
112
+ begin(stepOf('generate'));
113
+ applyChangesToConfig(old, changes);
114
+ const { skeleton, warnings } = buildSkeleton(old, subs);
115
+ const yamlText = YAML.stringify(skeleton, { lineWidth: 0, singleQuote: true });
116
+ finish(stepOf('generate'));
117
+ begin(stepOf('backup'));
118
+ backupPath = manager.backup();
119
+ finish(stepOf('backup'));
120
+ begin(stepOf('write'));
121
+ const pre = manager.validate(yamlText);
122
+ if (!pre.ok) {
123
+ throw new SettingsError('生成的配置未通过校验,已中止写入', 'write', pre.output);
124
+ }
125
+ manager.writeConfig(yamlText);
126
+ configWritten = true;
127
+ const post = manager.validate(manager.readConfigText());
128
+ if (!post.ok) {
129
+ manager.rollback(backupPath);
130
+ configWritten = false;
131
+ throw new SettingsError('配置写入后校验失败(配置已回滚)', 'write', post.output);
132
+ }
133
+ manager.ensureProvidersDir();
134
+ finish(stepOf('write'));
135
+ begin(stepOf('restart'));
136
+ await service.restart();
137
+ await sleep(graceMs);
138
+ finish(stepOf('restart'));
139
+ begin(stepOf('verify'));
140
+ if (!(await service.isActive())) {
141
+ manager.rollback(backupPath);
142
+ configWritten = false;
143
+ await service.restart();
144
+ throw new SettingsError('服务启动失败(已回滚)', 'verify', await service.status());
145
+ }
146
+ finish(stepOf('verify'));
147
+ return { warnings };
148
+ }
149
+ catch (err) {
150
+ if (configWritten && backupPath) {
151
+ try {
152
+ manager.rollback(backupPath);
153
+ await service.restart();
154
+ }
155
+ catch (rollbackErr) {
156
+ throw new SettingsError(`${msg(err)};回滚失败:${msg(rollbackErr)}`, err instanceof SettingsError ? err.step : 'write');
157
+ }
158
+ }
159
+ throw err;
160
+ }
161
+ }
162
+ //# sourceMappingURL=settingsService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settingsService.js","sourceRoot":"","sources":["../../src/config/settingsService.ts"],"names":[],"mappings":"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"}