@mgsoftwarebv/mg-dashboard-mcp 2.1.0 → 2.1.1
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/dist/index.js +169 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -386,12 +386,29 @@ async function attemptVercelSync(appName, environment, knownStageId) {
|
|
|
386
386
|
return `Vercel sync error: ${msg}`;
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
|
+
var SSH_PROXY_SERVER_ID = "03659d55-e194-400d-b82a-bf6457371ded";
|
|
390
|
+
var _proxyConnCache = null;
|
|
391
|
+
async function getProxyConnection() {
|
|
392
|
+
if (_proxyConnCache) return _proxyConnCache;
|
|
393
|
+
const { data, error } = await supabase.from("ssh_server").select("hostname, port, username, password_encrypted, ssh_key_encrypted, ssh_key_passphrase_encrypted").eq("id", SSH_PROXY_SERVER_ID).single();
|
|
394
|
+
if (error || !data) throw new Error("SSH Proxy server not found in database");
|
|
395
|
+
if (!encryptionKey) throw new Error("ENCRYPTION_KEY required to decrypt server credentials");
|
|
396
|
+
_proxyConnCache = {
|
|
397
|
+
hostname: data.hostname,
|
|
398
|
+
port: data.port || 22,
|
|
399
|
+
username: data.username,
|
|
400
|
+
password: data.password_encrypted ? decrypt(data.password_encrypted) : void 0,
|
|
401
|
+
privateKey: data.ssh_key_encrypted ? decrypt(data.ssh_key_encrypted) : void 0,
|
|
402
|
+
passphrase: data.ssh_key_passphrase_encrypted ? decrypt(data.ssh_key_passphrase_encrypted) : void 0
|
|
403
|
+
};
|
|
404
|
+
return _proxyConnCache;
|
|
405
|
+
}
|
|
389
406
|
async function getServerConnection(serverId) {
|
|
390
407
|
assertServerAccess(serverId);
|
|
391
|
-
const { data, error } = await supabase.from("ssh_server").select("hostname, port, username, password_encrypted, ssh_key_encrypted, ssh_key_passphrase_encrypted").eq("id", serverId).single();
|
|
408
|
+
const { data, error } = await supabase.from("ssh_server").select("hostname, port, username, password_encrypted, ssh_key_encrypted, ssh_key_passphrase_encrypted, allowed_ssh_ips").eq("id", serverId).single();
|
|
392
409
|
if (error || !data) throw new Error(`Server not found: ${serverId}`);
|
|
393
410
|
if (!encryptionKey) throw new Error("ENCRYPTION_KEY required to decrypt server credentials");
|
|
394
|
-
|
|
411
|
+
const conn = {
|
|
395
412
|
hostname: data.hostname,
|
|
396
413
|
port: data.port || 22,
|
|
397
414
|
username: data.username,
|
|
@@ -399,8 +416,12 @@ async function getServerConnection(serverId) {
|
|
|
399
416
|
privateKey: data.ssh_key_encrypted ? decrypt(data.ssh_key_encrypted) : void 0,
|
|
400
417
|
passphrase: data.ssh_key_passphrase_encrypted ? decrypt(data.ssh_key_passphrase_encrypted) : void 0
|
|
401
418
|
};
|
|
419
|
+
const needsProxy = data.allowed_ssh_ips !== null && serverId !== SSH_PROXY_SERVER_ID;
|
|
420
|
+
const proxy = needsProxy ? await getProxyConnection() : void 0;
|
|
421
|
+
return { conn, proxy };
|
|
402
422
|
}
|
|
403
|
-
async function sshExec(opts, command) {
|
|
423
|
+
async function sshExec(opts, command, proxy) {
|
|
424
|
+
if (proxy) return sshExecViaProxy(proxy, opts, command);
|
|
404
425
|
return new Promise((resolve) => {
|
|
405
426
|
const ssh = new Client();
|
|
406
427
|
let stdout = "";
|
|
@@ -459,6 +480,98 @@ async function sshExec(opts, command) {
|
|
|
459
480
|
});
|
|
460
481
|
});
|
|
461
482
|
}
|
|
483
|
+
function sshExecViaProxy(proxyOpts, targetOpts, command) {
|
|
484
|
+
return new Promise((resolve) => {
|
|
485
|
+
const proxyClient = new Client();
|
|
486
|
+
let done = false;
|
|
487
|
+
const timeout = targetOpts.timeout || 6e4;
|
|
488
|
+
const timer = setTimeout(() => {
|
|
489
|
+
if (!done) {
|
|
490
|
+
done = true;
|
|
491
|
+
proxyClient.end();
|
|
492
|
+
resolve({ stdout: "", stderr: "SSH proxy command timeout", exitCode: -1 });
|
|
493
|
+
}
|
|
494
|
+
}, timeout);
|
|
495
|
+
const cleanup = () => {
|
|
496
|
+
clearTimeout(timer);
|
|
497
|
+
proxyClient.end();
|
|
498
|
+
};
|
|
499
|
+
proxyClient.on("ready", () => {
|
|
500
|
+
proxyClient.forwardOut("127.0.0.1", 0, targetOpts.hostname, targetOpts.port, (err, tunnel) => {
|
|
501
|
+
if (err) {
|
|
502
|
+
if (!done) {
|
|
503
|
+
done = true;
|
|
504
|
+
cleanup();
|
|
505
|
+
resolve({ stdout: "", stderr: err.message, exitCode: -1 });
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const targetClient = new Client();
|
|
510
|
+
let stdout = "";
|
|
511
|
+
let stderr = "";
|
|
512
|
+
targetClient.on("ready", () => {
|
|
513
|
+
targetClient.exec(command, (execErr, stream) => {
|
|
514
|
+
if (execErr) {
|
|
515
|
+
if (!done) {
|
|
516
|
+
done = true;
|
|
517
|
+
targetClient.end();
|
|
518
|
+
cleanup();
|
|
519
|
+
resolve({ stdout, stderr, exitCode: -1 });
|
|
520
|
+
}
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
stream.on("data", (d) => {
|
|
524
|
+
stdout += d.toString();
|
|
525
|
+
});
|
|
526
|
+
stream.stderr.on("data", (d) => {
|
|
527
|
+
stderr += d.toString();
|
|
528
|
+
});
|
|
529
|
+
stream.on("close", (code) => {
|
|
530
|
+
if (!done) {
|
|
531
|
+
done = true;
|
|
532
|
+
targetClient.end();
|
|
533
|
+
cleanup();
|
|
534
|
+
resolve({ stdout, stderr, exitCode: code ?? 0 });
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
targetClient.on("error", (targetErr) => {
|
|
540
|
+
if (!done) {
|
|
541
|
+
done = true;
|
|
542
|
+
targetClient.end();
|
|
543
|
+
cleanup();
|
|
544
|
+
resolve({ stdout, stderr: targetErr.message, exitCode: -1 });
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
targetClient.connect({
|
|
548
|
+
sock: tunnel,
|
|
549
|
+
username: targetOpts.username,
|
|
550
|
+
password: targetOpts.password,
|
|
551
|
+
privateKey: targetOpts.privateKey,
|
|
552
|
+
passphrase: targetOpts.passphrase,
|
|
553
|
+
readyTimeout: timeout
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
proxyClient.on("error", (err) => {
|
|
558
|
+
if (!done) {
|
|
559
|
+
done = true;
|
|
560
|
+
cleanup();
|
|
561
|
+
resolve({ stdout: "", stderr: err.message, exitCode: -1 });
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
proxyClient.connect({
|
|
565
|
+
host: proxyOpts.hostname,
|
|
566
|
+
port: proxyOpts.port,
|
|
567
|
+
username: proxyOpts.username,
|
|
568
|
+
password: proxyOpts.password,
|
|
569
|
+
privateKey: proxyOpts.privateKey,
|
|
570
|
+
passphrase: proxyOpts.passphrase,
|
|
571
|
+
readyTimeout: proxyOpts.timeout || 3e4
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
}
|
|
462
575
|
function sanitizePath(path) {
|
|
463
576
|
let normalized = path.replace(/\\/g, "/").replace(/\0/g, "");
|
|
464
577
|
const parts = normalized.split("/");
|
|
@@ -743,7 +856,7 @@ function assertAllowedLogPath(filePath) {
|
|
|
743
856
|
throw new Error(`Path not allowed. Must be under: ${ALLOWED_LOG_PREFIXES.join(", ")}`);
|
|
744
857
|
}
|
|
745
858
|
}
|
|
746
|
-
async function discoverSiteDatabases(conn) {
|
|
859
|
+
async function discoverSiteDatabases(conn, proxy) {
|
|
747
860
|
const script = `
|
|
748
861
|
check_dir() {
|
|
749
862
|
local base="$1" root="$2"
|
|
@@ -780,7 +893,7 @@ for dir in /var/www/*/; do
|
|
|
780
893
|
done
|
|
781
894
|
done
|
|
782
895
|
`.trim();
|
|
783
|
-
const result = await sshExec(conn, script);
|
|
896
|
+
const result = await sshExec(conn, script, proxy);
|
|
784
897
|
const sites = [];
|
|
785
898
|
for (const line of result.stdout.split("\n")) {
|
|
786
899
|
if (!line.trim()) continue;
|
|
@@ -864,9 +977,9 @@ DB_PORT=\${DB_PORT:-3306}
|
|
|
864
977
|
mysql --user="$DB_USER" --password="$DB_PASS" --host="$DB_HOST" --port="$DB_PORT" -t -e '${safeQuery}' "$DB_NAME" 2>&1 | grep -v "\\[Warning\\].*password"
|
|
865
978
|
`.trim();
|
|
866
979
|
}
|
|
867
|
-
async function execSiteMysql(conn, sitePath, query) {
|
|
980
|
+
async function execSiteMysql(conn, sitePath, query, proxy) {
|
|
868
981
|
const cmd = buildSiteMysqlCommand(sitePath, query);
|
|
869
|
-
const result = await sshExec(conn, cmd);
|
|
982
|
+
const result = await sshExec(conn, cmd, proxy);
|
|
870
983
|
const output = (result.stdout || "").trim();
|
|
871
984
|
if (output.startsWith("ERROR: No database config found")) {
|
|
872
985
|
throw new Error(output);
|
|
@@ -1338,9 +1451,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1338
1451
|
return { content: [{ type: "text", text: lines.length ? lines.join("\n") : "No servers found" }] };
|
|
1339
1452
|
}
|
|
1340
1453
|
case "server-status": {
|
|
1341
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1454
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1342
1455
|
const cmd = 'echo "=== UPTIME ===" && uptime && echo "=== DISK ===" && df -h --total && echo "=== MEMORY ===" && free -h && echo "=== LOAD ===" && cat /proc/loadavg';
|
|
1343
|
-
const result = await sshExec(conn, cmd);
|
|
1456
|
+
const result = await sshExec(conn, cmd, proxy);
|
|
1344
1457
|
const output = result.exitCode === 0 ? result.stdout : `Exit ${result.exitCode}
|
|
1345
1458
|
${result.stdout}
|
|
1346
1459
|
${result.stderr}`;
|
|
@@ -1350,9 +1463,9 @@ ${result.stderr}`;
|
|
|
1350
1463
|
case "ssh-execute": {
|
|
1351
1464
|
const command = String(a.command);
|
|
1352
1465
|
assertSafeCommand(command);
|
|
1353
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1466
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1354
1467
|
if (a.timeout) conn.timeout = Number(a.timeout);
|
|
1355
|
-
const result = await sshExec(conn, command);
|
|
1468
|
+
const result = await sshExec(conn, command, proxy);
|
|
1356
1469
|
const output = [`Exit code: ${result.exitCode}`];
|
|
1357
1470
|
if (result.stdout) output.push(`--- stdout ---
|
|
1358
1471
|
${result.stdout}`);
|
|
@@ -1361,45 +1474,45 @@ ${result.stderr}`);
|
|
|
1361
1474
|
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
1362
1475
|
}
|
|
1363
1476
|
case "server-reboot": {
|
|
1364
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1365
|
-
const result = await sshExec(conn, "sudo reboot");
|
|
1477
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1478
|
+
const result = await sshExec(conn, "sudo reboot", proxy);
|
|
1366
1479
|
return { content: [{ type: "text", text: result.exitCode === 0 ? "Reboot command sent. Server will be unavailable shortly." : `Reboot failed: ${result.stderr}` }] };
|
|
1367
1480
|
}
|
|
1368
1481
|
case "server-restart-service": {
|
|
1369
1482
|
const service = String(a.serviceName).replace(/[^a-zA-Z0-9._@-]/g, "");
|
|
1370
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1371
|
-
const result = await sshExec(conn, `sudo systemctl restart ${service}
|
|
1483
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1484
|
+
const result = await sshExec(conn, `sudo systemctl restart ${service}`, proxy);
|
|
1372
1485
|
if (result.exitCode === 0) {
|
|
1373
|
-
const status = await sshExec(conn, `sudo systemctl is-active ${service}
|
|
1486
|
+
const status = await sshExec(conn, `sudo systemctl is-active ${service}`, proxy);
|
|
1374
1487
|
return { content: [{ type: "text", text: `Service "${service}" restarted. Status: ${status.stdout.trim()}` }] };
|
|
1375
1488
|
}
|
|
1376
1489
|
return { content: [{ type: "text", text: `Failed to restart "${service}": ${result.stderr}` }] };
|
|
1377
1490
|
}
|
|
1378
|
-
// ----- SFTP -----
|
|
1491
|
+
// ----- SFTP (no proxy support yet — direct connection only) -----
|
|
1379
1492
|
case "sftp-list": {
|
|
1380
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1493
|
+
const { conn } = await getServerConnection(String(a.serverId));
|
|
1381
1494
|
const listing = await sftpReaddir(conn, String(a.path || "/"));
|
|
1382
1495
|
return { content: [{ type: "text", text: listing }] };
|
|
1383
1496
|
}
|
|
1384
1497
|
case "sftp-read": {
|
|
1385
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1498
|
+
const { conn } = await getServerConnection(String(a.serverId));
|
|
1386
1499
|
const content = await sftpRead(conn, String(a.path));
|
|
1387
1500
|
return { content: [{ type: "text", text: content }] };
|
|
1388
1501
|
}
|
|
1389
1502
|
case "sftp-write": {
|
|
1390
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1503
|
+
const { conn } = await getServerConnection(String(a.serverId));
|
|
1391
1504
|
const result = await sftpWrite(conn, String(a.path), String(a.content));
|
|
1392
1505
|
return { content: [{ type: "text", text: result }] };
|
|
1393
1506
|
}
|
|
1394
1507
|
case "sftp-delete": {
|
|
1395
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1508
|
+
const { conn } = await getServerConnection(String(a.serverId));
|
|
1396
1509
|
const result = await sftpDelete(conn, String(a.path));
|
|
1397
1510
|
return { content: [{ type: "text", text: result }] };
|
|
1398
1511
|
}
|
|
1399
1512
|
// ----- Docker -----
|
|
1400
1513
|
case "docker-list": {
|
|
1401
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1402
|
-
const result = await sshExec(conn, 'docker ps -a --format "table {{.Names}} {{.Image}} {{.Status}} {{.Ports}}"');
|
|
1514
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1515
|
+
const result = await sshExec(conn, 'docker ps -a --format "table {{.Names}} {{.Image}} {{.Status}} {{.Ports}}"', proxy);
|
|
1403
1516
|
return { content: [{ type: "text", text: result.exitCode === 0 ? result.stdout : `Error: ${result.stderr}` }] };
|
|
1404
1517
|
}
|
|
1405
1518
|
case "docker-action": {
|
|
@@ -1408,22 +1521,22 @@ ${result.stderr}`);
|
|
|
1408
1521
|
if (!["start", "stop", "restart", "remove"].includes(action)) {
|
|
1409
1522
|
throw new Error(`Invalid action: ${action}. Use start, stop, restart, or remove.`);
|
|
1410
1523
|
}
|
|
1411
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1524
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1412
1525
|
const dockerCmd = action === "remove" ? `docker rm -f ${container}` : `docker ${action} ${container}`;
|
|
1413
|
-
const result = await sshExec(conn, dockerCmd);
|
|
1526
|
+
const result = await sshExec(conn, dockerCmd, proxy);
|
|
1414
1527
|
return { content: [{ type: "text", text: result.exitCode === 0 ? `Container "${container}" ${action}ed successfully` : `Error: ${result.stderr}` }] };
|
|
1415
1528
|
}
|
|
1416
1529
|
case "docker-logs": {
|
|
1417
1530
|
const container = String(a.containerName).replace(/[^a-zA-Z0-9._-]/g, "");
|
|
1418
1531
|
const lines = Number(a.lines) || 100;
|
|
1419
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1420
|
-
const result = await sshExec(conn, `docker logs --tail ${lines} ${container} 2>&1
|
|
1532
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1533
|
+
const result = await sshExec(conn, `docker logs --tail ${lines} ${container} 2>&1`, proxy);
|
|
1421
1534
|
return { content: [{ type: "text", text: result.exitCode === 0 ? result.stdout : `Error: ${result.stderr}` }] };
|
|
1422
1535
|
}
|
|
1423
1536
|
// ----- Database -----
|
|
1424
1537
|
case "db-discover": {
|
|
1425
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1426
|
-
const sites = await discoverSiteDatabases(conn);
|
|
1538
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1539
|
+
const sites = await discoverSiteDatabases(conn, proxy);
|
|
1427
1540
|
if (!sites.length) {
|
|
1428
1541
|
return { content: [{ type: "text", text: "No web applications with database configs found in /var/www/" }] };
|
|
1429
1542
|
}
|
|
@@ -1433,26 +1546,27 @@ ${result.stderr}`);
|
|
|
1433
1546
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1434
1547
|
}
|
|
1435
1548
|
case "db-tables": {
|
|
1436
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1549
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1437
1550
|
const sql = "SELECT TABLE_NAME, ENGINE, TABLE_ROWS, ROUND(DATA_LENGTH/1024/1024, 2) AS `Size (MB)`, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() ORDER BY TABLE_NAME";
|
|
1438
|
-
const output = await execSiteMysql(conn, String(a.sitePath), sql);
|
|
1551
|
+
const output = await execSiteMysql(conn, String(a.sitePath), sql, proxy);
|
|
1439
1552
|
return { content: [{ type: "text", text: output || "No tables found" }] };
|
|
1440
1553
|
}
|
|
1441
1554
|
case "db-describe": {
|
|
1442
1555
|
const table = String(a.table).replace(/[^a-zA-Z0-9_]/g, "");
|
|
1443
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1556
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1444
1557
|
const output = await execSiteMysql(
|
|
1445
1558
|
conn,
|
|
1446
1559
|
String(a.sitePath),
|
|
1447
|
-
`DESCRIBE \`${table}\`; SHOW INDEX FROM \`${table}
|
|
1560
|
+
`DESCRIBE \`${table}\`; SHOW INDEX FROM \`${table}\``,
|
|
1561
|
+
proxy
|
|
1448
1562
|
);
|
|
1449
1563
|
return { content: [{ type: "text", text: output }] };
|
|
1450
1564
|
}
|
|
1451
1565
|
case "db-query": {
|
|
1452
1566
|
const query = String(a.query).trim();
|
|
1453
1567
|
assertSafeSql(query);
|
|
1454
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1455
|
-
const output = await execSiteMysql(conn, String(a.sitePath), query);
|
|
1568
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1569
|
+
const output = await execSiteMysql(conn, String(a.sitePath), query, proxy);
|
|
1456
1570
|
return { content: [{ type: "text", text: output || "Query executed successfully (no output)" }] };
|
|
1457
1571
|
}
|
|
1458
1572
|
// ----- Env Config -----
|
|
@@ -1550,7 +1664,7 @@ ${result.stderr}`);
|
|
|
1550
1664
|
}
|
|
1551
1665
|
// ----- Cache Purge -----
|
|
1552
1666
|
case "cache-purge": {
|
|
1553
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1667
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1554
1668
|
conn.timeout = 12e4;
|
|
1555
1669
|
const script = `
|
|
1556
1670
|
R=""
|
|
@@ -1631,13 +1745,13 @@ else
|
|
|
1631
1745
|
fi
|
|
1632
1746
|
echo -e "$R"
|
|
1633
1747
|
`.trim();
|
|
1634
|
-
const result = await sshExec(conn, script);
|
|
1748
|
+
const result = await sshExec(conn, script, proxy);
|
|
1635
1749
|
const output = (result.stdout || "").trim();
|
|
1636
1750
|
return { content: [{ type: "text", text: output || "Cache purge completed (no output)" }] };
|
|
1637
1751
|
}
|
|
1638
1752
|
// ----- Log Reading -----
|
|
1639
1753
|
case "log-list": {
|
|
1640
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1754
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1641
1755
|
const script = `
|
|
1642
1756
|
{
|
|
1643
1757
|
[ -d /usr/local/lsws/logs ] && find /usr/local/lsws/logs -maxdepth 2 -name "*.log" -type f 2>/dev/null
|
|
@@ -1663,7 +1777,7 @@ echo -e "$R"
|
|
|
1663
1777
|
echo "$f $HR $MOD"
|
|
1664
1778
|
done
|
|
1665
1779
|
`.trim();
|
|
1666
|
-
const result = await sshExec(conn, script);
|
|
1780
|
+
const result = await sshExec(conn, script, proxy);
|
|
1667
1781
|
return { content: [{ type: "text", text: result.stdout || "No log files found" }] };
|
|
1668
1782
|
}
|
|
1669
1783
|
case "log-read": {
|
|
@@ -1671,9 +1785,9 @@ done
|
|
|
1671
1785
|
assertAllowedLogPath(logPath);
|
|
1672
1786
|
const lineCount = Math.min(Math.max(Number(a.lines) || 100, 1), 500);
|
|
1673
1787
|
const filter = a.filter ? String(a.filter).replace(/'/g, "'\\''") : "";
|
|
1674
|
-
const conn = await getServerConnection(String(a.serverId));
|
|
1788
|
+
const { conn, proxy } = await getServerConnection(String(a.serverId));
|
|
1675
1789
|
const cmd = filter ? `grep '${filter}' '${logPath}' 2>/dev/null | tail -n ${lineCount}` : `tail -n ${lineCount} '${logPath}' 2>/dev/null`;
|
|
1676
|
-
const result = await sshExec(conn, cmd);
|
|
1790
|
+
const result = await sshExec(conn, cmd, proxy);
|
|
1677
1791
|
if (result.exitCode !== 0 && !result.stdout) {
|
|
1678
1792
|
throw new Error(result.stderr || `Failed to read log: ${logPath}`);
|
|
1679
1793
|
}
|
|
@@ -1681,21 +1795,21 @@ done
|
|
|
1681
1795
|
}
|
|
1682
1796
|
// ----- Cron Jobs -----
|
|
1683
1797
|
case "cron-list": {
|
|
1684
|
-
const conn = await getServerConnection(a.serverId);
|
|
1798
|
+
const { conn, proxy } = await getServerConnection(a.serverId);
|
|
1685
1799
|
const user = a.user ? String(a.user) : null;
|
|
1686
1800
|
const parts = [];
|
|
1687
1801
|
if (user) {
|
|
1688
|
-
const result = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null || echo '(no crontab for ${user})'
|
|
1802
|
+
const result = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null || echo '(no crontab for ${user})'`, proxy);
|
|
1689
1803
|
parts.push(`## Crontab for ${user}
|
|
1690
1804
|
${result.stdout}`);
|
|
1691
1805
|
} else {
|
|
1692
|
-
const rootCron = await sshExec(conn, `crontab -l 2>/dev/null || echo '(no crontab for root)'
|
|
1806
|
+
const rootCron = await sshExec(conn, `crontab -l 2>/dev/null || echo '(no crontab for root)'`, proxy);
|
|
1693
1807
|
parts.push(`## Root crontab
|
|
1694
1808
|
${rootCron.stdout}`);
|
|
1695
|
-
const wwwCron = await sshExec(conn, `crontab -l -u www-data 2>/dev/null || echo '(no crontab for www-data)'
|
|
1809
|
+
const wwwCron = await sshExec(conn, `crontab -l -u www-data 2>/dev/null || echo '(no crontab for www-data)'`, proxy);
|
|
1696
1810
|
parts.push(`## www-data crontab
|
|
1697
1811
|
${wwwCron.stdout}`);
|
|
1698
|
-
const cronD = await sshExec(conn, `for f in /etc/cron.d/*; do [ -f "$f" ] && echo "--- $f ---" && cat "$f" && echo; done 2>/dev/null || echo '(no files in /etc/cron.d/)'
|
|
1812
|
+
const cronD = await sshExec(conn, `for f in /etc/cron.d/*; do [ -f "$f" ] && echo "--- $f ---" && cat "$f" && echo; done 2>/dev/null || echo '(no files in /etc/cron.d/)'`, proxy);
|
|
1699
1813
|
parts.push(`## /etc/cron.d/
|
|
1700
1814
|
${cronD.stdout}`);
|
|
1701
1815
|
const summary = await sshExec(conn, [
|
|
@@ -1704,13 +1818,13 @@ ${cronD.stdout}`);
|
|
|
1704
1818
|
`echo "cron.daily: $(ls /etc/cron.daily/ 2>/dev/null | wc -l) scripts"`,
|
|
1705
1819
|
`echo "cron.weekly: $(ls /etc/cron.weekly/ 2>/dev/null | wc -l) scripts"`,
|
|
1706
1820
|
`echo "cron.monthly: $(ls /etc/cron.monthly/ 2>/dev/null | wc -l) scripts"`
|
|
1707
|
-
].join(" && "));
|
|
1821
|
+
].join(" && "), proxy);
|
|
1708
1822
|
parts.push(summary.stdout);
|
|
1709
1823
|
}
|
|
1710
1824
|
return { content: [{ type: "text", text: parts.join("\n\n") }] };
|
|
1711
1825
|
}
|
|
1712
1826
|
case "cron-add": {
|
|
1713
|
-
const conn = await getServerConnection(a.serverId);
|
|
1827
|
+
const { conn, proxy } = await getServerConnection(a.serverId);
|
|
1714
1828
|
const user = a.user ? String(a.user) : "root";
|
|
1715
1829
|
const schedule = String(a.schedule).trim();
|
|
1716
1830
|
const command = String(a.command).trim();
|
|
@@ -1720,7 +1834,7 @@ ${cronD.stdout}`);
|
|
|
1720
1834
|
}
|
|
1721
1835
|
const entry = commentText ? `# ${commentText}
|
|
1722
1836
|
${schedule} ${command}` : `${schedule} ${command}`;
|
|
1723
|
-
const result = await sshExec(conn, `(crontab -l -u ${user} 2>/dev/null; echo '${entry.replace(/'/g, "'\\''")}') | crontab -u ${user}
|
|
1837
|
+
const result = await sshExec(conn, `(crontab -l -u ${user} 2>/dev/null; echo '${entry.replace(/'/g, "'\\''")}') | crontab -u ${user} -`, proxy);
|
|
1724
1838
|
if (result.exitCode !== 0) {
|
|
1725
1839
|
throw new Error(result.stderr || "Failed to add cron job");
|
|
1726
1840
|
}
|
|
@@ -1728,13 +1842,13 @@ ${schedule} ${command}` : `${schedule} ${command}`;
|
|
|
1728
1842
|
${schedule} ${command}` }] };
|
|
1729
1843
|
}
|
|
1730
1844
|
case "cron-remove": {
|
|
1731
|
-
const conn = await getServerConnection(a.serverId);
|
|
1845
|
+
const { conn, proxy } = await getServerConnection(a.serverId);
|
|
1732
1846
|
const user = a.user ? String(a.user) : "root";
|
|
1733
1847
|
const match = String(a.commandMatch).trim();
|
|
1734
1848
|
if (!match) {
|
|
1735
1849
|
throw new Error("commandMatch is required");
|
|
1736
1850
|
}
|
|
1737
|
-
const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null
|
|
1851
|
+
const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null`, proxy);
|
|
1738
1852
|
const lines = current.stdout.split("\n");
|
|
1739
1853
|
const before = lines.length;
|
|
1740
1854
|
const filtered = lines.filter((line) => {
|
|
@@ -1746,21 +1860,21 @@ ${schedule} ${command}` }] };
|
|
|
1746
1860
|
return { content: [{ type: "text", text: `No cron entries found matching "${match}"` }] };
|
|
1747
1861
|
}
|
|
1748
1862
|
const escapedCrontab = filtered.join("\n").replace(/'/g, "'\\''");
|
|
1749
|
-
const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user}
|
|
1863
|
+
const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user} -`, proxy);
|
|
1750
1864
|
if (result.exitCode !== 0) {
|
|
1751
1865
|
throw new Error(result.stderr || "Failed to update crontab");
|
|
1752
1866
|
}
|
|
1753
1867
|
return { content: [{ type: "text", text: `Removed ${removed} cron entry/entries matching "${match}" from ${user} crontab` }] };
|
|
1754
1868
|
}
|
|
1755
1869
|
case "cron-toggle": {
|
|
1756
|
-
const conn = await getServerConnection(a.serverId);
|
|
1870
|
+
const { conn, proxy } = await getServerConnection(a.serverId);
|
|
1757
1871
|
const user = a.user ? String(a.user) : "root";
|
|
1758
1872
|
const match = String(a.commandMatch).trim();
|
|
1759
1873
|
const enable = Boolean(a.enable);
|
|
1760
1874
|
if (!match) {
|
|
1761
1875
|
throw new Error("commandMatch is required");
|
|
1762
1876
|
}
|
|
1763
|
-
const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null
|
|
1877
|
+
const current = await sshExec(conn, `crontab -l -u ${user} 2>/dev/null`, proxy);
|
|
1764
1878
|
const lines = current.stdout.split("\n");
|
|
1765
1879
|
let toggled = 0;
|
|
1766
1880
|
const updated = lines.map((line) => {
|
|
@@ -1779,7 +1893,7 @@ ${schedule} ${command}` }] };
|
|
|
1779
1893
|
return { content: [{ type: "text", text: `No ${state} cron entries found matching "${match}"` }] };
|
|
1780
1894
|
}
|
|
1781
1895
|
const escapedCrontab = updated.join("\n").replace(/'/g, "'\\''");
|
|
1782
|
-
const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user}
|
|
1896
|
+
const result = await sshExec(conn, `echo '${escapedCrontab}' | crontab -u ${user} -`, proxy);
|
|
1783
1897
|
if (result.exitCode !== 0) {
|
|
1784
1898
|
throw new Error(result.stderr || "Failed to update crontab");
|
|
1785
1899
|
}
|