code wiki / (root) / nx_mesh_serve.nx

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 &mdash; sovereign job console</h1><p>Submit a prompt. The NAS dispatches it over the sovereign mesh to a GPU worker on the RTX&nbsp;5080, streams the pipeline live, and lands the image below &mdash; 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}