@lancewu602/mihomo-dash 0.6.0 → 0.6.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancewu602/mihomo-dash",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "mihomo 管理工具:CLI + 守护进程 + Web 面板",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,6 +5,20 @@
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.6.2] - 2026-10-31
9
+
10
+ ### 修复
11
+
12
+ - `service uninstall` / `service start` 回显的动作命令按作用域生成:system 作用域下不再印
13
+ `systemctl --user …`(真实执行的 argv 本来就不带 `--user`,只有文案错)
14
+
15
+ ## [0.6.1] - 2026-10-31
16
+
17
+ ### 修复
18
+
19
+ - CLI 与生成的服务定义里不再提「自启」页:`status` 的提示改为 `mihomo-dash service install`,
20
+ unit 头注释改为「由 `mihomo-dash service install` 生成」;`--help` 里 Debian 作用域不再只写 `--user`
21
+
8
22
  ## [0.6.0] - 2026-10-31
9
23
 
10
24
  ### 新功能
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-dash-server",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "mihomo-dash 后端源码(作为 mihomo-dash 的一部分发布,不单独发包)",
@@ -22,13 +22,13 @@ const HELP = `mihomo-dash —— mihomo 管理工具:CLI + Web 面板
22
22
  用法: mihomo-dash [命令]
23
23
 
24
24
  status 总览:内核 / 面板 / 系统代理 / 开机自启(默认命令;内核不通时退出码 1)
25
- service <子命令> 开机自启:print / install / uninstall / status(macOS launchd · Debian systemd --user)
25
+ service <子命令> 开机自启:print / install / uninstall / status(macOS launchd · Debian systemd)
26
26
 
27
27
  -V, --version 版本
28
28
  -h, --help 帮助
29
29
 
30
30
  面板进程不再由自带的守护进程管理:交给系统服务管理器,服务定义指向 server/src/index.js(面板)
31
- 与 server/src/kernel/run.js(内核),用上面的 service 子命令或在面板「自启」页安装。
31
+ 与 server/src/kernel/run.js(内核),开机自启用上面的 service 子命令。
32
32
  内核的装 / 升 / 卸用面板的「内核」页,进程启停在侧栏底部;系统代理开关也不在 CLI(用面板顶栏的
33
33
  胶囊开关或配置页的「系统代理」卡片)。(面板默认 http://127.0.0.1:8981)
34
34
 
@@ -5,11 +5,12 @@
5
5
  * 但平台面不一样,**不要照抄 deck 的实现**:
6
6
  *
7
7
  * - deck 只跑 Linux + **system** 单元(`/etc/systemd/system`,要 root);
8
- * - 这里要同时支持 **macOS(launchd)与 Debian(systemd --user)**,而且**是用户级**、不需要 root;
8
+ * - 这里要同时支持 **macOS(launchd 用户级)与 Debian(systemd system 与 --user 两种)**:
9
+ * Debian 默认 system(要 root、开机即起),--user 是备选;macOS 只有用户级;
9
10
  * 两个平台都交给 `createServiceManager()`(`server/src/shared/service.js`),CLI 只管打印结果。
10
11
  * - 两个 job:`panel`(server/src/index.js)与 `kernel`(server/src/kernel/run.js)。
11
12
  *
12
- * 行为与面板「自启」页保持一致:**装/卸都不停正在跑的进程**(这个面板很可能正是被服务拉起来的),
13
+ * 行为约定:**装/卸都不停正在跑的进程**(这个面板很可能正是被服务拉起来的),
13
14
  * 「只开机拉起一次、不保活」(launchd 不写 KeepAlive、systemd 写 Restart=no)。
14
15
  */
15
16
  import { ownKernelRunning } from '../kernel/lifecycle.js';
@@ -197,7 +197,9 @@ export function formatStatus(d) {
197
197
  bits.push(`已运行 ${duration(panel.uptime)}`);
198
198
  out.push(line('面板', `运行中 · ${panel.url} · ${bits.join(' · ')}`));
199
199
  } else {
200
- out.push(line('面板', `未运行(端口 ${panel.port};在面板「自启」页装成系统服务)`));
200
+ out.push(
201
+ line('面板', `未运行(端口 ${panel.port};用 \`mihomo-dash service install\` 装成系统服务)`),
202
+ );
201
203
  }
202
204
 
203
205
  // 系统代理
@@ -220,7 +222,7 @@ export function formatStatus(d) {
220
222
  if (!autostart.supported) {
221
223
  out.push(line('开机自启', autostart.hint ? `不支持(${autostart.hint})` : '不支持'));
222
224
  } else if (!autostart.installed) {
223
- out.push(line('开机自启', '未安装(面板「自启」页打开开关即可)'));
225
+ out.push(line('开机自启', '未安装(`mihomo-dash service install`)'));
224
226
  } else {
225
227
  const where = autostart.platform === 'darwin' ? 'macOS LaunchAgent' : 'systemd --user';
226
228
  out.push(
@@ -143,7 +143,7 @@ export function renderUnit(job, opts) {
143
143
  const entry = opts.entry ?? opts.entries[job];
144
144
  const argv = [opts.node, entry, ...def.args({ port, host })];
145
145
  const log = def.log(opts.cliDir);
146
- return `# 由面板「自启」页生成(server/src/shared/service.js),勿手改 —— 重新点「安装」会覆盖
146
+ return `# 由 \`mihomo-dash service install\` 生成(server/src/shared/service.js),勿手改 —— 重新安装会覆盖
147
147
  # 作用域:${scope === 'system' ? 'system(/etc/systemd/system,以 root 运行,开机即起)' : 'user(~/.config/systemd/user,登录后启动 or linger)'}
148
148
  [Unit]
149
149
  Description=${def.description}
@@ -227,6 +227,8 @@ export function createServiceManager(options = {}) {
227
227
  const scope = platform === 'linux' ? (options.scope ?? (isRoot ? 'system' : 'user')) : 'user';
228
228
  /** systemd 的公共参数:system 作用域不带 `--user` */
229
229
  const systemdArgs = scope === 'system' ? [] : ['--user'];
230
+ /** 回显给人看的 systemctl 前缀,必须与 systemdArgs 一致 */
231
+ const systemctlCmd = ['systemctl', ...systemdArgs].join(' ');
230
232
  /** launchd 的域:GUI 会话固定在 gui/<uid>,只有 SSH 时会话里退到 user/<uid> */
231
233
  const domains = [`gui/${uid}`, `user/${uid}`];
232
234
  const dir = serviceDir({ platform, home, scope, systemDir: options.systemDir });
@@ -433,7 +435,7 @@ export function createServiceManager(options = {}) {
433
435
  const stillLoaded = platform === 'darwin' && !!(await loadedDomain(job));
434
436
  if (platform === 'linux') {
435
437
  const r = await run('systemctl', [...systemdArgs, 'disable', jobOf(job).unit]);
436
- if (r.ok) actions.push(`systemctl --user disable ${jobOf(job).unit}`);
438
+ if (r.ok) actions.push(`${systemctlCmd} disable ${jobOf(job).unit}`);
437
439
  else if (r.stderr && !/not loaded|does not exist|No such file/i.test(r.stderr)) {
438
440
  errors.push(`${job}: ${r.stderr.trim()}`);
439
441
  }
@@ -541,7 +543,7 @@ export function createServiceManager(options = {}) {
541
543
  else errors.push(`${j}: ${r.stderr.trim() || 'launchctl kickstart 失败'}`);
542
544
  } else {
543
545
  const r = await run('systemctl', [...systemdArgs, 'start', jobOf(j).unit]);
544
- if (r.ok) started.push(`systemctl --user start ${jobOf(j).unit}`);
546
+ if (r.ok) started.push(`${systemctlCmd} start ${jobOf(j).unit}`);
545
547
  else errors.push(`${j}: ${r.stderr.trim() || 'systemctl start 失败'}`);
546
548
  }
547
549
  }
@@ -106,7 +106,7 @@ test('plist:路径里的 XML 特殊字符会被转义', () => {
106
106
 
107
107
  test('systemd 单元:ExecStart 固定 argv、Restart=no、日志 append', () => {
108
108
  const unit = renderUnit('panel', OPTS());
109
- assert.match(unit, /^# 由面板「自启」页生成/m);
109
+ assert.match(unit, /^# 由 `mihomo-dash service install` 生成/m);
110
110
  assert.match(unit, /Description=mihomo-dash 面板/);
111
111
  assert.match(
112
112
  unit,
@@ -252,6 +252,36 @@ test('Linux 安装/卸载:daemon-reload + enable --now / disable(不 --now
252
252
  assert.ok(!calls.some((c) => c.includes('--now') && c.includes('disable')));
253
253
  });
254
254
 
255
+ test('Linux system 作用域:回显的动作命令不带 --user', async () => {
256
+ const { run, calls } = stubRun();
257
+ // system 作用域默认写 /etc(测试里换成临时目录,别碰本机)
258
+ const systemDir = path.join(tmpDir, 'etc-systemd');
259
+ const svc = createServiceManager({
260
+ run,
261
+ platform: 'linux',
262
+ scope: 'system',
263
+ systemDir,
264
+ home,
265
+ cliDir,
266
+ });
267
+ await svc.install({ port: 8982, host: '0.0.0.0' });
268
+ // 真正执行的命令本来就不带 --user;回显文案以前写死了 --user,照着抄会失败
269
+ assert.ok(calls.some((c) => c.join(' ') === 'systemctl enable --now mihomo-dash-panel.service'));
270
+
271
+ const started = await svc.start();
272
+ assert.deepEqual(started.started, [
273
+ 'systemctl start mihomo-dash-panel.service',
274
+ 'systemctl start mihomo-dash-kernel.service',
275
+ ]);
276
+
277
+ const res = await svc.uninstall();
278
+ assert.deepEqual(res.actions, [
279
+ 'systemctl disable mihomo-dash-panel.service',
280
+ 'systemctl disable mihomo-dash-kernel.service',
281
+ ]);
282
+ assert.deepEqual(res.errors, []);
283
+ });
284
+
255
285
  test('Linux 安装:manualRunning 的 job 只 enable(不 --now),不拉第二个实例', async () => {
256
286
  const { run, calls } = stubRun();
257
287
  const svc = createServiceManager({ run, platform: 'linux', home, cliDir });
@@ -194,7 +194,7 @@ test('内核不通:如实说原因 + 给启动去处(退出码由 cmdStatus
194
194
  assert.match(out, /^开机自启\s+/m);
195
195
  });
196
196
 
197
- test('面板没起:如实说,并指向「自启」页', async () => {
197
+ test('面板没起:如实说,并指向 service install', async () => {
198
198
  const closed = await new Promise((resolve) => {
199
199
  const srv = http.createServer();
200
200
  srv.listen(0, '127.0.0.1', () => {
@@ -207,7 +207,10 @@ test('面板没起:如实说,并指向「自启」页', async () => {
207
207
  const out = mods.status.formatStatus(d);
208
208
  assert.match(
209
209
  out,
210
- new RegExp(`^面板\\s+未运行(端口 ${closed};在面板「自启」页装成系统服务)$`, 'm'),
210
+ new RegExp(
211
+ '^面板\\s+未运行(端口 ' + closed + ';用 `mihomo-dash service install` 装成系统服务)$',
212
+ 'm',
213
+ ),
211
214
  );
212
215
  });
213
216
 
@@ -247,7 +250,7 @@ test('平台不支持 / 没装:系统代理与开机自启各写一行,不
247
250
  });
248
251
  const out2 = mods.status.formatStatus(off);
249
252
  assert.match(out2, /^系统代理\s+关(系统直连,内核照跑)$/m);
250
- assert.match(out2, /^开机自启\s+未安装(面板「自启」页打开开关即可)$/m);
253
+ assert.match(out2, /^开机自启\s+未安装(`mihomo-dash service install`)$/m);
251
254
  });
252
255
 
253
256
  test('某个块抛错:只记下原因,不连累别的块', async () => {