nx_gpu_ctl.nx source
↩ module page · 102 lines · 6457 B
1// nx_gpu_ctl.nx -- SOVEREIGN GPU-provider CONTROL PLANE (Vast.ai) over our OWN TLS-1.2 stack (console.vast.ai is
2// TLS-1.2-only; the shipped stack is 1.3-only). Retires the curl+ssh provisioning dance: every verb is one
3// authenticated request via t12_request (nx_tls12_req.nx) -- Bearer key read from a vault FILE, never argv.
4// The MCP-ready capability the operator asked for: turn a GPU ON/OFF, provision, check, destroy -- all sovereign.
5// nx_gpu_ctl me <keyfile> GET /api/v0/users/current/ -> account (auth proof)
6// nx_gpu_ctl find <keyfile> GET /api/v0/bundles/?q=<vm 5090> -> offer list
7// nx_gpu_ctl up <offer_id> <keyfile> PUT /api/v0/asks/<id>/ {vm KVM} -> {new_contract:<inst>}
8// nx_gpu_ctl status <instance_id> <keyfile> GET /api/v0/instances/<id>/ -> state + ssh + $/hr
9// nx_gpu_ctl off <instance_id> <keyfile> PUT /api/v0/instances/<id>/ {stopped}-> stop billing, keep disk
10// nx_gpu_ctl on <instance_id> <keyfile> PUT /api/v0/instances/<id>/ {running}-> resume
11// nx_gpu_ctl destroy <instance_id> <keyfile> DELETE /api/v0/instances/<id>/ -> tear down (billing ends)
12// OPERATOR-DRIVEN by design (nothing acts autonomously). license_tier: ORIGINAL expect_exit: 0
13import "nx_tls12_req.nx"
14const GC_MAGIC_262144: i64 = 262144
15const GC_MAGIC_4096: i64 = 4096
16const GC_MAGIC_4095: i64 = 4095
17const GC_MAGIC_2048: i64 = 2048
18
19const GC_VM_BODY: *u8 = "{\"image\":\"docker.io/vastai/kvm:ubuntu_terminal\",\"disk\":50,\"runtype\":\"ssh_direct\",\"vm\":true,\"label\":\"nishi-sovereign-gpu\",\"target_state\":\"running\"}" as *u8
20const GC_FIND_URL: *u8 = "https://console.vast.ai/api/v0/bundles/?q=%7B%22gpu_name%22%3A%7B%22eq%22%3A%22RTX%205090%22%7D%2C%22num_gpus%22%3A%7B%22eq%22%3A1%7D%2C%22rentable%22%3A%7B%22eq%22%3Atrue%7D%2C%22vms_enabled%22%3A%7B%22eq%22%3Atrue%7D%2C%22order%22%3A%5B%5B%22dph_total%22%2C%22asc%22%5D%5D%2C%22limit%22%3A40%7D" as *u8
21
22func gc_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
23func gc_putn(v: i64) -> i64 { let b: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
24func gc_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 }
25func gc_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ d[o+i]=s[i]; i=i+1 } return o+i }
26func gc_readfile(path: *u8, out: *u8, cap: i64) -> i64 {
27 let fd: i64 = sys_openat_rd(path); if fd <= 0 { return 0 }
28 var off: i64 = 0; var go: i64 = 1
29 while go == 1 { let r: i64 = sys_read(fd, ((out as i64)+off) as *u8, cap-off); if r <= 0 { go = 0 } else { off = off + r } }
30 sys_close(fd)
31 var trimming: i64 = 1
32 while trimming == 1 { if off <= 0 { trimming = 0 } else { let c: i64 = out[off-1] as i64; if c == 10 { off = off - 1 } else { if c == 13 { off = off - 1 } else { trimming = 0 } } } }
33 return off
34}
35// build "https://console.vast.ai" + prefix + id + "/" into out (NUL-terminated); returns length. id required.
36func gc_url(prefix: *u8, id: *u8, out: *u8) -> i64 {
37 var o: i64 = gc_cat(out, 0, "https://console.vast.ai" as *u8)
38 o = gc_cat(out, o, prefix)
39 var i: i64=0; while id[i]!=(0 as u8){ out[o]=id[i]; o=o+1; i=i+1 }
40 out[o]=47 as u8; o=o+1
41 out[o] = 0 as u8
42 return o
43}
44func gc_bodylen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
45
46func gc_usage() -> i64 {
47 gc_puts("usage: nx_gpu_ctl {me <key>|find <key>|up <offer> <key>|status <id> <key>|off <id> <key>|on <id> <key>|destroy <id> <key>}\n" as *u8)
48 return 0
49}
50
51// run one request, print the response, return 0 if HTTP 2xx else 1
52func gc_run(url: *u8, method: *u8, mlen: i64, tok: *u8, tn: i64, body: *u8, blen: i64) -> i64 {
53 let out: *u8 = sys_mmap(GC_MAGIC_262144)
54 let n: i64 = t12_request(url, method, mlen, tok, tn, body, blen, out, GC_MAGIC_262144)
55 if n < 0 { gc_puts("REQUEST FAILED rc=" as *u8); gc_putn(n); gc_puts(" (-2=connect -3=handshake -5=write -6=no-cert -7=truststore-load -8=cert-INVALID)\n" as *u8); return 1 }
56 sys_write(1, out, n)
57 gc_puts("\n" as *u8)
58 // crude 2xx check: response starts "HTTP/1.1 2"
59 if n > 11 { if out[9]==(50 as u8) { return 0 } }
60 return 1
61}
62
63func main(argc: i64, argv: *i64) -> i64 {
64 if argc < 3 { gc_usage(); sys_exit(1); return 1 }
65 let verb: *u8 = argv[1] as *u8
66 let keyfile: *u8 = argv[argc-1] as *u8 // keyfile is always the LAST argument
67 let kbuf: *u8 = sys_mmap(GC_MAGIC_4096)
68 let tn: i64 = gc_readfile(keyfile, kbuf, GC_MAGIC_4095)
69 if tn <= 0 { gc_puts("cannot read keyfile\n" as *u8); sys_exit(1); return 1 }
70 let url: *u8 = sys_mmap(GC_MAGIC_2048)
71
72 // no-id verbs (full literal URL)
73 if gc_streq(verb, "me" as *u8) == 1 { sys_exit(gc_run("https://console.vast.ai/api/v0/users/current/" as *u8, "GET" as *u8, 3, kbuf, tn, 0 as *u8, 0)); return 0 }
74 if gc_streq(verb, "find" as *u8) == 1 { sys_exit(gc_run(GC_FIND_URL, "GET" as *u8, 3, kbuf, tn, 0 as *u8, 0)); return 0 }
75
76 // id verbs need <verb> <id> <keyfile>
77 if argc < 4 { gc_usage(); sys_exit(1); return 1 }
78 let id: *u8 = argv[2] as *u8
79
80 if gc_streq(verb, "up" as *u8) == 1 {
81 gc_url("/api/v0/asks/" as *u8, id, url)
82 sys_exit(gc_run(url, "PUT" as *u8, 3, kbuf, tn, GC_VM_BODY, gc_bodylen(GC_VM_BODY))); return 0
83 }
84 if gc_streq(verb, "status" as *u8) == 1 {
85 gc_url("/api/v0/instances/" as *u8, id, url)
86 sys_exit(gc_run(url, "GET" as *u8, 3, kbuf, tn, 0 as *u8, 0)); return 0
87 }
88 if gc_streq(verb, "off" as *u8) == 1 {
89 gc_url("/api/v0/instances/" as *u8, id, url)
90 sys_exit(gc_run(url, "PUT" as *u8, 3, kbuf, tn, "{\"state\":\"stopped\"}" as *u8, 19)); return 0
91 }
92 if gc_streq(verb, "on" as *u8) == 1 {
93 gc_url("/api/v0/instances/" as *u8, id, url)
94 sys_exit(gc_run(url, "PUT" as *u8, 3, kbuf, tn, "{\"state\":\"running\"}" as *u8, 19)); return 0
95 }
96 if gc_streq(verb, "destroy" as *u8) == 1 {
97 gc_url("/api/v0/instances/" as *u8, id, url)
98 sys_exit(gc_run(url, "DELETE" as *u8, 6, kbuf, tn, 0 as *u8, 0)); return 0
99 }
100 gc_usage(); sys_exit(1)
101 return 1
102}