@oxiaom/adoremix 1.0.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.
- package/bin/adoremix.js +11 -0
- package/bin/postinstall-hint.js +14 -0
- package/package.json +50 -0
- package/src/cli.js +263 -0
- package/src/config/defaults.js +98 -0
- package/src/config/index.js +102 -0
- package/src/config/ini.js +60 -0
- package/src/config/wizard.js +124 -0
- package/src/index.js +7 -0
- package/src/install.js +173 -0
- package/src/logger.js +29 -0
- package/src/paths.js +94 -0
- package/src/runner/index.js +6 -0
- package/src/runner/pid.js +65 -0
- package/src/runner/signals.js +15 -0
- package/src/runner/spawn.js +153 -0
- package/src/service/index.js +15 -0
- package/src/service/linux.js +139 -0
- package/src/service/windows.js +88 -0
- package/templates/adoremix.service.tmpl +19 -0
- package/templates/winservice.js.tmpl +66 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const fse = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
const logger = require('../logger');
|
|
10
|
+
const paths = require('../paths');
|
|
11
|
+
|
|
12
|
+
const SYSTEM_UNIT_PATH = '/etc/systemd/system/adoremix.service';
|
|
13
|
+
const USER_UNIT_DIR = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
14
|
+
const USER_UNIT_PATH = path.join(USER_UNIT_DIR, 'adoremix.service');
|
|
15
|
+
|
|
16
|
+
function isSystemMode() {
|
|
17
|
+
return process.getuid && process.getuid() === 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ensureUserExists(username) {
|
|
21
|
+
try {
|
|
22
|
+
execSync(`id -u ${username}`, { stdio: 'ignore' });
|
|
23
|
+
return false;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
execSync(`useradd -r -s /usr/sbin/nologin ${username}`);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function chownWorkdir(workdir, username) {
|
|
31
|
+
execSync(`chown -R ${username}:${username} ${workdir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fillTemplate(workdir) {
|
|
35
|
+
const tplPath = path.join(__dirname, '..', '..', 'templates', 'adoremix.service.tmpl');
|
|
36
|
+
let tpl = fs.readFileSync(tplPath, 'utf8');
|
|
37
|
+
const user = isSystemMode() ? 'adoremix' : os.userInfo().username;
|
|
38
|
+
const node = paths.nodeExecutable();
|
|
39
|
+
const cli = paths.cliBin();
|
|
40
|
+
return tpl
|
|
41
|
+
.replace(/__USER__/g, user)
|
|
42
|
+
.replace(/__GROUP__/g, user)
|
|
43
|
+
.replace(/__WORKDIR__/g, workdir)
|
|
44
|
+
.replace(/__NODE__/g, node)
|
|
45
|
+
.replace(/__CLI__/g, cli);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function installSystem(workdir) {
|
|
49
|
+
let created = false;
|
|
50
|
+
try {
|
|
51
|
+
created = ensureUserExists('adoremix');
|
|
52
|
+
if (created) logger.ok('创建系统用户 adoremix');
|
|
53
|
+
} catch (e) {
|
|
54
|
+
logger.warn(`无法创建 adoremix 用户:${e.message}(服务将以 root 运行)`);
|
|
55
|
+
}
|
|
56
|
+
if (created) {
|
|
57
|
+
try { chownWorkdir(workdir, 'adoremix'); } catch (e) {
|
|
58
|
+
logger.warn(`chown 失败:${e.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const content = fillTemplate(workdir);
|
|
62
|
+
fs.writeFileSync(SYSTEM_UNIT_PATH, content, 'utf8');
|
|
63
|
+
logger.ok(`写入 unit 文件 ${SYSTEM_UNIT_PATH}`);
|
|
64
|
+
execSync('systemctl daemon-reload');
|
|
65
|
+
execSync('systemctl enable adoremix');
|
|
66
|
+
logger.ok('systemctl enable adoremix');
|
|
67
|
+
execSync('systemctl start adoremix');
|
|
68
|
+
logger.ok('systemctl start adoremix');
|
|
69
|
+
logger.log('');
|
|
70
|
+
logger.log('查看状态:systemctl status adoremix');
|
|
71
|
+
logger.log('查看日志:journalctl -u adoremix -f');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function installUser(workdir) {
|
|
75
|
+
fse.ensureDirSync(USER_UNIT_DIR);
|
|
76
|
+
const content = fillTemplate(workdir);
|
|
77
|
+
fs.writeFileSync(USER_UNIT_PATH, content, 'utf8');
|
|
78
|
+
logger.ok(`写入 user unit ${USER_UNIT_PATH}`);
|
|
79
|
+
execSync('systemctl --user daemon-reload');
|
|
80
|
+
execSync('systemctl --user enable adoremix');
|
|
81
|
+
logger.ok('systemctl --user enable adoremix');
|
|
82
|
+
execSync('systemctl --user start adoremix');
|
|
83
|
+
logger.ok('systemctl --user start adoremix');
|
|
84
|
+
logger.log('');
|
|
85
|
+
logger.log('用户服务需 linger 才能开机自启:loginctl enable-linger $USER');
|
|
86
|
+
logger.log('查看状态:systemctl --user status adoremix');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function uninstallSystem() {
|
|
90
|
+
try { execSync('systemctl stop adoremix', { stdio: 'ignore' }); } catch (e) {}
|
|
91
|
+
try { execSync('systemctl disable adoremix', { stdio: 'ignore' }); } catch (e) {}
|
|
92
|
+
if (fs.existsSync(SYSTEM_UNIT_PATH)) fs.unlinkSync(SYSTEM_UNIT_PATH);
|
|
93
|
+
execSync('systemctl daemon-reload');
|
|
94
|
+
logger.ok('服务已卸载');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function uninstallUser() {
|
|
98
|
+
try { execSync('systemctl --user stop adoremix', { stdio: 'ignore' }); } catch (e) {}
|
|
99
|
+
try { execSync('systemctl --user disable adoremix', { stdio: 'ignore' }); } catch (e) {}
|
|
100
|
+
if (fs.existsSync(USER_UNIT_PATH)) fs.unlinkSync(USER_UNIT_PATH);
|
|
101
|
+
execSync('systemctl --user daemon-reload');
|
|
102
|
+
logger.ok('用户服务已卸载');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function statusSystem() {
|
|
106
|
+
try {
|
|
107
|
+
execSync('systemctl status adoremix --no-pager', { stdio: 'inherit' });
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function statusUser() {
|
|
115
|
+
try {
|
|
116
|
+
execSync('systemctl --user status adoremix --no-pager', { stdio: 'inherit' });
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
install(opts) {
|
|
125
|
+
const workdir = opts.workdir;
|
|
126
|
+
if (isSystemMode()) installSystem(workdir);
|
|
127
|
+
else installUser(workdir);
|
|
128
|
+
return 0;
|
|
129
|
+
},
|
|
130
|
+
uninstall() {
|
|
131
|
+
if (isSystemMode()) uninstallSystem();
|
|
132
|
+
else uninstallUser();
|
|
133
|
+
return 0;
|
|
134
|
+
},
|
|
135
|
+
status() {
|
|
136
|
+
return isSystemMode() ? statusSystem() : statusUser();
|
|
137
|
+
},
|
|
138
|
+
_internal: { fillTemplate, SYSTEM_UNIT_PATH, USER_UNIT_PATH }
|
|
139
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const logger = require('../logger');
|
|
8
|
+
const paths = require('../paths');
|
|
9
|
+
|
|
10
|
+
const SVC_NAME = 'AdoreMixService';
|
|
11
|
+
|
|
12
|
+
function fillTemplate(workdir) {
|
|
13
|
+
const tplPath = path.join(__dirname, '..', '..', 'templates', 'winservice.js.tmpl');
|
|
14
|
+
let tpl = fs.readFileSync(tplPath, 'utf8');
|
|
15
|
+
return tpl
|
|
16
|
+
.replace(/__WORKDIR__/g, workdir.replace(/\\/g, '\\\\'))
|
|
17
|
+
.replace(/__NODE__/g, paths.nodeExecutable().replace(/\\/g, '\\\\'))
|
|
18
|
+
.replace(/__CLI__/g, paths.cliBin().replace(/\\/g, '\\\\'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureNodeWindows(workdir) {
|
|
22
|
+
try {
|
|
23
|
+
require.resolve('node-windows');
|
|
24
|
+
return true;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
logger.warn('node-windows 未安装,正在尝试本地安装...');
|
|
27
|
+
try {
|
|
28
|
+
execSync('npm install --no-save node-windows', { cwd: workdir, stdio: 'inherit' });
|
|
29
|
+
return true;
|
|
30
|
+
} catch (e2) {
|
|
31
|
+
logger.error('node-windows 安装失败,请手动在工作目录执行 npm install node-windows');
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function install(opts) {
|
|
38
|
+
const workdir = opts.workdir;
|
|
39
|
+
if (!ensureNodeWindows(workdir)) return 1;
|
|
40
|
+
const wrapperPath = path.join(workdir, 'winservice.js');
|
|
41
|
+
const content = fillTemplate(workdir);
|
|
42
|
+
fs.writeFileSync(wrapperPath, content, 'utf8');
|
|
43
|
+
logger.ok(`生成 wrapper ${wrapperPath}`);
|
|
44
|
+
try {
|
|
45
|
+
execSync(`node "${wrapperPath}" install`, { cwd: workdir, stdio: 'inherit' });
|
|
46
|
+
} catch (e) {
|
|
47
|
+
logger.error(`注册失败:${e.message}`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
logger.log('');
|
|
51
|
+
logger.log('查看服务:sc query AdoreMixService');
|
|
52
|
+
logger.log('启动/停止:sc start/stop AdoreMixService');
|
|
53
|
+
logger.log('或:net start/stop AdoreMixService');
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function uninstall(opts) {
|
|
58
|
+
const workdir = opts.workdir;
|
|
59
|
+
const wrapperPath = path.join(workdir, 'winservice.js');
|
|
60
|
+
if (!fs.existsSync(wrapperPath)) {
|
|
61
|
+
logger.warn(`未找到 ${wrapperPath},直接 sc delete`);
|
|
62
|
+
try {
|
|
63
|
+
execSync(`sc delete ${SVC_NAME}`, { stdio: 'inherit' });
|
|
64
|
+
return 0;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
execSync(`node "${wrapperPath}" uninstall`, { cwd: workdir, stdio: 'inherit' });
|
|
71
|
+
} catch (e) {
|
|
72
|
+
logger.error(`卸载失败:${e.message}`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function status() {
|
|
79
|
+
try {
|
|
80
|
+
execSync(`sc query ${SVC_NAME}`, { stdio: 'inherit' });
|
|
81
|
+
return 0;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
logger.warn(`服务未安装或查询失败`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { install, uninstall, status, SVC_NAME };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=AdoreMix Broadcast Server
|
|
3
|
+
Documentation=https://github.com/your-org/adoremix
|
|
4
|
+
After=network.target mysqld.service redis.service
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User=__USER__
|
|
9
|
+
Group=__GROUP__
|
|
10
|
+
WorkingDirectory=__WORKDIR__
|
|
11
|
+
ExecStart=__NODE__ __CLI__ start --daemon --workdir __WORKDIR__
|
|
12
|
+
Restart=on-failure
|
|
13
|
+
RestartSec=5
|
|
14
|
+
StandardOutput=append:__WORKDIR__/logs/svc.log
|
|
15
|
+
StandardError=append:__WORKDIR__/logs/svc.err
|
|
16
|
+
LimitNOFILE=65536
|
|
17
|
+
|
|
18
|
+
[Install]
|
|
19
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// 该文件由 `adoremix service install` 在工作目录生成。
|
|
3
|
+
// 它通过 node-windows 创建/卸载 Windows 服务。
|
|
4
|
+
// 用法:node winservice.js install|uninstall
|
|
5
|
+
|
|
6
|
+
const Service = require('node-windows').Service;
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const WORKDIR = '__WORKDIR__';
|
|
10
|
+
const NODE = '__NODE__';
|
|
11
|
+
const CLI = '__CLI__';
|
|
12
|
+
const SVC_NAME = 'AdoreMixService';
|
|
13
|
+
const SVC_DESC = 'AdoreMix Broadcast Server';
|
|
14
|
+
|
|
15
|
+
const svc = new Service({
|
|
16
|
+
name: SVC_NAME,
|
|
17
|
+
description: SVC_DESC,
|
|
18
|
+
script: CLI,
|
|
19
|
+
execPath: NODE,
|
|
20
|
+
args: ['start', '--daemon', '--workdir', WORKDIR],
|
|
21
|
+
cwd: WORKDIR,
|
|
22
|
+
env: [{
|
|
23
|
+
name: 'NODE_ENV',
|
|
24
|
+
value: 'production'
|
|
25
|
+
}]
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
svc.on('install', () => {
|
|
29
|
+
console.log(`[ok] 服务已安装:${SVC_NAME}`);
|
|
30
|
+
svc.start();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
svc.on('alreadyinstalled', () => {
|
|
34
|
+
console.log(`[warn] 服务已存在:${SVC_NAME}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
svc.on('start', () => {
|
|
38
|
+
console.log(`[ok] 服务已启动:${SVC_NAME}`);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
svc.on('stop', () => {
|
|
42
|
+
console.log(`[ok] 服务已停止:${SVC_NAME}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
svc.on('uninstall', () => {
|
|
46
|
+
console.log(`[ok] 服务已卸载:${SVC_NAME}`);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
svc.on('error', (err) => {
|
|
50
|
+
console.error(`[err] node-windows 错误:${err.message}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const cmd = process.argv[2] || 'install';
|
|
55
|
+
if (cmd === 'install') {
|
|
56
|
+
svc.install();
|
|
57
|
+
} else if (cmd === 'uninstall') {
|
|
58
|
+
svc.uninstall();
|
|
59
|
+
} else if (cmd === 'start') {
|
|
60
|
+
svc.start();
|
|
61
|
+
} else if (cmd === 'stop') {
|
|
62
|
+
svc.stop();
|
|
63
|
+
} else {
|
|
64
|
+
console.error('用法:node winservice.js install|uninstall|start|stop');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|