@spssrl/sps-ps-agent 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/sps-ps-agent.js +146 -0
- package/package.json +35 -0
- package/src/config.js +89 -0
- package/src/middleware/auth.js +19 -0
- package/src/routes/logs.js +44 -0
- package/src/routes/processes.js +56 -0
- package/src/routes/system.js +66 -0
- package/src/server.js +69 -0
- package/src/services/pm2.js +114 -0
- package/templates/sps-ps-agent.service +14 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const config = require('../src/config');
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const command = process.argv[2];
|
|
8
|
+
|
|
9
|
+
switch (command) {
|
|
10
|
+
case 'init':
|
|
11
|
+
handleInit();
|
|
12
|
+
break;
|
|
13
|
+
case 'start':
|
|
14
|
+
handleStart();
|
|
15
|
+
break;
|
|
16
|
+
case 'stop':
|
|
17
|
+
handleStop();
|
|
18
|
+
break;
|
|
19
|
+
case 'status':
|
|
20
|
+
handleStatus();
|
|
21
|
+
break;
|
|
22
|
+
case 'token':
|
|
23
|
+
handleToken();
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
showHelp();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function handleInit() {
|
|
30
|
+
const force = process.argv.includes('--force');
|
|
31
|
+
const result = config.init({ force });
|
|
32
|
+
|
|
33
|
+
if (result.created) {
|
|
34
|
+
console.log('\n SPS-PS Agent initialized!');
|
|
35
|
+
console.log(` Config: ${config.CONFIG_FILE}`);
|
|
36
|
+
console.log(` Port: ${result.config.port}`);
|
|
37
|
+
console.log(` Token: ${result.config.token}`);
|
|
38
|
+
console.log(` Server: ${result.config.serverName}`);
|
|
39
|
+
console.log('\n Copy the token above into your CRM server configuration.');
|
|
40
|
+
console.log(' Start the agent with: sps-ps-agent start\n');
|
|
41
|
+
} else {
|
|
42
|
+
console.log('\n Config already exists. Use --force to overwrite.');
|
|
43
|
+
console.log(` Config: ${config.CONFIG_FILE}`);
|
|
44
|
+
console.log(` Token: ${result.config.token}\n`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleStart() {
|
|
49
|
+
const cfg = config.load();
|
|
50
|
+
if (!cfg) {
|
|
51
|
+
console.error('No configuration found. Run: sps-ps-agent init');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const existingPid = config.readPid();
|
|
56
|
+
if (existingPid && config.isRunning(existingPid)) {
|
|
57
|
+
console.log(`Agent already running (PID: ${existingPid})`);
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (process.argv.includes('--daemon') || process.argv.includes('-d')) {
|
|
62
|
+
// Daemonize
|
|
63
|
+
const serverPath = path.join(__dirname, '..', 'src', 'server.js');
|
|
64
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
65
|
+
detached: true,
|
|
66
|
+
stdio: 'ignore',
|
|
67
|
+
env: { ...process.env }
|
|
68
|
+
});
|
|
69
|
+
child.unref();
|
|
70
|
+
config.savePid(child.pid);
|
|
71
|
+
console.log(`\n SPS-PS Agent started in background (PID: ${child.pid})`);
|
|
72
|
+
console.log(` Port: ${cfg.port}`);
|
|
73
|
+
console.log(` Stop with: sps-ps-agent stop\n`);
|
|
74
|
+
} else {
|
|
75
|
+
// Foreground
|
|
76
|
+
require('../src/server').start();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleStop() {
|
|
81
|
+
const pid = config.readPid();
|
|
82
|
+
if (!pid) {
|
|
83
|
+
console.log('Agent is not running (no PID file).');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!config.isRunning(pid)) {
|
|
88
|
+
console.log(`Agent is not running (stale PID: ${pid}). Cleaning up.`);
|
|
89
|
+
config.removePid();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 'SIGTERM');
|
|
95
|
+
console.log(`Agent stopped (PID: ${pid}).`);
|
|
96
|
+
config.removePid();
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`Failed to stop agent: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handleStatus() {
|
|
103
|
+
const cfg = config.load();
|
|
104
|
+
if (!cfg) {
|
|
105
|
+
console.log('\n Not initialized. Run: sps-ps-agent init\n');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const pid = config.readPid();
|
|
110
|
+
const running = pid && config.isRunning(pid);
|
|
111
|
+
|
|
112
|
+
console.log('\n SPS-PS Agent Status');
|
|
113
|
+
console.log(` Server: ${cfg.serverName}`);
|
|
114
|
+
console.log(` Port: ${cfg.port}`);
|
|
115
|
+
console.log(` Status: ${running ? `Running (PID: ${pid})` : 'Stopped'}`);
|
|
116
|
+
console.log(` Config: ${config.CONFIG_FILE}\n`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleToken() {
|
|
120
|
+
const cfg = config.load();
|
|
121
|
+
if (!cfg) {
|
|
122
|
+
console.log('Not initialized. Run: sps-ps-agent init');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
console.log(cfg.token);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function showHelp() {
|
|
129
|
+
const version = require('../package.json').version;
|
|
130
|
+
console.log(`
|
|
131
|
+
SPS-PS Agent v${version}
|
|
132
|
+
|
|
133
|
+
Usage: sps-ps-agent <command>
|
|
134
|
+
|
|
135
|
+
Commands:
|
|
136
|
+
init Initialize configuration (generates token)
|
|
137
|
+
start Start the agent (add -d for daemon mode)
|
|
138
|
+
stop Stop the agent
|
|
139
|
+
status Show agent status
|
|
140
|
+
token Display the authentication token
|
|
141
|
+
|
|
142
|
+
Options:
|
|
143
|
+
init --force Overwrite existing configuration
|
|
144
|
+
start -d Run as background daemon
|
|
145
|
+
`);
|
|
146
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spssrl/sps-ps-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SPS Process Services - Lightweight PM2 monitoring agent",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sps-ps-agent": "./bin/sps-ps-agent.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/server.js",
|
|
11
|
+
"dev": "node src/server.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["pm2", "process", "monitoring", "agent", "sps"],
|
|
14
|
+
"author": "SPS Srl",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"src/",
|
|
22
|
+
"templates/",
|
|
23
|
+
"package.json"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"cors": "^2.8.5",
|
|
30
|
+
"express": "^4.21.0",
|
|
31
|
+
"pm2": "^5.4.0",
|
|
32
|
+
"systeminformation": "^5.23.0",
|
|
33
|
+
"uuid": "^10.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { v4: uuidv4 } = require('uuid');
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), '.sps-ps-agent');
|
|
7
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
8
|
+
const PID_FILE = path.join(CONFIG_DIR, 'agent.pid');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
port: 9876,
|
|
12
|
+
token: null,
|
|
13
|
+
serverName: os.hostname(),
|
|
14
|
+
bindAddress: '0.0.0.0',
|
|
15
|
+
corsOrigins: ['*']
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function ensureConfigDir() {
|
|
19
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
20
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function load() {
|
|
25
|
+
ensureConfigDir();
|
|
26
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function save(config) {
|
|
34
|
+
ensureConfigDir();
|
|
35
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function init(options = {}) {
|
|
39
|
+
const existing = load();
|
|
40
|
+
if (existing && !options.force) {
|
|
41
|
+
return { created: false, config: existing };
|
|
42
|
+
}
|
|
43
|
+
const config = {
|
|
44
|
+
...DEFAULT_CONFIG,
|
|
45
|
+
token: uuidv4(),
|
|
46
|
+
...options
|
|
47
|
+
};
|
|
48
|
+
save(config);
|
|
49
|
+
return { created: true, config };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function savePid(pid) {
|
|
53
|
+
ensureConfigDir();
|
|
54
|
+
fs.writeFileSync(PID_FILE, String(pid), 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readPid() {
|
|
58
|
+
if (!fs.existsSync(PID_FILE)) return null;
|
|
59
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
60
|
+
return isNaN(pid) ? null : pid;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function removePid() {
|
|
64
|
+
if (fs.existsSync(PID_FILE)) {
|
|
65
|
+
fs.unlinkSync(PID_FILE);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isRunning(pid) {
|
|
70
|
+
try {
|
|
71
|
+
process.kill(pid, 0);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
CONFIG_DIR,
|
|
80
|
+
CONFIG_FILE,
|
|
81
|
+
PID_FILE,
|
|
82
|
+
load,
|
|
83
|
+
save,
|
|
84
|
+
init,
|
|
85
|
+
savePid,
|
|
86
|
+
readPid,
|
|
87
|
+
removePid,
|
|
88
|
+
isRunning
|
|
89
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const config = require('../config');
|
|
2
|
+
|
|
3
|
+
function authMiddleware(req, res, next) {
|
|
4
|
+
const authHeader = req.headers.authorization;
|
|
5
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
6
|
+
return res.status(401).json({ error: 'Missing or invalid authorization header' });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const token = authHeader.slice(7);
|
|
10
|
+
const cfg = config.load();
|
|
11
|
+
|
|
12
|
+
if (!cfg || token !== cfg.token) {
|
|
13
|
+
return res.status(403).json({ error: 'Invalid token' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
next();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = authMiddleware;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const pm2Service = require('../services/pm2');
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
// GET /api/processes/:name/logs?lines=100&type=out|err|all
|
|
8
|
+
router.get('/:name/logs', async (req, res) => {
|
|
9
|
+
const lines = Math.min(parseInt(req.query.lines) || 100, 5000);
|
|
10
|
+
const type = req.query.type || 'all'; // out, err, all
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const desc = await pm2Service.withConnection(() => pm2Service.describe(req.params.name));
|
|
14
|
+
const env = desc.pm2_env || {};
|
|
15
|
+
const outPath = env.pm_out_log_path;
|
|
16
|
+
const errPath = env.pm_err_log_path;
|
|
17
|
+
|
|
18
|
+
const result = {};
|
|
19
|
+
|
|
20
|
+
if ((type === 'out' || type === 'all') && outPath) {
|
|
21
|
+
result.out = tailFile(outPath, lines);
|
|
22
|
+
}
|
|
23
|
+
if ((type === 'err' || type === 'all') && errPath) {
|
|
24
|
+
result.err = tailFile(errPath, lines);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
res.json({ success: true, process: req.params.name, logs: result });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
res.status(500).json({ error: err.message });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function tailFile(filePath, numLines) {
|
|
34
|
+
try {
|
|
35
|
+
if (!fs.existsSync(filePath)) return '';
|
|
36
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
37
|
+
const allLines = content.split('\n');
|
|
38
|
+
return allLines.slice(-numLines).join('\n');
|
|
39
|
+
} catch {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = router;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const pm2Service = require('../services/pm2');
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
// GET /api/processes - List all PM2 processes
|
|
7
|
+
router.get('/', async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const processes = await pm2Service.withConnection(() => pm2Service.list());
|
|
10
|
+
res.json({ success: true, data: processes });
|
|
11
|
+
} catch (err) {
|
|
12
|
+
res.status(500).json({ error: err.message });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// POST /api/processes/:name/restart
|
|
17
|
+
router.post('/:name/restart', async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = await pm2Service.withConnection(() => pm2Service.restart(req.params.name));
|
|
20
|
+
res.json(result);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
res.status(500).json({ error: err.message });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// POST /api/processes/:name/stop
|
|
27
|
+
router.post('/:name/stop', async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await pm2Service.withConnection(() => pm2Service.stop(req.params.name));
|
|
30
|
+
res.json(result);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
res.status(500).json({ error: err.message });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// POST /api/processes/:name/start
|
|
37
|
+
router.post('/:name/start', async (req, res) => {
|
|
38
|
+
try {
|
|
39
|
+
const result = await pm2Service.withConnection(() => pm2Service.start(req.params.name));
|
|
40
|
+
res.json(result);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
res.status(500).json({ error: err.message });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// DELETE /api/processes/:name
|
|
47
|
+
router.delete('/:name', async (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const result = await pm2Service.withConnection(() => pm2Service.deleteProcess(req.params.name));
|
|
50
|
+
res.json(result);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
res.status(500).json({ error: err.message });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
module.exports = router;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { Router } = require('express');
|
|
2
|
+
const si = require('systeminformation');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
// GET /api/status - Quick health check
|
|
8
|
+
router.get('/status', async (req, res) => {
|
|
9
|
+
res.json({
|
|
10
|
+
success: true,
|
|
11
|
+
agent: 'sps-ps-agent',
|
|
12
|
+
version: require('../../package.json').version,
|
|
13
|
+
hostname: os.hostname(),
|
|
14
|
+
platform: os.platform(),
|
|
15
|
+
uptime: os.uptime(),
|
|
16
|
+
timestamp: new Date().toISOString()
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// GET /api/system - Detailed system info
|
|
21
|
+
router.get('/system', async (req, res) => {
|
|
22
|
+
try {
|
|
23
|
+
const [cpu, mem, disk, osInfo, time] = await Promise.all([
|
|
24
|
+
si.currentLoad(),
|
|
25
|
+
si.mem(),
|
|
26
|
+
si.fsSize(),
|
|
27
|
+
si.osInfo(),
|
|
28
|
+
si.time()
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
res.json({
|
|
32
|
+
success: true,
|
|
33
|
+
data: {
|
|
34
|
+
hostname: os.hostname(),
|
|
35
|
+
platform: osInfo.platform,
|
|
36
|
+
distro: osInfo.distro,
|
|
37
|
+
release: osInfo.release,
|
|
38
|
+
arch: osInfo.arch,
|
|
39
|
+
uptime: os.uptime(),
|
|
40
|
+
cpu: {
|
|
41
|
+
load: Math.round(cpu.currentLoad * 100) / 100,
|
|
42
|
+
cores: os.cpus().length
|
|
43
|
+
},
|
|
44
|
+
memory: {
|
|
45
|
+
total: mem.total,
|
|
46
|
+
used: mem.used,
|
|
47
|
+
free: mem.free,
|
|
48
|
+
usage_percent: Math.round((mem.used / mem.total) * 10000) / 100
|
|
49
|
+
},
|
|
50
|
+
disk: disk.map(d => ({
|
|
51
|
+
fs: d.fs,
|
|
52
|
+
size: d.size,
|
|
53
|
+
used: d.used,
|
|
54
|
+
available: d.available,
|
|
55
|
+
usage_percent: d.use,
|
|
56
|
+
mount: d.mount
|
|
57
|
+
})),
|
|
58
|
+
timestamp: new Date().toISOString()
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
} catch (err) {
|
|
62
|
+
res.status(500).json({ error: err.message });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
module.exports = router;
|
package/src/server.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const cors = require('cors');
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
const authMiddleware = require('./middleware/auth');
|
|
5
|
+
const processRoutes = require('./routes/processes');
|
|
6
|
+
const logRoutes = require('./routes/logs');
|
|
7
|
+
const systemRoutes = require('./routes/system');
|
|
8
|
+
|
|
9
|
+
function createServer() {
|
|
10
|
+
const cfg = config.load();
|
|
11
|
+
if (!cfg) {
|
|
12
|
+
console.error('No configuration found. Run: sps-ps-agent init');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
|
|
18
|
+
app.use(cors({ origin: cfg.corsOrigins || '*' }));
|
|
19
|
+
app.use(express.json());
|
|
20
|
+
|
|
21
|
+
// Public health endpoint (no auth)
|
|
22
|
+
app.get('/api/ping', (req, res) => {
|
|
23
|
+
res.json({ pong: true, timestamp: new Date().toISOString() });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// All other routes require auth
|
|
27
|
+
app.use('/api', authMiddleware);
|
|
28
|
+
|
|
29
|
+
// Routes
|
|
30
|
+
app.use('/api', systemRoutes);
|
|
31
|
+
app.use('/api/processes', processRoutes);
|
|
32
|
+
app.use('/api/processes', logRoutes);
|
|
33
|
+
|
|
34
|
+
return { app, cfg };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function start() {
|
|
38
|
+
const { app, cfg } = createServer();
|
|
39
|
+
const port = cfg.port || 9876;
|
|
40
|
+
const bind = cfg.bindAddress || '0.0.0.0';
|
|
41
|
+
|
|
42
|
+
const server = app.listen(port, bind, () => {
|
|
43
|
+
console.log(`\n SPS-PS Agent v${require('../package.json').version}`);
|
|
44
|
+
console.log(` Server: ${cfg.serverName}`);
|
|
45
|
+
console.log(` Listening: http://${bind}:${port}`);
|
|
46
|
+
console.log(` Token: ${cfg.token.substring(0, 8)}...`);
|
|
47
|
+
console.log('');
|
|
48
|
+
|
|
49
|
+
config.savePid(process.pid);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const shutdown = () => {
|
|
53
|
+
console.log('\nShutting down...');
|
|
54
|
+
config.removePid();
|
|
55
|
+
server.close(() => process.exit(0));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
process.on('SIGINT', shutdown);
|
|
59
|
+
process.on('SIGTERM', shutdown);
|
|
60
|
+
|
|
61
|
+
return server;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { createServer, start };
|
|
65
|
+
|
|
66
|
+
// Direct execution
|
|
67
|
+
if (require.main === module) {
|
|
68
|
+
start();
|
|
69
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const pm2 = require('pm2');
|
|
2
|
+
|
|
3
|
+
function connect() {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
pm2.connect((err) => {
|
|
6
|
+
if (err) reject(err);
|
|
7
|
+
else resolve();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function disconnect() {
|
|
13
|
+
pm2.disconnect();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function list() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
pm2.list((err, list) => {
|
|
19
|
+
if (err) reject(err);
|
|
20
|
+
else resolve(list.map(formatProcess));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function describe(nameOrId) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
pm2.describe(nameOrId, (err, desc) => {
|
|
28
|
+
if (err) reject(err);
|
|
29
|
+
else if (!desc || desc.length === 0) reject(new Error(`Process "${nameOrId}" not found`));
|
|
30
|
+
else resolve(desc[0]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function restart(nameOrId) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
pm2.restart(nameOrId, (err) => {
|
|
38
|
+
if (err) reject(err);
|
|
39
|
+
else resolve({ success: true, action: 'restart', process: nameOrId });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stop(nameOrId) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
pm2.stop(nameOrId, (err) => {
|
|
47
|
+
if (err) reject(err);
|
|
48
|
+
else resolve({ success: true, action: 'stop', process: nameOrId });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function start(nameOrId) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
pm2.restart(nameOrId, (err) => {
|
|
56
|
+
if (err) reject(err);
|
|
57
|
+
else resolve({ success: true, action: 'start', process: nameOrId });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function deleteProcess(nameOrId) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
pm2.delete(nameOrId, (err) => {
|
|
65
|
+
if (err) reject(err);
|
|
66
|
+
else resolve({ success: true, action: 'delete', process: nameOrId });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatProcess(proc) {
|
|
72
|
+
const env = proc.pm2_env || {};
|
|
73
|
+
const monit = proc.monit || {};
|
|
74
|
+
const startedAt = env.pm_uptime || null;
|
|
75
|
+
const uptime = startedAt ? Date.now() - startedAt : 0;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
pm_id: env.pm_id ?? proc.pm_id,
|
|
79
|
+
name: env.name || proc.name,
|
|
80
|
+
status: env.status || 'unknown',
|
|
81
|
+
cpu: monit.cpu || 0,
|
|
82
|
+
memory: monit.memory || 0,
|
|
83
|
+
uptime,
|
|
84
|
+
restarts: env.restart_time || 0,
|
|
85
|
+
pid: proc.pid || 0,
|
|
86
|
+
exec_mode: env.exec_mode || 'fork',
|
|
87
|
+
node_version: env.node_version || process.version,
|
|
88
|
+
script: env.pm_exec_path || '',
|
|
89
|
+
cwd: env.pm_cwd || '',
|
|
90
|
+
created_at: startedAt ? new Date(startedAt).toISOString() : null,
|
|
91
|
+
log_path: env.pm_out_log_path || null,
|
|
92
|
+
error_log_path: env.pm_err_log_path || null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function withConnection(fn) {
|
|
97
|
+
await connect();
|
|
98
|
+
try {
|
|
99
|
+
return await fn();
|
|
100
|
+
} finally {
|
|
101
|
+
disconnect();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
withConnection,
|
|
107
|
+
list,
|
|
108
|
+
describe,
|
|
109
|
+
restart,
|
|
110
|
+
stop,
|
|
111
|
+
start,
|
|
112
|
+
deleteProcess,
|
|
113
|
+
formatProcess
|
|
114
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=SPS Process Services Agent
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
User=root
|
|
8
|
+
ExecStart=/usr/bin/env node /usr/lib/node_modules/sps-ps-agent/src/server.js
|
|
9
|
+
Restart=always
|
|
10
|
+
RestartSec=5
|
|
11
|
+
Environment=NODE_ENV=production
|
|
12
|
+
|
|
13
|
+
[Install]
|
|
14
|
+
WantedBy=multi-user.target
|