@magpiecloud/mags 1.1.0 → 1.2.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/mags.js +89 -2
- package/package.json +1 -1
package/bin/mags.js
CHANGED
|
@@ -6,7 +6,7 @@ const { URL } = require('url');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const readline = require('readline');
|
|
9
|
-
const { exec } = require('child_process');
|
|
9
|
+
const { exec, spawn } = require('child_process');
|
|
10
10
|
|
|
11
11
|
// Config file path
|
|
12
12
|
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.mags');
|
|
@@ -144,6 +144,7 @@ ${colors.bold}Commands:${colors.reset}
|
|
|
144
144
|
logout Remove saved credentials
|
|
145
145
|
whoami Show current authenticated user
|
|
146
146
|
run [options] <script> Execute a script on a microVM
|
|
147
|
+
ssh <job-id> Open SSH session to a running VM
|
|
147
148
|
status <job-id> Get job status
|
|
148
149
|
logs <job-id> Get job logs
|
|
149
150
|
list List recent jobs
|
|
@@ -163,6 +164,7 @@ ${colors.bold}Examples:${colors.reset}
|
|
|
163
164
|
mags run -w myproject 'python3 script.py'
|
|
164
165
|
mags run -p --url 'python3 -m http.server 8080'
|
|
165
166
|
mags run -w webapp -p --url --port 3000 'npm start'
|
|
167
|
+
mags ssh abc123 # SSH into a running VM
|
|
166
168
|
mags status abc123
|
|
167
169
|
mags logs abc123
|
|
168
170
|
mags url abc123 8080
|
|
@@ -492,6 +494,87 @@ async function stopJob(requestId) {
|
|
|
492
494
|
}
|
|
493
495
|
}
|
|
494
496
|
|
|
497
|
+
async function sshToJob(requestId) {
|
|
498
|
+
if (!requestId) {
|
|
499
|
+
log('red', 'Error: Job ID required');
|
|
500
|
+
console.log(`\nUsage: mags ssh <job-id>\n`);
|
|
501
|
+
console.log('Get job IDs with: mags list');
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// First check job status
|
|
506
|
+
log('blue', 'Checking job status...');
|
|
507
|
+
const status = await request('GET', `/api/v1/mags-jobs/${requestId}/status`);
|
|
508
|
+
|
|
509
|
+
if (status.error) {
|
|
510
|
+
log('red', `Error: ${status.error}`);
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (status.status !== 'running' && status.status !== 'sleeping') {
|
|
515
|
+
log('red', `Cannot SSH to job with status: ${status.status}`);
|
|
516
|
+
log('gray', 'Job must be running or sleeping (persistent)');
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Enable SSH access (port 22)
|
|
521
|
+
log('blue', 'Enabling SSH access...');
|
|
522
|
+
const accessResp = await request('POST', `/api/v1/mags-jobs/${requestId}/access`, { port: 22 });
|
|
523
|
+
|
|
524
|
+
if (!accessResp.success) {
|
|
525
|
+
log('red', 'Failed to enable SSH access');
|
|
526
|
+
if (accessResp.error) {
|
|
527
|
+
log('red', accessResp.error);
|
|
528
|
+
}
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const sshHost = accessResp.ssh_host;
|
|
533
|
+
const sshPort = accessResp.ssh_port;
|
|
534
|
+
|
|
535
|
+
if (!sshHost || !sshPort) {
|
|
536
|
+
log('red', 'SSH access enabled but no connection details returned');
|
|
537
|
+
console.log(JSON.stringify(accessResp, null, 2));
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
log('green', `Connecting to ${sshHost}:${sshPort}...`);
|
|
542
|
+
console.log(`${colors.gray}(Use Ctrl+D or 'exit' to disconnect)${colors.reset}\n`);
|
|
543
|
+
|
|
544
|
+
// Spawn SSH process
|
|
545
|
+
const sshArgs = [
|
|
546
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
547
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
548
|
+
'-o', 'LogLevel=ERROR',
|
|
549
|
+
'-p', sshPort.toString(),
|
|
550
|
+
`root@${sshHost}`
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
const ssh = spawn('ssh', sshArgs, {
|
|
554
|
+
stdio: 'inherit' // Inherit stdin/stdout/stderr for interactive session
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
ssh.on('error', (err) => {
|
|
558
|
+
if (err.code === 'ENOENT') {
|
|
559
|
+
log('red', 'SSH client not found. Please install OpenSSH.');
|
|
560
|
+
log('gray', 'On macOS/Linux: ssh is usually pre-installed');
|
|
561
|
+
log('gray', 'On Windows: Install OpenSSH or use WSL');
|
|
562
|
+
} else {
|
|
563
|
+
log('red', `SSH error: ${err.message}`);
|
|
564
|
+
}
|
|
565
|
+
process.exit(1);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
ssh.on('close', (code) => {
|
|
569
|
+
if (code === 0) {
|
|
570
|
+
log('green', '\nSSH session ended');
|
|
571
|
+
} else {
|
|
572
|
+
log('yellow', `\nSSH session ended with code ${code}`);
|
|
573
|
+
}
|
|
574
|
+
process.exit(code || 0);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
495
578
|
async function main() {
|
|
496
579
|
const args = process.argv.slice(2);
|
|
497
580
|
const command = args[0];
|
|
@@ -516,13 +599,17 @@ async function main() {
|
|
|
516
599
|
break;
|
|
517
600
|
case '--version':
|
|
518
601
|
case '-v':
|
|
519
|
-
console.log('mags v1.
|
|
602
|
+
console.log('mags v1.2.0');
|
|
520
603
|
process.exit(0);
|
|
521
604
|
break;
|
|
522
605
|
case 'run':
|
|
523
606
|
await requireAuth();
|
|
524
607
|
await runJob(args.slice(1));
|
|
525
608
|
break;
|
|
609
|
+
case 'ssh':
|
|
610
|
+
await requireAuth();
|
|
611
|
+
await sshToJob(args[1]);
|
|
612
|
+
break;
|
|
526
613
|
case 'url':
|
|
527
614
|
await requireAuth();
|
|
528
615
|
await enableUrlAccess(args[1], parseInt(args[2]) || 8080);
|