@drocketxx/pm2me 1.1.1 → 1.1.4
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/README.md +22 -12
- package/backend/routes/api.js +3 -3
- package/backend/services/gitService.js +6 -6
- package/backend/services/systemService.js +3 -3
- package/bin/pm2me.js +41 -8
- package/package.json +58 -58
package/README.md
CHANGED
|
@@ -10,32 +10,42 @@ PM2Me lets you **deploy apps directly from a GitHub repository**, manage PM2 pro
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## 🚀
|
|
13
|
+
## 🚀 Recommended Installation
|
|
14
14
|
|
|
15
|
-
The
|
|
15
|
+
The standard way to use PM2Me is to install it globally via npm:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
#
|
|
18
|
+
# Install globally
|
|
19
19
|
npm install -g @drocketxx/pm2me
|
|
20
|
-
|
|
21
|
-
# 2. Run the server
|
|
22
|
-
pm2me
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
Open your browser at: **http://localhost:12345**
|
|
26
|
-
|
|
27
22
|
---
|
|
28
23
|
|
|
29
|
-
##
|
|
24
|
+
## 📋 Commands
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
### Standard Execution
|
|
27
|
+
```bash
|
|
28
|
+
# Run the server in foreground
|
|
29
|
+
pm2me
|
|
30
|
+
|
|
31
|
+
# Run on a custom port
|
|
32
|
+
pm2me --port 8080
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Background Service (Recommended)
|
|
36
|
+
Manage PM2Me as a system service using these commands:
|
|
32
37
|
|
|
33
38
|
```bash
|
|
34
|
-
# Install as a background service (using PM2)
|
|
39
|
+
# Install & start as a background service (using PM2)
|
|
35
40
|
pm2me service install
|
|
36
41
|
|
|
37
|
-
#
|
|
42
|
+
# Uninstall/Remove the service
|
|
38
43
|
pm2me service uninstall
|
|
44
|
+
|
|
45
|
+
# Control the service
|
|
46
|
+
pm2me service start
|
|
47
|
+
pm2me service stop
|
|
48
|
+
pm2me service restart
|
|
39
49
|
```
|
|
40
50
|
|
|
41
51
|
> **Note:** The `service install` command will register PM2Me as `pm2me-server` in PM2 and attempt to set up system startup.
|
package/backend/routes/api.js
CHANGED
|
@@ -45,7 +45,7 @@ router.get('/pm2/version-check', async (req, res) => {
|
|
|
45
45
|
// Check globally installed PM2 version via npm (avoids local node_modules/.bin/pm2)
|
|
46
46
|
let installedVersion = null;
|
|
47
47
|
try {
|
|
48
|
-
const { stdout } = await execAsync('npm list -g pm2 --depth=0 --json');
|
|
48
|
+
const { stdout } = await execAsync('npm list -g pm2 --depth=0 --json', { windowsHide: true });
|
|
49
49
|
const parsed = JSON.parse(stdout);
|
|
50
50
|
installedVersion = parsed?.dependencies?.pm2?.version || null;
|
|
51
51
|
} catch {
|
|
@@ -404,7 +404,7 @@ export const performDeployment = async (appId, io) => {
|
|
|
404
404
|
await setPipelineState('building');
|
|
405
405
|
logProcess('Executing Build Script', true);
|
|
406
406
|
await new Promise((resolve, reject) => {
|
|
407
|
-
const child = exec(normalizedScript, { cwd: targetPath, maxBuffer: 10 * 1024 * 1024 });
|
|
407
|
+
const child = exec(normalizedScript, { cwd: targetPath, maxBuffer: 10 * 1024 * 1024, windowsHide: true });
|
|
408
408
|
child.stdout.on('data', data => { io.emit(`deploy-log-${appId}`, data.toString()); fs.appendFileSync(logFilePath, data); });
|
|
409
409
|
child.stderr.on('data', data => { io.emit(`deploy-log-${appId}`, data.toString()); fs.appendFileSync(logFilePath, data); });
|
|
410
410
|
child.on('close', code => {
|
|
@@ -1023,7 +1023,7 @@ router.post('/system/update', async (req, res) => {
|
|
|
1023
1023
|
cmd = 'git pull origin main && npm run build';
|
|
1024
1024
|
cwd = path.resolve(__dirname, '../..');
|
|
1025
1025
|
}
|
|
1026
|
-
const { stdout, stderr } = await execAsync(cmd, { cwd }).catch(err => ({
|
|
1026
|
+
const { stdout, stderr } = await execAsync(cmd, { cwd, windowsHide: true }).catch(err => ({
|
|
1027
1027
|
stdout: '', stderr: err.message
|
|
1028
1028
|
}));
|
|
1029
1029
|
const output = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
@@ -28,9 +28,9 @@ export const syncRepo = async (repoUrl, targetPath, branchName, token = null) =>
|
|
|
28
28
|
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
29
29
|
}
|
|
30
30
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
31
|
-
const git = simpleGit().env(envOptions);
|
|
31
|
+
const git = simpleGit({ spawnOptions: { windowsHide: true } }).env(envOptions);
|
|
32
32
|
await git.clone(finalUrl, targetPath, ['--branch', branchName]);
|
|
33
|
-
const gitLocal = simpleGit(targetPath);
|
|
33
|
+
const gitLocal = simpleGit(targetPath, { spawnOptions: { windowsHide: true } });
|
|
34
34
|
const log = await gitLocal.log(['-1']);
|
|
35
35
|
const commitHash = log.latest.hash;
|
|
36
36
|
const commitMessage = log.latest.message;
|
|
@@ -45,7 +45,7 @@ export const syncRepo = async (repoUrl, targetPath, branchName, token = null) =>
|
|
|
45
45
|
}
|
|
46
46
|
} else {
|
|
47
47
|
try {
|
|
48
|
-
const git = simpleGit(targetPath).env(envOptions);
|
|
48
|
+
const git = simpleGit(targetPath, { spawnOptions: { windowsHide: true } }).env(envOptions);
|
|
49
49
|
await git.remote(['set-url', 'origin', finalUrl]);
|
|
50
50
|
await git.fetch('origin', branchName);
|
|
51
51
|
await git.checkout(branchName);
|
|
@@ -68,7 +68,7 @@ export const syncRepo = async (repoUrl, targetPath, branchName, token = null) =>
|
|
|
68
68
|
|
|
69
69
|
export const getBranches = async (repoUrl, token = null) => {
|
|
70
70
|
const finalUrl = getFinalUrl(repoUrl, token);
|
|
71
|
-
const git = simpleGit().env({ ...process.env, GIT_TERMINAL_PROMPT: '0' });
|
|
71
|
+
const git = simpleGit({ spawnOptions: { windowsHide: true } }).env({ ...process.env, GIT_TERMINAL_PROMPT: '0' });
|
|
72
72
|
const branches = await git.listRemote(['--heads', finalUrl]);
|
|
73
73
|
// Parse branch output, simplistic split
|
|
74
74
|
return branches.split('\n').filter(Boolean).map(line => line.split('refs/heads/')[1]);
|
|
@@ -82,7 +82,7 @@ export const getBehindCount = async (repoUrl, targetPath, branchName, token = nu
|
|
|
82
82
|
if (!isGitRepo) return 0;
|
|
83
83
|
|
|
84
84
|
const finalUrl = getFinalUrl(repoUrl, token);
|
|
85
|
-
const git = simpleGit(targetPath).env({ ...process.env, GIT_TERMINAL_PROMPT: '0' });
|
|
85
|
+
const git = simpleGit(targetPath, { spawnOptions: { windowsHide: true } }).env({ ...process.env, GIT_TERMINAL_PROMPT: '0' });
|
|
86
86
|
|
|
87
87
|
try {
|
|
88
88
|
await git.remote(['set-url', 'origin', finalUrl]);
|
|
@@ -96,7 +96,7 @@ export const getBehindCount = async (repoUrl, targetPath, branchName, token = nu
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
export const checkout = async (targetPath, ref) => {
|
|
99
|
-
const git = simpleGit(targetPath).env({ ...process.env, GIT_TERMINAL_PROMPT: '0' });
|
|
99
|
+
const git = simpleGit(targetPath, { spawnOptions: { windowsHide: true } }).env({ ...process.env, GIT_TERMINAL_PROMPT: '0' });
|
|
100
100
|
try {
|
|
101
101
|
await git.checkout(ref);
|
|
102
102
|
const log = await git.log(['-1']);
|
|
@@ -32,7 +32,7 @@ export const getSystemStats = async () => {
|
|
|
32
32
|
const promises = [];
|
|
33
33
|
|
|
34
34
|
// 1. CPU Usage (Every time)
|
|
35
|
-
const cpuPromise = execPromise('powershell -Command "(Get-Counter \'\\Processor(_Total)\\% Processor Time\' -ErrorAction SilentlyContinue).CounterSamples.CookedValue"')
|
|
35
|
+
const cpuPromise = execPromise('powershell -Command "(Get-Counter \'\\Processor(_Total)\\% Processor Time\' -ErrorAction SilentlyContinue).CounterSamples.CookedValue"', { windowsHide: true })
|
|
36
36
|
.then(({ stdout }) => {
|
|
37
37
|
const realCpu = parseFloat(stdout.trim());
|
|
38
38
|
if (!isNaN(realCpu)) cpuUsage = realCpu;
|
|
@@ -41,7 +41,7 @@ export const getSystemStats = async () => {
|
|
|
41
41
|
promises.push(cpuPromise);
|
|
42
42
|
|
|
43
43
|
// 2. Network Stats (Every time)
|
|
44
|
-
const netPromise = execPromise('powershell -Command "Get-NetAdapterStatistics | Select-Object ReceivedBytes, SentBytes | ConvertTo-Json"')
|
|
44
|
+
const netPromise = execPromise('powershell -Command "Get-NetAdapterStatistics | Select-Object ReceivedBytes, SentBytes | ConvertTo-Json"', { windowsHide: true })
|
|
45
45
|
.then(({ stdout }) => {
|
|
46
46
|
const netData = JSON.parse(stdout);
|
|
47
47
|
let totalRx = 0;
|
|
@@ -67,7 +67,7 @@ export const getSystemStats = async () => {
|
|
|
67
67
|
|
|
68
68
|
// 3. Disk Stats (Cached)
|
|
69
69
|
if (now - cachedDiskStats.lastFetch > DISK_CACHE_TTL) {
|
|
70
|
-
const diskPromise = execPromise('powershell -Command "Get-Volume -DriveLetter C | Select-Object Size, SizeRemaining | ConvertTo-Json"')
|
|
70
|
+
const diskPromise = execPromise('powershell -Command "Get-Volume -DriveLetter C | Select-Object Size, SizeRemaining | ConvertTo-Json"', { windowsHide: true })
|
|
71
71
|
.then(({ stdout }) => {
|
|
72
72
|
const data = JSON.parse(stdout);
|
|
73
73
|
if (data.Size > 0) {
|
package/bin/pm2me.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* pm2me (Run in foreground)
|
|
6
6
|
* pm2me service install (Run in background via PM2 + set boot startup)
|
|
7
7
|
* pm2me service uninstall (Remove from PM2 background)
|
|
8
|
+
* pm2me service start (Start background service)
|
|
9
|
+
* pm2me service stop (Stop background service)
|
|
10
|
+
* pm2me service restart (Restart background service)
|
|
8
11
|
*/
|
|
9
12
|
import path from 'path';
|
|
10
13
|
import { fileURLToPath } from 'url';
|
|
@@ -22,6 +25,8 @@ const args = process.argv.slice(2);
|
|
|
22
25
|
// ── Handle Service Commands ───────────────────────────────────────────────────
|
|
23
26
|
if (args[0] === 'service') {
|
|
24
27
|
const action = args[1];
|
|
28
|
+
const SERVICE_NAME = 'pm2me-server';
|
|
29
|
+
|
|
25
30
|
if (action === 'install') {
|
|
26
31
|
console.log('[pm2me] Installing background service...');
|
|
27
32
|
try {
|
|
@@ -30,21 +35,21 @@ if (args[0] === 'service') {
|
|
|
30
35
|
const homeDir = path.join(os.homedir(), '.pm2me');
|
|
31
36
|
const dbPath = path.join(homeDir, 'database.json');
|
|
32
37
|
|
|
33
|
-
console.log(`[pm2me] Starting via PM2 as '
|
|
34
|
-
execSync(`pm2 start "${appPath}" --name
|
|
38
|
+
console.log(`[pm2me] Starting via PM2 as '${SERVICE_NAME}'...`);
|
|
39
|
+
execSync(`pm2 start "${appPath}" --name ${SERVICE_NAME} --env PM2ME_DB_PATH="${dbPath}"`, { stdio: 'inherit', windowsHide: true });
|
|
35
40
|
|
|
36
41
|
console.log(`[pm2me] Saving PM2 process list...`);
|
|
37
|
-
execSync(`pm2 save`, { stdio: 'inherit' });
|
|
42
|
+
execSync(`pm2 save`, { stdio: 'inherit', windowsHide: true });
|
|
38
43
|
|
|
39
44
|
if (isWindows) {
|
|
40
|
-
console.log(`\n[pm2me] 💡 Windows Info: '
|
|
45
|
+
console.log(`\n[pm2me] 💡 Windows Info: '${SERVICE_NAME}' is now running in the background.`);
|
|
41
46
|
console.log(`[pm2me] To make it start automatically on Windows restart, we recommend using 'pm2-windows-startup':`);
|
|
42
47
|
console.log(` npm install -g pm2-windows-startup`);
|
|
43
48
|
console.log(` pm2-startup install`);
|
|
44
49
|
} else {
|
|
45
50
|
console.log(`[pm2me] Setting up boot startup...`);
|
|
46
51
|
try {
|
|
47
|
-
execSync(`pm2 startup`, { stdio: 'inherit' });
|
|
52
|
+
execSync(`pm2 startup`, { stdio: 'inherit', windowsHide: true });
|
|
48
53
|
console.log(`[pm2me] Done! If you see a command above, please copy and run it to finalize startup.`);
|
|
49
54
|
} catch (e) {
|
|
50
55
|
console.log(`[pm2me] 'pm2 startup' might need manual execution. Check instructions above.`);
|
|
@@ -60,16 +65,43 @@ if (args[0] === 'service') {
|
|
|
60
65
|
} else if (action === 'uninstall') {
|
|
61
66
|
console.log('[pm2me] Uninstalling background service...');
|
|
62
67
|
try {
|
|
63
|
-
execSync(`pm2 delete
|
|
64
|
-
execSync(`pm2 save`, { stdio: 'inherit' });
|
|
68
|
+
execSync(`pm2 delete ${SERVICE_NAME}`, { stdio: 'inherit', windowsHide: true });
|
|
69
|
+
execSync(`pm2 save`, { stdio: 'inherit', windowsHide: true });
|
|
65
70
|
console.log(`[pm2me] Service uninstalled successfully.`);
|
|
66
71
|
process.exit(0);
|
|
67
72
|
} catch (err) {
|
|
68
73
|
console.error(`[pm2me] Failed to uninstall service:`, err.message);
|
|
69
74
|
process.exit(1);
|
|
70
75
|
}
|
|
76
|
+
} else if (action === 'start') {
|
|
77
|
+
console.log(`[pm2me] Starting service '${SERVICE_NAME}'...`);
|
|
78
|
+
try {
|
|
79
|
+
execSync(`pm2 start ${SERVICE_NAME}`, { stdio: 'inherit', windowsHide: true });
|
|
80
|
+
process.exit(0);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(`[pm2me] Failed to start service:`, err.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
} else if (action === 'stop') {
|
|
86
|
+
console.log(`[pm2me] Stopping service '${SERVICE_NAME}'...`);
|
|
87
|
+
try {
|
|
88
|
+
execSync(`pm2 stop ${SERVICE_NAME}`, { stdio: 'inherit', windowsHide: true });
|
|
89
|
+
process.exit(0);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(`[pm2me] Failed to stop service:`, err.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
} else if (action === 'restart') {
|
|
95
|
+
console.log(`[pm2me] Restarting service '${SERVICE_NAME}'...`);
|
|
96
|
+
try {
|
|
97
|
+
execSync(`pm2 restart ${SERVICE_NAME}`, { stdio: 'inherit', windowsHide: true });
|
|
98
|
+
process.exit(0);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(`[pm2me] Failed to restart service:`, err.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
71
103
|
} else {
|
|
72
|
-
console.log(`Usage: pm2me service [install|uninstall]`);
|
|
104
|
+
console.log(`Usage: pm2me service [install|uninstall|start|stop|restart]`);
|
|
73
105
|
process.exit(1);
|
|
74
106
|
}
|
|
75
107
|
}
|
|
@@ -113,6 +145,7 @@ console.log(`[pm2me] Using database: ${dbPath}`);
|
|
|
113
145
|
const server = spawn('node', ['app.js'], {
|
|
114
146
|
cwd: backendDir,
|
|
115
147
|
stdio: 'inherit',
|
|
148
|
+
windowsHide: true,
|
|
116
149
|
env: {
|
|
117
150
|
...process.env,
|
|
118
151
|
PORT: String(PORT),
|
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@drocketxx/pm2me",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "PM2 Deployment and Management System — Web UI for PM2 process management",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "backend/app.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"pm2me": "./bin/pm2me.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin/",
|
|
12
|
-
"backend/",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"dependencies": {
|
|
16
|
-
"bcrypt": "^6.0.0",
|
|
17
|
-
"connect-history-api-fallback": "^2.0.0",
|
|
18
|
-
"cors": "^2.8.6",
|
|
19
|
-
"dotenv": "^17.3.1",
|
|
20
|
-
"express": "^5.2.1",
|
|
21
|
-
"jsonwebtoken": "^9.0.3",
|
|
22
|
-
"lowdb": "^7.0.1",
|
|
23
|
-
"morgan": "^1.10.1",
|
|
24
|
-
"pm2": "^6.0.14",
|
|
25
|
-
"simple-git": "^3.32.3",
|
|
26
|
-
"socket.io": "^4.8.3"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=18"
|
|
30
|
-
},
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "https://github.com/drocketxx/PM2Me.git"
|
|
34
|
-
},
|
|
35
|
-
"homepage": "https://github.com/drocketxx/PM2Me",
|
|
36
|
-
"keywords": [
|
|
37
|
-
"pm2",
|
|
38
|
-
"deploy",
|
|
39
|
-
"management",
|
|
40
|
-
"nginx",
|
|
41
|
-
"web-ui"
|
|
42
|
-
],
|
|
43
|
-
"author": "drocketxx",
|
|
44
|
-
"license": "ISC",
|
|
45
|
-
"scripts": {
|
|
46
|
-
"dev:frontend": "cd frontend && npm run dev",
|
|
47
|
-
"dev:backend": "cd backend && npm run dev",
|
|
48
|
-
"dev:concurrent": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
|
|
49
|
-
"build": "cd frontend && npm run build",
|
|
50
|
-
"dev": "npm run build && cd backend && npm run dev",
|
|
51
|
-
"install:all": "npm install && cd frontend && npm install && cd ../backend && npm install",
|
|
52
|
-
"pw": "cd backend && node scripts/change-password.js",
|
|
53
|
-
"start": "node bin/pm2me.js"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"concurrently": "^8.2.2",
|
|
57
|
-
"nodemon": "^3.1.14"
|
|
58
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@drocketxx/pm2me",
|
|
3
|
+
"version": "1.1.4",
|
|
4
|
+
"description": "PM2 Deployment and Management System — Web UI for PM2 process management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "backend/app.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pm2me": "./bin/pm2me.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"backend/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"bcrypt": "^6.0.0",
|
|
17
|
+
"connect-history-api-fallback": "^2.0.0",
|
|
18
|
+
"cors": "^2.8.6",
|
|
19
|
+
"dotenv": "^17.3.1",
|
|
20
|
+
"express": "^5.2.1",
|
|
21
|
+
"jsonwebtoken": "^9.0.3",
|
|
22
|
+
"lowdb": "^7.0.1",
|
|
23
|
+
"morgan": "^1.10.1",
|
|
24
|
+
"pm2": "^6.0.14",
|
|
25
|
+
"simple-git": "^3.32.3",
|
|
26
|
+
"socket.io": "^4.8.3"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/drocketxx/PM2Me.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/drocketxx/PM2Me",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"pm2",
|
|
38
|
+
"deploy",
|
|
39
|
+
"management",
|
|
40
|
+
"nginx",
|
|
41
|
+
"web-ui"
|
|
42
|
+
],
|
|
43
|
+
"author": "drocketxx",
|
|
44
|
+
"license": "ISC",
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev:frontend": "cd frontend && npm run dev",
|
|
47
|
+
"dev:backend": "cd backend && npm run dev",
|
|
48
|
+
"dev:concurrent": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
|
|
49
|
+
"build": "cd frontend && npm run build",
|
|
50
|
+
"dev": "npm run build && cd backend && npm run dev",
|
|
51
|
+
"install:all": "npm install && cd frontend && npm install && cd ../backend && npm install",
|
|
52
|
+
"pw": "cd backend && node scripts/change-password.js",
|
|
53
|
+
"start": "node bin/pm2me.js"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"concurrently": "^8.2.2",
|
|
57
|
+
"nodemon": "^3.1.14"
|
|
58
|
+
}
|
|
59
59
|
}
|