ezpm2gui 1.3.2 → 1.5.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/README.md +295 -294
- package/bin/ezpm2gui.js +8 -8
- package/bin/ezpm2gui.ts +51 -51
- package/bin/generate-ecosystem.js +35 -35
- package/bin/generate-ecosystem.ts +56 -56
- package/dist/index.js +1 -1
- package/dist/server/config/project-configs.json +236 -236
- package/dist/server/index.js +256 -83
- package/dist/server/routes/deployApplication.js +6 -5
- package/dist/server/routes/logStreaming.js +20 -13
- package/dist/server/routes/modules.js +89 -69
- package/dist/server/routes/remoteConnections.js +279 -40
- package/dist/server/routes/updates.d.ts +3 -0
- package/dist/server/routes/updates.js +135 -0
- package/dist/server/utils/encryption.js +0 -12
- package/dist/server/utils/pm2-connection.d.ts +1 -1
- package/dist/server/utils/pm2-connection.js +1 -3
- package/dist/server/utils/remote-connection.d.ts +36 -3
- package/dist/server/utils/remote-connection.js +307 -79
- package/package.json +73 -69
- package/scripts/postinstall.js +36 -36
- package/src/client/build/asset-manifest.json +6 -6
- package/src/client/build/favicon.ico +2 -2
- package/src/client/build/index.html +1 -1
- package/src/client/build/logo192.svg +7 -7
- package/src/client/build/logo512.svg +7 -7
- package/src/client/build/manifest.json +24 -24
- package/src/client/build/static/css/main.2d095544.css +5 -0
- package/src/client/build/static/css/main.2d095544.css.map +1 -0
- package/src/client/build/static/js/main.17e17668.js +3 -0
- package/src/client/build/static/js/main.17e17668.js.map +1 -0
- package/dist/server/config/cron-jobs.json +0 -18
- package/dist/server/config/cron-scripts/6d8d5e1d-2bc8-463f-82a6-6c294f2b9dbe.sh +0 -2
- package/dist/server/config/remote-connections.json +0 -22
- package/dist/server/logs/deployment.log +0 -12
- package/dist/server/utils/dialog.d.ts +0 -1
- package/dist/server/utils/dialog.js +0 -16
- package/dist/server/utils/upload.d.ts +0 -3
- package/dist/server/utils/upload.js +0 -39
- package/src/client/build/static/css/main.d46bc75c.css +0 -5
- package/src/client/build/static/css/main.d46bc75c.css.map +0 -1
- package/src/client/build/static/js/main.b0e1c9b1.js +0 -3
- package/src/client/build/static/js/main.b0e1c9b1.js.map +0 -1
- /package/src/client/build/static/js/{main.b0e1c9b1.js.LICENSE.txt → main.17e17668.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const express_1 = require("express");
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const https_1 = __importDefault(require("https"));
|
|
11
|
+
// @group Utilities : Read the current installed version from package.json
|
|
12
|
+
const getCurrentVersion = () => {
|
|
13
|
+
try {
|
|
14
|
+
// Try the package root (works when run from source or dist)
|
|
15
|
+
const candidates = [
|
|
16
|
+
path_1.default.resolve(__dirname, '../../../package.json'),
|
|
17
|
+
path_1.default.resolve(__dirname, '../../package.json'),
|
|
18
|
+
path_1.default.resolve(process.cwd(), 'package.json'),
|
|
19
|
+
];
|
|
20
|
+
for (const p of candidates) {
|
|
21
|
+
if (fs_1.default.existsSync(p)) {
|
|
22
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(p, 'utf8'));
|
|
23
|
+
if (pkg.name === 'ezpm2gui')
|
|
24
|
+
return pkg.version;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// fall through
|
|
30
|
+
}
|
|
31
|
+
return '0.0.0';
|
|
32
|
+
};
|
|
33
|
+
// @group Utilities : Fetch latest version info from npm registry (no external deps)
|
|
34
|
+
const fetchNpmLatest = () => new Promise((resolve, reject) => {
|
|
35
|
+
const req = https_1.default.get('https://registry.npmjs.org/ezpm2gui/latest', { headers: { Accept: 'application/json' } }, (res) => {
|
|
36
|
+
let data = '';
|
|
37
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
38
|
+
res.on('end', () => {
|
|
39
|
+
var _a;
|
|
40
|
+
try {
|
|
41
|
+
const json = JSON.parse(data);
|
|
42
|
+
resolve({
|
|
43
|
+
version: json.version,
|
|
44
|
+
description: json.description,
|
|
45
|
+
publishedAt: (_a = json.time) === null || _a === void 0 ? void 0 : _a.modified,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
reject(new Error('Failed to parse npm registry response'));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
req.on('error', reject);
|
|
54
|
+
req.setTimeout(8000, () => { req.destroy(); reject(new Error('npm registry request timed out')); });
|
|
55
|
+
});
|
|
56
|
+
// @group Utilities : Compare semver strings — returns true if b is newer than a
|
|
57
|
+
const isNewer = (current, latest) => {
|
|
58
|
+
const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
|
|
59
|
+
const [cMaj, cMin, cPat] = parse(current);
|
|
60
|
+
const [lMaj, lMin, lPat] = parse(latest);
|
|
61
|
+
if (lMaj !== cMaj)
|
|
62
|
+
return lMaj > cMaj;
|
|
63
|
+
if (lMin !== cMin)
|
|
64
|
+
return lMin > cMin;
|
|
65
|
+
return lPat > cPat;
|
|
66
|
+
};
|
|
67
|
+
const router = (0, express_1.Router)();
|
|
68
|
+
// @group CheckUpdate : GET /api/update/check — returns current vs latest npm version
|
|
69
|
+
router.get('/check', async (_req, res) => {
|
|
70
|
+
try {
|
|
71
|
+
const currentVersion = getCurrentVersion();
|
|
72
|
+
const { version: latestVersion, publishedAt } = await fetchNpmLatest();
|
|
73
|
+
const updateAvailable = isNewer(currentVersion, latestVersion);
|
|
74
|
+
const result = {
|
|
75
|
+
currentVersion,
|
|
76
|
+
latestVersion,
|
|
77
|
+
updateAvailable,
|
|
78
|
+
publishedAt,
|
|
79
|
+
};
|
|
80
|
+
res.json({ success: true, data: result });
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error('Update check failed:', err);
|
|
84
|
+
res.status(503).json({ success: false, error: err.message || 'Failed to reach npm registry' });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// @group InstallUpdate : POST /api/update/install — installs ezpm2gui@latest globally
|
|
88
|
+
// Streams progress lines as newline-delimited JSON (ndjson) so the client can read incrementally.
|
|
89
|
+
router.post('/install', (req, res) => {
|
|
90
|
+
res.setHeader('Content-Type', 'application/x-ndjson');
|
|
91
|
+
res.setHeader('Transfer-Encoding', 'chunked');
|
|
92
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
93
|
+
res.flushHeaders();
|
|
94
|
+
const send = (type, message) => {
|
|
95
|
+
res.write(JSON.stringify({ type, message }) + '\n');
|
|
96
|
+
};
|
|
97
|
+
send('log', 'Starting update — running npm install -g ezpm2gui@latest...');
|
|
98
|
+
// Use `npm` with execFile for safety — no shell injection possible
|
|
99
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
100
|
+
const child = (0, child_process_1.spawn)(npmCmd, ['install', '-g', 'ezpm2gui@latest'], {
|
|
101
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
102
|
+
shell: false,
|
|
103
|
+
});
|
|
104
|
+
child.stdout.on('data', (chunk) => {
|
|
105
|
+
chunk.toString().split('\n').filter(Boolean).forEach((line) => send('log', line));
|
|
106
|
+
});
|
|
107
|
+
child.stderr.on('data', (chunk) => {
|
|
108
|
+
// npm writes progress to stderr — treat as log unless it's an actual error keyword
|
|
109
|
+
const text = chunk.toString();
|
|
110
|
+
const isError = /^npm (ERR|error)/i.test(text.trim());
|
|
111
|
+
text.split('\n').filter(Boolean).forEach((line) => send(isError ? 'error' : 'log', line));
|
|
112
|
+
});
|
|
113
|
+
child.on('close', (code) => {
|
|
114
|
+
if (code === 0) {
|
|
115
|
+
send('done', 'Update installed successfully. Reload the page to use the new frontend. Restart the server to apply backend changes.');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
send('fail', `npm exited with code ${code}. Update may have failed.`);
|
|
119
|
+
}
|
|
120
|
+
res.end();
|
|
121
|
+
});
|
|
122
|
+
child.on('error', (err) => {
|
|
123
|
+
send('fail', `Failed to run npm: ${err.message}`);
|
|
124
|
+
res.end();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
// @group RestartServer : POST /api/update/restart — graceful server restart (relies on process manager to respawn)
|
|
128
|
+
router.post('/restart', (_req, res) => {
|
|
129
|
+
res.json({ success: true, message: 'Server will restart in 1 second.' });
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
console.log('[ezpm2gui] Graceful restart triggered via API.');
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}, 1000);
|
|
134
|
+
});
|
|
135
|
+
exports.default = router;
|
|
@@ -44,29 +44,17 @@ function encrypt(text) {
|
|
|
44
44
|
function decrypt(encryptedText) {
|
|
45
45
|
if (!encryptedText)
|
|
46
46
|
return '';
|
|
47
|
-
// Special handling for manually entered passwords in the config file
|
|
48
|
-
if (encryptedText === '11342b0ca35d70c17955d874e5d4b0a26547521f705a6f74320b5d7bfeb56369') {
|
|
49
|
-
console.log('Using hardcoded credential for specific password hash');
|
|
50
|
-
return 'test1234';
|
|
51
|
-
}
|
|
52
47
|
try {
|
|
53
|
-
console.log(`Attempting to decrypt: ${encryptedText.substring(0, 10)}...`);
|
|
54
48
|
// Ensure key and IV are the correct length
|
|
55
49
|
const key = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_KEY)).digest('base64').slice(0, 32);
|
|
56
50
|
const iv = crypto_1.default.createHash('sha256').update(String(ENCRYPTION_IV)).digest('base64').slice(0, 16);
|
|
57
51
|
const decipher = crypto_1.default.createDecipheriv(ALGORITHM, key, iv);
|
|
58
52
|
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
|
59
53
|
decrypted += decipher.final('utf8');
|
|
60
|
-
console.log('Decryption successful');
|
|
61
54
|
return decrypted;
|
|
62
55
|
}
|
|
63
56
|
catch (error) {
|
|
64
57
|
console.error('Decryption error:', error);
|
|
65
|
-
// For development only - return a value even if decryption fails
|
|
66
|
-
if (encryptedText && encryptedText.length > 10) {
|
|
67
|
-
console.log('Returning fallback credential for development');
|
|
68
|
-
return 'test1234';
|
|
69
|
-
}
|
|
70
58
|
return '';
|
|
71
59
|
}
|
|
72
60
|
}
|
|
@@ -13,4 +13,4 @@ export declare const disconnectFromPM2: () => Promise<void>;
|
|
|
13
13
|
* This will connect to PM2 if needed, run the command,
|
|
14
14
|
* and properly handle the result
|
|
15
15
|
*/
|
|
16
|
-
export declare const executePM2Command: <T>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
|
|
16
|
+
export declare const executePM2Command: <T = any>(command: (callback: (err: Error | null, result?: T) => void) => void) => Promise<T>;
|
|
@@ -103,6 +103,7 @@ exports.disconnectFromPM2 = disconnectFromPM2;
|
|
|
103
103
|
* This will connect to PM2 if needed, run the command,
|
|
104
104
|
* and properly handle the result
|
|
105
105
|
*/
|
|
106
|
+
// @group ConnectionPool : Execute a PM2 command using the shared pooled connection
|
|
106
107
|
const executePM2Command = async (command) => {
|
|
107
108
|
try {
|
|
108
109
|
await (0, exports.connectToPM2)();
|
|
@@ -111,9 +112,6 @@ const executePM2Command = async (command) => {
|
|
|
111
112
|
if (err) {
|
|
112
113
|
reject(err);
|
|
113
114
|
}
|
|
114
|
-
else if (result === undefined) {
|
|
115
|
-
reject(new Error('PM2 command returned undefined result'));
|
|
116
|
-
}
|
|
117
115
|
else {
|
|
118
116
|
resolve(result);
|
|
119
117
|
}
|
|
@@ -54,16 +54,49 @@ export declare class RemoteConnection extends EventEmitter {
|
|
|
54
54
|
* @param forceSudo Whether to force using sudo for this specific command
|
|
55
55
|
*/
|
|
56
56
|
executeCommand(command: string, forceSudo?: boolean): Promise<CommandResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Stream a remote file directly into an HTTP response without buffering.
|
|
59
|
+
*
|
|
60
|
+
* Strategy (tried in order):
|
|
61
|
+
* 1. SFTP createReadStream — best; handles large files, uses file-transfer protocol
|
|
62
|
+
* 2. exec `cat <file>` — current SSH user, direct pipe to response
|
|
63
|
+
* 3. exec `sudo -S cat` — password-based sudo (when password auth is configured)
|
|
64
|
+
* 4. exec `sudo cat` — NOPASSWD sudo (key-based auth where sudo needs no password)
|
|
65
|
+
*/
|
|
66
|
+
private static readonly SHELL_UNSAFE;
|
|
67
|
+
private static validateRemotePath;
|
|
68
|
+
streamFileToResponse(remotePath: string, res: import('express').Response, fileName: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Stream a remote .gz file to an HTTP response, decompressing it on the fly.
|
|
71
|
+
* Uses SFTP + local zlib.createGunzip() pipeline — no server-side buffering.
|
|
72
|
+
* Falls back to exec `zcat` if SFTP is unavailable.
|
|
73
|
+
*/
|
|
74
|
+
streamGzFileToResponse(remotePath: string, res: import('express').Response, fileName: string): Promise<void>;
|
|
57
75
|
/**
|
|
58
76
|
* Check if PM2 is installed on the remote server
|
|
59
77
|
* Uses multiple detection methods for better reliability
|
|
60
78
|
*/
|
|
61
79
|
checkPM2Installation(): Promise<boolean>;
|
|
62
80
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
81
|
+
* Ordered list of command builders tried when resolving the pm2 binary.
|
|
82
|
+
* The index of the first successful builder is cached so that subsequent
|
|
83
|
+
* calls (including streaming commands) reuse the same invocation.
|
|
84
|
+
*/
|
|
85
|
+
private static readonly PM2_BUILDERS;
|
|
86
|
+
/** Index into PM2_BUILDERS of the invocation that actually works on this host. -1 = not yet resolved. */
|
|
87
|
+
private resolvedBuilderIndex;
|
|
88
|
+
/**
|
|
89
|
+
* Execute a PM2 command with proper PATH handling.
|
|
90
|
+
* The first successful invocation is cached so later calls (and streaming
|
|
91
|
+
* commands) reuse the same shell/binary path without re-probing.
|
|
92
|
+
*/
|
|
93
|
+
executePM2Command(pm2Args: string): Promise<CommandResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Build a pm2 command string using the already-resolved invocation pattern.
|
|
96
|
+
* Falls back to bash -li -c if the resolver has not yet run.
|
|
97
|
+
* Use this when you need the raw command string for a streaming shell exec.
|
|
65
98
|
*/
|
|
66
|
-
|
|
99
|
+
buildPM2StreamCommand(pm2Args: string): string;
|
|
67
100
|
/**
|
|
68
101
|
* Get PM2 processes from the remote server
|
|
69
102
|
*/
|