nx_mesh_serve.nx source
↩ module page · 267 lines · 14461 B
1// nx_mesh_serve.nx -- WORKER MESH SERVICE: the standing daemon that makes the whole mesh LIVE at a URL, now a
2// gated 3-route console (fronted by nx_mesh_gateway which OPAQUE-validates every /mesh/* request, strips the
3// /mesh prefix, and reverse-proxies to us on :8029). Routes (paths are POST-strip, i.e. what the gateway forwards):
4//
5// GET / -> the mesh console: a prompt box that opens an EventSource to /mesh/gen and shows the image
6// GET /gen?p=<prompt> -> text/event-stream of the pipeline (accepted->authorized->routed->generating->result{url})
7// GET /served_<N>.png -> the generated PNG, served from sites/nishifamily/mesh/ (GATED: only reachable through
8// the authed gateway, so a logged-in operator can SEE their result; strict whitelist,
9// no path traversal)
10//
11// The /gen route fork+dup3's the client socket onto the conductor's stdout and execve's ./nx_mesh_run.elf sse so the
12// SSE frames stream straight to the client. The / and /served routes fork a child that writes the response and exits
13// (bounds memory: the file buffer is freed with the child). Run from the nishihost cwd (where the organ elfs +
14// sites/ live). Build --build-only (it loops forever). license_tier: ORIGINAL
15import "nx_syscalls.nx"
16import "nx_runtime.nx"
17const MS_MAGIC_8029: i64 = 8029
18const MS_MAGIC_8192: i64 = 8192
19const MS_MAGIC_1024: i64 = 1024
20const MS_MAGIC_8191: i64 = 8191
21
22func ms_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
23func ms_atoi(s: *u8) -> i64 { var v: i64 = 0; var i: i64 = 0; while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } return v }
24// write decimal v into buf at o, return new o
25func ms_num(buf: *u8, o: i64, v: i64) -> i64 {
26 if v == 0 { buf[o] = 48 as u8; return o + 1 }
27 let tt: *u8 = sys_mmap(28)
28 var m: i64 = v
29 var k: i64 = 0
30 while m > 0 { tt[k] = (48 + (m - (m/10)*10)) as u8; m = m / 10; k = k + 1 }
31 var j: i64 = 0
32 while j < k { buf[o+j] = tt[k-1-j]; j = j + 1 }
33 return o + k
34}
35func ms_lit(buf: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { buf[o+i] = s[i]; i = i + 1 } return o + i }
36
37// hex digit -> value, or -1
38func ms_hexval(c: i64) -> i64 {
39 if c >= 48 { if c <= 57 { return c - 48 } }
40 if c >= 65 { if c <= 70 { return c - 55 } }
41 if c >= 97 { if c <= 102 { return c - 87 } }
42 return 0 - 1
43}
44
45// find "p=" in the request; copy the value (until ' ' '&' '\r' '\n') into out, decoding '+'->' ' and %XX->byte.
46// returns len (0 = not found -> caller defaults)
47func ms_extract_prompt(req: *u8, n: i64, out: *u8, cap: i64) -> i64 {
48 var i: i64 = 0
49 var found: i64 = 0 - 1
50 while i + 1 < n {
51 if req[i] == (112 as u8) { if req[i+1] == (61 as u8) { found = i + 2; i = n } } // "p="
52 i = i + 1
53 }
54 if found < 0 { return 0 }
55 var k: i64 = 0
56 var j: i64 = found
57 var go: i64 = 1
58 while go == 1 {
59 if j >= n { go = 0 } else {
60 let c: i64 = req[j] as i64
61 if c == 32 { go = 0 } else { if c == 38 { go = 0 } else { if c == 13 { go = 0 } else { if c == 10 { go = 0 } else {
62 if c == 43 {
63 if k + 1 < cap { out[k] = 32 as u8; k = k + 1 }
64 j = j + 1
65 } else { if c == 37 {
66 // %XX
67 if j + 2 < n {
68 let h1: i64 = ms_hexval(req[j+1] as i64)
69 let h2: i64 = ms_hexval(req[j+2] as i64)
70 if h1 >= 0 { if h2 >= 0 {
71 if k + 1 < cap { out[k] = ((h1*16)+h2) as u8; k = k + 1 }
72 j = j + 3
73 } else {
74 if k + 1 < cap { out[k] = c as u8; k = k + 1 }
75 j = j + 1
76 } } else {
77 if k + 1 < cap { out[k] = c as u8; k = k + 1 }
78 j = j + 1
79 }
80 } else {
81 if k + 1 < cap { out[k] = c as u8; k = k + 1 }
82 j = j + 1
83 }
84 } else {
85 if k + 1 < cap { out[k] = c as u8; k = k + 1 }
86 j = j + 1
87 } }
88 } } } }
89 }
90 }
91 out[k] = 0 as u8
92 return k
93}
94
95// copy the request path token (after the first space, up to ' ' or '?') into out. returns len.
96func ms_extract_path(req: *u8, n: i64, out: *u8, cap: i64) -> i64 {
97 var start: i64 = 0 - 1
98 var i: i64 = 0
99 while i < n {
100 if req[i] == (32 as u8) { start = i + 1; i = n } else { i = i + 1 }
101 }
102 if start < 0 { out[0] = 0 as u8; return 0 }
103 var k: i64 = 0
104 var j: i64 = start
105 var go: i64 = 1
106 while go == 1 {
107 if j >= n { go = 0 } else {
108 let c: i64 = req[j] as i64
109 if c == 32 { go = 0 } else { if c == 63 { go = 0 } else {
110 if k + 1 < cap { out[k] = c as u8; k = k + 1 }
111 j = j + 1
112 } }
113 }
114 }
115 out[k] = 0 as u8
116 return k
117}
118
119// does path start with pat (patlen chars)?
120func ms_starts(path: *u8, plen: i64, pat: *u8, patlen: i64) -> i64 {
121 if plen < patlen { return 0 }
122 var i: i64 = 0
123 while i < patlen { if path[i] != pat[i] { return 0 } i = i + 1 }
124 return 1
125}
126
127// strict whitelist: path is exactly "/served_<digits>.png" (no traversal, no other files). 1=yes 0=no
128func ms_is_served_png(path: *u8, plen: i64) -> i64 {
129 if plen < 13 { return 0 } // "/served_0.png" = 13
130 let pfx: *u8 = "/served_" as *u8 // 8 chars
131 var i: i64 = 0
132 while i < 8 { if path[i] != pfx[i] { return 0 } i = i + 1 }
133 let dend: i64 = plen - 4 // index where ".png" begins
134 if dend <= 8 { return 0 } // need >=1 digit
135 var j: i64 = 8
136 while j < dend {
137 let c: i64 = path[j] as i64
138 if c < 48 { return 0 }
139 if c > 57 { return 0 }
140 j = j + 1
141 }
142 if path[dend] != (46 as u8) { return 0 } // '.'
143 if path[dend+1] != (112 as u8) { return 0 } // 'p'
144 if path[dend+2] != (110 as u8) { return 0 } // 'n'
145 if path[dend+3] != (103 as u8) { return 0 } // 'g'
146 return 1
147}
148
149// the mesh console page. single-quoted attrs + JS (no embedded double-quotes to escape); status lines appended as
150// text nodes + <br> (no '\n' -- a NishiLang literal turns \n into a real newline, which would break a JS string).
151const MS_HOME_HTML: *u8 = "<!doctype html><html><head><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1'><title>Nishi Mesh</title><style>body{font-family:system-ui,sans-serif;max-width:640px;margin:6vh auto;padding:0 18px;color:#cdd7e6;background:#0b1019}h1{font-size:1.25rem;color:#e8eef7}p{color:#7c8aa5;font-size:.88rem;line-height:1.5}input{width:100%;padding:10px;margin:8px 0;box-sizing:border-box;border:1px solid #2a3550;border-radius:6px;background:#121a28;color:#e8eef7;font-size:.95rem}button{padding:10px 18px;background:#2d6cdf;color:#fff;border:0;border-radius:6px;cursor:pointer;font-size:.95rem}button:disabled{opacity:.5;cursor:default}pre{margin:16px 0;padding:12px;background:#121a28;border-left:3px solid #2d6cdf;color:#cdd7e6;white-space:pre-wrap;min-height:1.2em;font-size:.82rem}img{max-width:100%;border-radius:8px;margin-top:12px;display:none}a{color:#5a9bff;font-size:.85rem;text-decoration:none}.row{display:flex;gap:8px;align-items:center}</style></head><body><h1>Nishi Mesh — sovereign job console</h1><p>Submit a prompt. The NAS dispatches it over the sovereign mesh to a GPU worker on the RTX 5080, streams the pipeline live, and lands the image below — all behind your login. This is the sovereign container plane that replaces Docker/Portainer.</p><input id=p value='a red fox in a snowy forest at sunrise'><div class=row><button id=b onclick='go()'>Generate</button><a href='/gallery/'>open gallery</a></div><pre id=log>ready.</pre><img id=out><script>function E(i){return document.getElementById(i)} function L(s){var g=E('log');g.appendChild(document.createTextNode(s));g.appendChild(document.createElement('br'))} function go(){var p=E('p').value;var out=E('out');var b=E('b');E('log').textContent='';b.disabled=true;out.style.display='none';L('submitting: '+p);var es=new EventSource('/mesh/gen?p='+encodeURIComponent(p));es.addEventListener('accepted',function(e){L('accepted')});es.addEventListener('authorized',function(e){L('authorized (capability ok)')});es.addEventListener('routed',function(e){var d={};try{d=JSON.parse(e.data)}catch(x){}L('routed to worker: '+(d.worker||'gpu'))});es.addEventListener('generating',function(e){L('generating on GPU...')});es.addEventListener('result',function(e){var d={};try{d=JSON.parse(e.data)}catch(x){}var u=d.url||'';var n=u.substring(u.lastIndexOf('/')+1);out.src='/mesh/'+n;out.style.display='block';L('done: '+n);b.disabled=false;es.close()});es.addEventListener('error',function(e){var d={};try{d=JSON.parse(e.data)}catch(x){}L('error: '+(d.error||'stream ended'));b.disabled=false;es.close()});es.onerror=function(){b.disabled=false}}</script></body></html>" as *u8
152
153// serve MS_HOME_HTML with a 200 text/html response
154func ms_serve_home(cfd: i64) {
155 let home: *u8 = MS_HOME_HTML
156 let hl: i64 = ms_slen(home)
157 let hh: *u8 = sys_mmap(512)
158 var ho: i64 = ms_lit(hh, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nCache-Control: no-store\r\nConnection: close\r\nContent-Length: " as *u8)
159 ho = ms_num(hh, ho, hl)
160 ho = ms_lit(hh, ho, "\r\n\r\n" as *u8)
161 sys_write(cfd, hh, ho)
162 var w: i64 = 0
163 while w < hl {
164 let r: i64 = sys_write(cfd, ((home as i64) + w) as *u8, hl - w)
165 if r <= 0 { w = hl } else { w = w + r }
166 }
167}
168
169// serve sites/nishifamily/mesh/<path> as image/png (path already validated /served_<N>.png)
170func ms_serve_png(cfd: i64, path: *u8, plen: i64) {
171 let fp: *u8 = sys_mmap(512)
172 var o: i64 = ms_lit(fp, 0, "sites/nishifamily/mesh" as *u8)
173 var k: i64 = 0
174 while k < plen { fp[o] = path[k]; o = o + 1; k = k + 1 } // append "/served_<N>.png"
175 fp[o] = 0 as u8
176 let szp: *i64 = sys_mmap(16) as *i64
177 let png: *u8 = sys_read_file(fp, szp)
178 let pln: i64 = szp[0]
179 if (png as i64) == 0 {
180 let nf: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 9\r\nConnection: close\r\n\r\nnot found" as *u8
181 sys_write(cfd, nf, ms_slen(nf))
182 return
183 }
184 let hb: *u8 = sys_mmap(256)
185 var h: i64 = ms_lit(hb, 0, "HTTP/1.1 200 OK\r\nContent-Type: image/png\r\nCache-Control: no-store\r\nConnection: close\r\nContent-Length: " as *u8)
186 h = ms_num(hb, h, pln)
187 h = ms_lit(hb, h, "\r\n\r\n" as *u8)
188 sys_write(cfd, hb, h)
189 var w: i64 = 0
190 while w < pln {
191 let r: i64 = sys_write(cfd, ((png as i64) + w) as *u8, pln - w)
192 if r <= 0 { w = pln } else { w = w + r }
193 }
194}
195
196func main(argc: i64, argv: *i64) -> i64 {
197 var port: i64 = MS_MAGIC_8029
198 if argc >= 2 { port = ms_atoi(argv[1] as *u8) }
199 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
200 if fd < 0 { return 1 }
201 let one: *i64 = sys_mmap(8) as *i64
202 one[0] = 1
203 sys_setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, one as *u8, 4)
204 let sa: *u8 = sys_mmap(16)
205 sa[0] = 2 as u8; sa[1] = 0 as u8
206 sa[2] = ((port >> 8) & 0xff) as u8; sa[3] = (port & 0xff) as u8
207 sa[4] = 0 as u8; sa[5] = 0 as u8; sa[6] = 0 as u8; sa[7] = 0 as u8 // 0.0.0.0
208 if sys_bind(fd, sa, 16) < 0 { return 2 }
209 sys_listen(fd, 16)
210 let ssehdr: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nCache-Control: no-cache\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\n\r\n" as *u8
211 let ssehdrn: i64 = ms_slen(ssehdr)
212 let defprompt: *u8 = "a red fox in a snowy forest" as *u8
213 var counter: i64 = 0
214 // hoisted reusable buffers (loop is sequential -- one request handled to completion before the next accept)
215 let reqbuf: *u8 = sys_mmap(MS_MAGIC_8192)
216 let path: *u8 = sys_mmap(MS_MAGIC_1024)
217 let prompt: *u8 = sys_mmap(MS_MAGIC_1024)
218 let outp: *u8 = sys_mmap(256)
219 let st: *i64 = sys_mmap(8) as *i64
220 var go: i64 = 1
221 while go == 1 {
222 let cfd: i64 = sys_accept(fd)
223 if cfd >= 0 {
224 let rn: i64 = sys_read(cfd, reqbuf, MS_MAGIC_8191)
225 let pl: i64 = ms_extract_path(reqbuf, rn, path, MS_MAGIC_1024)
226 let isgen: i64 = ms_starts(path, pl, "/gen" as *u8, 4)
227 if isgen == 1 {
228 // SSE gen: extract prompt, per-request outpath, stream the conductor's SSE straight to the client
229 let plp: i64 = ms_extract_prompt(reqbuf, rn, prompt, MS_MAGIC_1024)
230 var pp: *u8 = prompt
231 if plp == 0 { pp = defprompt }
232 var oo: i64 = ms_lit(outp, 0, "sites/nishifamily/mesh/served_" as *u8)
233 oo = ms_num(outp, oo, counter)
234 oo = ms_lit(outp, oo, ".png" as *u8)
235 outp[oo] = 0 as u8
236 counter = counter + 1
237 sys_write(cfd, ssehdr, ssehdrn)
238 let pid: i64 = sys_fork()
239 if pid == 0 {
240 sys_dup3(cfd, 1, 0) // conductor stdout -> client socket
241 let av: *i64 = sys_mmap(64) as *i64
242 av[0] = "./nx_mesh_run.elf" as *u8 as i64
243 av[1] = "sse" as *u8 as i64
244 av[2] = pp as i64
245 av[3] = outp as i64
246 av[4] = 0
247 sys_execve_clean("./nx_mesh_run.elf" as *u8, av, 0 as *i64)
248 sys_exit(127)
249 }
250 sys_close(cfd)
251 sys_wait4(pid, st, 0)
252 } else {
253 // / (console) or /served_<N>.png (gated image): fork a child that writes + exits (frees the buffer)
254 let ispng: i64 = ms_is_served_png(path, pl)
255 let pid2: i64 = sys_fork()
256 if pid2 == 0 {
257 if ispng == 1 { ms_serve_png(cfd, path, pl) } else { ms_serve_home(cfd) }
258 sys_close(cfd)
259 sys_exit(0)
260 }
261 sys_close(cfd)
262 sys_wait4(pid2, st, 0)
263 }
264 }
265 }
266 return 0
267}