@oxiaom/adoremix 1.0.6 → 1.0.7

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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +10 -0
  3. package/src/doctor.js +184 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxiaom/adoremix",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "AdoreMix broadcast server - cross-platform installer, runner and service manager",
5
5
  "bin": {
6
6
  "adoremix": "./bin/adoremix.js"
package/src/cli.js CHANGED
@@ -35,6 +35,16 @@ function buildProgram() {
35
35
  }
36
36
  });
37
37
 
38
+ program
39
+ .command('doctor')
40
+ .description('健康检查:node 版本、二进制、缺库、config.ini、.Adore.db、服务状态')
41
+ .option('--workdir <path>')
42
+ .option('--fix', '自动 apt install 缺的库(需要 root / sudo)')
43
+ .action((opts) => {
44
+ const doctor = require('./doctor');
45
+ process.exitCode = doctor.runDoctor(resolveWorkdir(opts.workdir), { fix: !!opts.fix });
46
+ });
47
+
38
48
  program
39
49
  .command('install')
40
50
  .description('初始化工作目录、复制资源、安装协作依赖、生成 config.ini')
package/src/doctor.js ADDED
@@ -0,0 +1,184 @@
1
+ 'use strict';
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const logger = require('./logger');
7
+ const paths = require('./paths');
8
+
9
+ // 已知 .so → apt 包名映射(Ubuntu/Debian)
10
+ const LIB_TO_PKG = {
11
+ 'libdouble-conversion.so.3': 'libdouble-conversion3',
12
+ 'libpcre2-16.so.0': 'libpcre2-16-0',
13
+ 'libssl.so.3': 'libssl3',
14
+ 'libcrypto.so.3': 'libssl3',
15
+ 'libhiredis.so.0.14': 'libhiredis0.14',
16
+ 'libopus.so.0': 'libopus0',
17
+ 'libopusfile.so.0': 'libopusfile0',
18
+ 'libmp3lame.so.0': 'libmp3lame0',
19
+ 'libsqlite3.so.0': 'libsqlite3-0',
20
+ 'libmariadb.so.3': 'libmariadb3',
21
+ 'libstdc++.so.6': 'libstdc++6',
22
+ 'libgcc_s.so.1': 'libgcc-s1',
23
+ 'libz.so.1': 'zlib1g',
24
+ 'libglib-2.0.so.0': 'libglib2.0-0',
25
+ 'libgssapi_krb5.so.2': 'libkrb5-3',
26
+ 'libkrb5.so.3': 'libkrb5-3'
27
+ };
28
+
29
+ function runDoctor(workdir, opts) {
30
+ opts = opts || {};
31
+ logger.log('=== AdoreMix 健康检查 ===');
32
+ logger.log('');
33
+
34
+ const issues = [];
35
+
36
+ // 1. Node 版本
37
+ const nodeVer = process.versions.node;
38
+ const major = parseInt(nodeVer.split('.')[0], 10);
39
+ if (major < 16) {
40
+ issues.push({ type: 'node', severity: 'error', msg: `Node ${nodeVer} 太老,需要 >= 16` });
41
+ logger.error(`❌ Node.js ${nodeVer}(需要 >= 16)`);
42
+ } else {
43
+ logger.ok(`✓ Node.js v${nodeVer}`);
44
+ }
45
+
46
+ // 2. native 包是否加载
47
+ let native;
48
+ try {
49
+ native = paths.loadNative();
50
+ logger.ok(`✓ 平台子包 ${native.pkgName}`);
51
+ } catch (e) {
52
+ issues.push({ type: 'native', severity: 'error', msg: e.message });
53
+ logger.error(`❌ 平台子包未加载:${e.message.split('\n')[0]}`);
54
+ return reportAndExit(issues, opts);
55
+ }
56
+
57
+ // 3. 二进制存在 + 可执行
58
+ const binPath = path.join(workdir, native.binName);
59
+ const nativeBinPath = native.bin;
60
+ let binOk = false;
61
+ if (fs.existsSync(binPath)) {
62
+ binOk = true;
63
+ logger.ok(`✓ 工作目录二进制 ${native.binName}`);
64
+ } else if (fs.existsSync(nativeBinPath)) {
65
+ binOk = true;
66
+ logger.warn(`⚠ 工作目录无二进制(用 node_modules 子包里的)`);
67
+ } else {
68
+ issues.push({ type: 'binary', severity: 'error', msg: '二进制不存在' });
69
+ logger.error(`❌ 二进制不存在:${native.binName}`);
70
+ }
71
+
72
+ if (binOk && process.platform !== 'win32') {
73
+ // 检查 +x
74
+ const stat = fs.statSync(fs.existsSync(binPath) ? binPath : nativeBinPath);
75
+ const mode = stat.mode & 0o111;
76
+ if (!mode) {
77
+ issues.push({ type: 'perm', severity: 'warn', msg: '二进制缺执行权限' });
78
+ logger.warn(`⚠ 二进制缺 +x 权限(spawn 时会自动 chmod)`);
79
+ }
80
+ }
81
+
82
+ // 4. ldd 检查缺库(仅 Linux)
83
+ if (binOk && process.platform === 'linux') {
84
+ const checkBin = fs.existsSync(binPath) ? binPath : nativeBinPath;
85
+ try {
86
+ const ldd = execSync(`ldd "${checkBin}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
87
+ const missing = [];
88
+ for (const line of ldd.split('\n')) {
89
+ const m = line.match(/^\s*(\S+)\s*=>\s*not found/);
90
+ if (m) missing.push(m[1]);
91
+ }
92
+ if (missing.length === 0) {
93
+ logger.ok(`✓ 动态库依赖完整`);
94
+ } else {
95
+ logger.error(`❌ 缺动态库:${missing.join(', ')}`);
96
+ const pkgs = [];
97
+ const unknown = [];
98
+ for (const lib of missing) {
99
+ const pkg = LIB_TO_PKG[lib];
100
+ if (pkg) pkgs.push(pkg);
101
+ else unknown.push(lib);
102
+ }
103
+ const allPkgs = [...new Set(pkgs)];
104
+ if (allPkgs.length > 0) {
105
+ const cmd = `apt-get install -y ${allPkgs.join(' ')}`;
106
+ logger.log(` 建议命令(root 用户):${cmd}`);
107
+ if (unknown.length > 0) {
108
+ logger.warn(` 未知库(无 apt 映射):${unknown.join(', ')}`);
109
+ }
110
+ issues.push({
111
+ type: 'libs',
112
+ severity: 'error',
113
+ msg: missing.join(', '),
114
+ fixCmd: cmd,
115
+ pkgs: allPkgs
116
+ });
117
+ if (opts.fix) {
118
+ logger.info(`==> 自动修复:执行 ${cmd}`);
119
+ try {
120
+ execSync(cmd, { stdio: 'inherit', cwd: workdir });
121
+ logger.ok('✓ 缺库已安装');
122
+ } catch (e) {
123
+ logger.error(`修复失败:${e.message}`);
124
+ logger.warn('可能需要 sudo:手动执行 sudo ' + cmd);
125
+ }
126
+ }
127
+ }
128
+ }
129
+ } catch (e) {
130
+ logger.warn(`⚠ ldd 检查失败(非 Linux 或权限问题):${e.message.split('\n')[0]}`);
131
+ }
132
+ }
133
+
134
+ // 5. config.ini 存在
135
+ const cfgPath = path.join(workdir, 'config.ini');
136
+ if (fs.existsSync(cfgPath)) {
137
+ logger.ok(`✓ config.ini`);
138
+ } else {
139
+ issues.push({ type: 'config', severity: 'warn', msg: 'config.ini 不存在,需要 adoremix install' });
140
+ logger.warn(`⚠ config.ini 不存在`);
141
+ }
142
+
143
+ // 6. .Adore.db 存在 + 有数据
144
+ const dbPath = path.join(workdir, '.Adore.db');
145
+ if (fs.existsSync(dbPath)) {
146
+ const size = fs.statSync(dbPath).size;
147
+ if (size > 0) {
148
+ logger.ok(`✓ .Adore.db (${(size / 1024).toFixed(1)} KB)`);
149
+ } else {
150
+ issues.push({ type: 'db', severity: 'warn', msg: '.Adore.db 是空文件' });
151
+ logger.warn(`⚠ .Adore.db 是空文件(缺默认数据)`);
152
+ }
153
+ }
154
+
155
+ // 7. 进程状态
156
+ const pidMgr = require('./runner/pid');
157
+ const wp = paths.workdirPaths(workdir);
158
+ const pid = pidMgr.readPid(wp.pidfile);
159
+ if (pid && pidMgr.isRunning(pid)) {
160
+ logger.ok(`✓ 服务运行中 PID=${pid}`);
161
+ } else {
162
+ logger.log(`○ 服务未运行(adoremix start 启动)`);
163
+ }
164
+
165
+ return reportAndExit(issues, opts);
166
+ }
167
+
168
+ function reportAndExit(issues, opts) {
169
+ logger.log('');
170
+ const errors = issues.filter(i => i.severity === 'error');
171
+ const warns = issues.filter(i => i.severity === 'warn');
172
+ if (errors.length === 0 && warns.length === 0) {
173
+ logger.ok('🎉 全部正常');
174
+ return 0;
175
+ }
176
+ if (errors.length > 0) {
177
+ logger.error(`发现 ${errors.length} 个问题:`);
178
+ return 1;
179
+ }
180
+ logger.warn(`发现 ${warns.length} 个警告(不影响运行)`);
181
+ return 0;
182
+ }
183
+
184
+ module.exports = { runDoctor, LIB_TO_PKG };