@magpiecloud/mags 1.7.0 → 1.7.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/bin/mags.js +15 -3
- package/package.json +1 -1
- package/python/INTEGRATION.md +61 -8
- package/python/src/mags/client.py +8 -0
- package/website/index.html +4 -1
package/bin/mags.js
CHANGED
|
@@ -389,10 +389,21 @@ To use Mags, you need to authenticate first.
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
// Create a new persistent VM
|
|
392
|
-
async function newVM(
|
|
392
|
+
async function newVM(args) {
|
|
393
|
+
let name = null;
|
|
394
|
+
let baseWorkspace = null;
|
|
395
|
+
|
|
396
|
+
for (let i = 0; i < args.length; i++) {
|
|
397
|
+
if (args[i] === '--base' && args[i + 1]) {
|
|
398
|
+
baseWorkspace = args[++i];
|
|
399
|
+
} else if (!name) {
|
|
400
|
+
name = args[i];
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
393
404
|
if (!name) {
|
|
394
405
|
log('red', 'Error: Name required');
|
|
395
|
-
console.log(`\nUsage: mags new <name
|
|
406
|
+
console.log(`\nUsage: mags new <name> [--base <workspace>]\n`);
|
|
396
407
|
process.exit(1);
|
|
397
408
|
}
|
|
398
409
|
|
|
@@ -404,6 +415,7 @@ async function newVM(name) {
|
|
|
404
415
|
workspace_id: name,
|
|
405
416
|
startup_command: 'sleep infinity'
|
|
406
417
|
};
|
|
418
|
+
if (baseWorkspace) payload.base_workspace_id = baseWorkspace;
|
|
407
419
|
|
|
408
420
|
const response = await request('POST', '/api/v1/mags-jobs', payload);
|
|
409
421
|
|
|
@@ -1255,7 +1267,7 @@ async function main() {
|
|
|
1255
1267
|
break;
|
|
1256
1268
|
case 'new':
|
|
1257
1269
|
await requireAuth();
|
|
1258
|
-
await newVM(args
|
|
1270
|
+
await newVM(args.slice(1));
|
|
1259
1271
|
break;
|
|
1260
1272
|
case 'run':
|
|
1261
1273
|
await requireAuth();
|
package/package.json
CHANGED
package/python/INTEGRATION.md
CHANGED
|
@@ -116,20 +116,39 @@ result = run_with_packages(
|
|
|
116
116
|
|
|
117
117
|
### With a pre-built base image
|
|
118
118
|
|
|
119
|
-
For repeated runs, avoid re-installing packages every time by
|
|
119
|
+
For repeated runs, avoid re-installing packages every time by creating a base workspace and syncing it:
|
|
120
120
|
|
|
121
121
|
```python
|
|
122
122
|
# One-time setup: create a base workspace with common packages
|
|
123
|
-
m.
|
|
123
|
+
job = m.run(
|
|
124
124
|
"pip install pandas numpy requests flask scikit-learn",
|
|
125
125
|
workspace_id="python-base",
|
|
126
|
+
persistent=True,
|
|
126
127
|
)
|
|
127
128
|
|
|
129
|
+
# Wait for setup to finish, then sync to S3
|
|
130
|
+
import time
|
|
131
|
+
for _ in range(60):
|
|
132
|
+
status = m.status(job["request_id"])
|
|
133
|
+
if status["status"] == "running":
|
|
134
|
+
break
|
|
135
|
+
time.sleep(1)
|
|
136
|
+
|
|
137
|
+
# Force sync — persists everything to S3 immediately
|
|
138
|
+
m.sync(job["request_id"])
|
|
139
|
+
|
|
128
140
|
# Every subsequent run inherits the base (read-only, no install needed)
|
|
129
141
|
result = m.run_and_wait(
|
|
130
142
|
"python3 -c 'import pandas; print(pandas.__version__)'",
|
|
131
143
|
base_workspace_id="python-base",
|
|
132
144
|
)
|
|
145
|
+
|
|
146
|
+
# Fork: load base, save changes to a new workspace
|
|
147
|
+
result = m.run_and_wait(
|
|
148
|
+
"pip install torch",
|
|
149
|
+
base_workspace_id="python-base",
|
|
150
|
+
workspace_id="python-ml",
|
|
151
|
+
)
|
|
133
152
|
```
|
|
134
153
|
|
|
135
154
|
---
|
|
@@ -259,7 +278,7 @@ def run_data_pipeline(sql_query, workspace_id="etl-pipeline"):
|
|
|
259
278
|
python3 << 'PYEOF'
|
|
260
279
|
import sqlite3, json
|
|
261
280
|
|
|
262
|
-
conn = sqlite3.connect("/
|
|
281
|
+
conn = sqlite3.connect("/root/data.db")
|
|
263
282
|
cursor = conn.execute("{sql_query}")
|
|
264
283
|
rows = cursor.fetchall()
|
|
265
284
|
print(json.dumps(rows))
|
|
@@ -430,7 +449,7 @@ cron = m.cron_create(
|
|
|
430
449
|
m.cron_create(
|
|
431
450
|
name="db-backup",
|
|
432
451
|
cron_expression="0 2 * * *",
|
|
433
|
-
script="pg_dump $DATABASE_URL | gzip > /
|
|
452
|
+
script="pg_dump $DATABASE_URL | gzip > /root/backup-$(date +%F).sql.gz",
|
|
434
453
|
workspace_id="backups",
|
|
435
454
|
)
|
|
436
455
|
|
|
@@ -462,17 +481,17 @@ def deploy_preview(user_id, html_content):
|
|
|
462
481
|
import shlex
|
|
463
482
|
|
|
464
483
|
script = f"""
|
|
465
|
-
mkdir -p /
|
|
466
|
-
cat > /
|
|
484
|
+
mkdir -p /root/site
|
|
485
|
+
cat > /root/site/index.html << 'HTMLEOF'
|
|
467
486
|
{html_content}
|
|
468
487
|
HTMLEOF
|
|
469
|
-
cd /
|
|
488
|
+
cd /root/site && python3 -m http.server 8080
|
|
470
489
|
"""
|
|
471
490
|
job = m.run(
|
|
472
491
|
script,
|
|
473
492
|
workspace_id=f"preview-{user_id}",
|
|
474
493
|
persistent=True,
|
|
475
|
-
startup_command="cd /
|
|
494
|
+
startup_command="cd /root/site && python3 -m http.server 8080",
|
|
476
495
|
)
|
|
477
496
|
|
|
478
497
|
# Wait for VM to start
|
|
@@ -528,6 +547,40 @@ m.run_and_wait("echo 'no persistence'", ephemeral=True)
|
|
|
528
547
|
| Read-only base | omit | `"my-base"` | Base mounted read-only. Changes discarded. |
|
|
529
548
|
| Fork | `"fork-1"` | `"my-base"` | Starts from base, saves to `fork-1`. |
|
|
530
549
|
|
|
550
|
+
### Syncing workspaces
|
|
551
|
+
|
|
552
|
+
Workspaces sync to S3 automatically when a job completes. For persistent VMs (`persistent=True`), workspaces also sync every 30 seconds and on sleep.
|
|
553
|
+
|
|
554
|
+
Use `m.sync()` to force an immediate sync without stopping the VM — useful for persisting a base image you've just set up:
|
|
555
|
+
|
|
556
|
+
```python
|
|
557
|
+
# Set up a base workspace on a persistent VM
|
|
558
|
+
job = m.run(
|
|
559
|
+
"pip install pandas numpy scikit-learn",
|
|
560
|
+
workspace_id="ml-base",
|
|
561
|
+
persistent=True,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# Wait for the install to finish
|
|
565
|
+
import time
|
|
566
|
+
for _ in range(60):
|
|
567
|
+
status = m.status(job["request_id"])
|
|
568
|
+
if status["status"] == "running":
|
|
569
|
+
break
|
|
570
|
+
time.sleep(1)
|
|
571
|
+
|
|
572
|
+
# Force sync — base image is now available for other jobs
|
|
573
|
+
m.sync(job["request_id"])
|
|
574
|
+
|
|
575
|
+
# List and manage workspaces
|
|
576
|
+
workspaces = m.list_workspaces()
|
|
577
|
+
for ws in workspaces.get("workspaces", []):
|
|
578
|
+
print(f"{ws['workspace_id']} — {ws['job_count']} jobs")
|
|
579
|
+
|
|
580
|
+
# Delete a workspace (removes stored data from S3)
|
|
581
|
+
m.delete_workspace("old-workspace")
|
|
582
|
+
```
|
|
583
|
+
|
|
531
584
|
---
|
|
532
585
|
|
|
533
586
|
## File Uploads
|
|
@@ -241,6 +241,14 @@ class Mags:
|
|
|
241
241
|
"""
|
|
242
242
|
return self._request("DELETE", f"/mags-workspaces/{workspace_id}")
|
|
243
243
|
|
|
244
|
+
def sync(self, request_id: str) -> dict:
|
|
245
|
+
"""Sync a running job's workspace to S3 without stopping the VM.
|
|
246
|
+
|
|
247
|
+
Use this to persist workspace changes immediately, e.g. after
|
|
248
|
+
setting up a base image.
|
|
249
|
+
"""
|
|
250
|
+
return self._request("POST", f"/mags-jobs/{request_id}/sync")
|
|
251
|
+
|
|
244
252
|
# ── cron jobs ────────────────────────────────────────────────────
|
|
245
253
|
|
|
246
254
|
def cron_create(
|
package/website/index.html
CHANGED
|
@@ -203,6 +203,7 @@ mags login</code></pre>
|
|
|
203
203
|
<tr><td><code>mags status <id></code></td><td>Get job status</td></tr>
|
|
204
204
|
<tr><td><code>mags logs <id></code></td><td>Get job output</td></tr>
|
|
205
205
|
<tr><td><code>mags stop <id></code></td><td>Stop a running job</td></tr>
|
|
206
|
+
<tr><td><code>mags sync <workspace></code></td><td>Sync workspace to S3 (without stopping VM)</td></tr>
|
|
206
207
|
<tr><td><code>mags url <id> [port]</code></td><td>Enable public URL access</td></tr>
|
|
207
208
|
<tr><td><code>mags workspace list</code></td><td>List persistent workspaces</td></tr>
|
|
208
209
|
<tr><td><code>mags workspace delete <id></code></td><td>Delete workspace + S3 data</td></tr>
|
|
@@ -243,7 +244,9 @@ mags run 'echo Hello World && uname -a'
|
|
|
243
244
|
mags run -w myproject 'pip install flask requests'
|
|
244
245
|
mags run -w myproject 'python3 app.py'
|
|
245
246
|
|
|
246
|
-
# Base image —
|
|
247
|
+
# Base image — create a golden image, sync it, then reuse
|
|
248
|
+
mags run -w golden -p 'apt install -y nodejs && npm install -g typescript'
|
|
249
|
+
mags sync golden # persist to S3
|
|
247
250
|
mags run --base golden 'npm test' # read-only, changes discarded
|
|
248
251
|
mags run --base golden -w fork-1 'npm test' # fork: load golden, save to fork-1
|
|
249
252
|
|