@oxiaom/adoremix 1.0.5 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxiaom/adoremix",
3
- "version": "1.0.5",
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')
@@ -147,6 +157,25 @@ function buildProgram() {
147
157
  process.exitCode = code || 0;
148
158
  });
149
159
 
160
+ config
161
+ .command('log')
162
+ .description('日志开关 isopenrizhi(开启会保存播放日志 + mp3,长期会占存储)')
163
+ .option('--workdir <path>')
164
+ .option('--enable', '开启日志')
165
+ .option('--disable', '关闭日志(默认)')
166
+ .option('--no-interactive', '不询问')
167
+ .action(async (opts) => {
168
+ const net = require('./config/network');
169
+ let enable;
170
+ if (opts.enable) enable = true;
171
+ else if (opts.disable) enable = false;
172
+ const code = await net.setupLog(resolveWorkdir(opts.workdir), {
173
+ enable,
174
+ interactive: opts.interactive !== false
175
+ });
176
+ process.exitCode = code || 0;
177
+ });
178
+
150
179
  const runner = require('./runner');
151
180
  const nativeRef = () => {
152
181
  try { return paths.loadNative(); }
@@ -4,7 +4,7 @@ module.exports = {
4
4
  Settings: {
5
5
  ISLOCALHOST: false,
6
6
  wbport: 6082,
7
- isopenrizhi: true,
7
+ isopenrizhi: false,
8
8
  exename: '',
9
9
  Meida_port: 6002,
10
10
  Meida_ip: '127.0.0.1',
@@ -158,6 +158,44 @@ async function setupNetwork(workdir, opts) {
158
158
  return 0;
159
159
  }
160
160
 
161
+ async function setupLog(workdir, opts) {
162
+ opts = opts || {};
163
+ const cfg = require('./index');
164
+ const KEY = 'Settings.isopenrizhi';
165
+ let val;
166
+ if (opts.enable !== undefined) {
167
+ val = !!opts.enable;
168
+ } else if (opts.interactive === false) {
169
+ val = false;
170
+ } else {
171
+ const cur = cfg.getConfigValue(workdir, KEY);
172
+ logger.log('');
173
+ logger.log('=== 日志开关 (isopenrizhi) ===');
174
+ logger.log('开启后:');
175
+ logger.log(' • 记录所有播放日志');
176
+ logger.log(' • 实时喊话推流的语音/音频自动保存为 mp3');
177
+ logger.log('注意:长期开启会占用大量磁盘空间');
178
+ logger.log(`当前:${cur ? '开启' : '关闭'}`);
179
+ logger.log('');
180
+ const resp = await prompts({
181
+ type: 'toggle',
182
+ name: 'val',
183
+ message: '是否开启日志',
184
+ initial: !!cur,
185
+ active: '开启',
186
+ inactive: '关闭'
187
+ });
188
+ val = resp.val;
189
+ }
190
+ cfg.setConfigValue(workdir, KEY, val);
191
+ logger.ok(`isopenrizhi = ${val}`);
192
+ if (val) {
193
+ logger.warn('日志已开启,长期运行会占用磁盘,建议定期清理 tty/ 和 logs/');
194
+ }
195
+ logger.log('改完后记得:adoremix restart');
196
+ return 0;
197
+ }
198
+
161
199
  async function setupPreurl(workdir, opts) {
162
200
  opts = opts || {};
163
201
  const cfg = require('./index');
@@ -211,6 +249,7 @@ async function setupPreurl(workdir, opts) {
211
249
  module.exports = {
212
250
  setupNetwork,
213
251
  setupPreurl,
252
+ setupLog,
214
253
  listInterfaces,
215
254
  isValidIPv4,
216
255
  isPreurlCdn,
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 };