code wiki / _hdl_build / nx_dev_api.nx

nx_dev_api.nx source

↩ module page · 411 lines · 19691 B

1// nx_dev_api.nx -- SOVEREIGN DEV/CI API: development itself becomes Nishi API calls (operator 2026-07-02: 2// "get this all setup as api calls in the nishi ecosystem ... so we dont use shitty wsl and shitty shell"). 3// The ONE operation no existing API exposed = BUILD (compile a .nx -> ELF). This closes it, so the WSL 4// door is retired: a client POSTs a build request over our own HTTP and reads the gate verdict back. 5// GET /api/dev -> index (routes) 6// POST /api/dev/build body organ=<name> -> fork/exec ./_offc/nx_sov_build_run.elf <name>, capture 7// stdout+stderr, return {organ,exit,output} 8// POST /api/dev/gate body organ=<name> -> build a *_gate + return GREEN/RED verdict + output 9// GET /api/dev/status -> the job-runner receipts tail (what ran, exit codes) 10// PURE ROUTER da_handle(req,req_n,out)->out_n (bytes in, bytes out) so the gate drives it in-process, 11// exactly like ta_handle/ma_handle. THE DEFENSIVE BOUNDARY (rule 12): the organ name is charset-validated 12// [a-z0-9_] only -> no path traversal, no args, FIXED-ARGV exec = injection structurally impossible. 13// AUTH: /api/dev EXECUTES code -> the mutating routes require an X-Nishi-Cap capability token granting the 14// tool "dev_build" (nx_cap_token: HMAC, unforgeable, least-authority, no ambient identity = confused-deputy 15// impossible). No cap / forged / expired / wrong-tool -> 401. Secret from a keyfile (prod), const fallback 16// (gates/dev stay deterministic) -- the tools-API convention. license_tier: ORIGINAL 17import "nx_syscalls.nx" 18import "nx_cap_token.nx" // capt_verify / CAPT_OK 19const DA_MAGIC_16384: i64 = 16384 20const DA_MAGIC_8192: i64 = 8192 21const DA_MAGIC_8000: i64 = 8000 22 23const DA_OUTCAP: i64 = 262144 24const DA_TMP: *u8 = "/tmp/da_build.out" 25const DA_HARNESS: *u8 = "./_offc/nx_sov_build_run.elf" 26const DA_CAP_TOOL: *u8 = "dev_build" as *u8 27const DA_CAP_KEYFILE: *u8 = "dev_cap_secret.key" as *u8 28// PLACEHOLDER (gates/dev only); production loads DA_CAP_KEYFILE (0600, provisioned once from the vault). 29const DA_CAP_SECRET: *u8 = "nishi-dev-cap-hmac-secret-v1-REPLACE-FROM-VAULT" as *u8 30 31func da_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 32func da_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 } 33func da_catn(d: *u8, o: i64, v: i64) -> i64 { 34 let t: *u8=sys_mmap(24); var m: i64=v; var k: i64=0 35 if m < 0 { d[o] = 45 as u8; o = o + 1; m = 0 - m } 36 if m<0 { d[o]=45 as u8; return da_catn(d, o+1, 0-m) } 37 if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 38 var w: i64=o; var i: i64=0; while i<k{d[w]=t[k-1-i];w=w+1;i=i+1} return w 39} 40// JSON string escape (defensive boundary: build output carries quotes/newlines/control bytes) 41func da_json_esc(d: *u8, o: i64, s: *u8, n: i64) -> i64 { 42 var i: i64=0 43 while i < n { 44 let c: i64 = (s[i] as i64) & 0xff 45 if c==34 { d[o]=92 as u8; d[o+1]=34 as u8; o=o+2 } 46 else { if c==92 { d[o]=92 as u8; d[o+1]=92 as u8; o=o+2 } 47 else { if c==10 { d[o]=92 as u8; d[o+1]=110 as u8; o=o+2 } 48 else { if c==13 { d[o]=92 as u8; d[o+1]=114 as u8; o=o+2 } 49 else { if c==9 { d[o]=92 as u8; d[o+1]=116 as u8; o=o+2 } 50 else { if c<32 { d[o]=32 as u8; o=o+1 } 51 else { d[o]=c as u8; o=o+1 } } } } } } 52 i=i+1 53 } 54 return o 55} 56 57// validate an organ name: [a-z0-9_] only, 1..64 chars. Returns len or -1 (rejects .. / space / args). 58func da_valid_name(s: *u8, n: i64, out: *u8) -> i64 { 59 if n < 1 { return 0 - 1 } 60 if n > 64 { return 0 - 1 } 61 var i: i64 = 0 62 while i < n { 63 let c: i64 = (s[i] as i64) & 0xff 64 var ok: i64 = 0 65 if c>=97 { if c<=122 { ok=1 } } 66 if c>=48 { if c<=57 { ok=1 } } 67 if c==95 { ok=1 } 68 if ok==0 { return 0 - 1 } 69 out[i] = s[i] 70 i = i + 1 71 } 72 out[n] = 0 as u8 73 return n 74} 75 76func da_body_off(req: *u8, n: i64) -> i64 { 77 var i: i64 = 0 78 while i + 3 < n { 79 if (req[i] as i64)==13 { if (req[i+1] as i64)==10 { if (req[i+2] as i64)==13 { if (req[i+3] as i64)==10 { return i+4 } } } } 80 i = i + 1 81 } 82 return 0 - 1 83} 84// extract form field `key=` value from body[boff..n) into out; returns len or -1 85func da_form_field(req: *u8, boff: i64, n: i64, key: *u8, out: *u8) -> i64 { 86 if boff < 0 { return 0 - 1 } 87 let kl: i64 = da_slen(key) 88 var i: i64 = boff 89 while i + kl < n { 90 var m: i64 = 1 91 var j: i64 = 0 92 while j < kl { if req[i+j] != key[j] { m=0; j=kl } else { j=j+1 } } 93 if m==1 { if req[i+kl]==(61 as u8) { 94 var o: i64 = 0 95 var p: i64 = i+kl+1 96 while p < n { let c: i64 = req[p] as i64; if c==38 { p=n } else { if c==10 { p=n } else { if c==13 { p=n } else { out[o]=req[p]; o=o+1; p=p+1 } } } } 97 out[o] = 0 as u8 98 return o 99 } } 100 i = i + 1 101 } 102 return 0 - 1 103} 104 105// bounded file read into a caller buffer. NO 4GiB mmap (unlike sys_read_file, which reserves 4GiB VA per call and 106// never munmaps -> ~13 per-request reads exhausted address space and made fork() fail). fd CLOSED. returns bytes. 107func da_read_small(path: *u8, out: *u8, cap: i64) -> i64 { 108 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } 109 var total: i64 = 0; var go: i64 = 1 110 while go == 1 { 111 let n: i64 = sys_read(fd, (out as i64 + total) as *u8, cap - total) 112 if n <= 0 { go = 0 } 113 if n > 0 { total = total + n } 114 if total >= cap { go = 0 } 115 } 116 sys_close(fd) 117 return total 118} 119// read a file's LAST `cap` bytes into out via lseek-to-tail (NO 4GiB mmap; fd CLOSED); returns bytes read. 120func da_read_tail(path: *u8, out: *u8, cap: i64) -> i64 { 121 let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } 122 let size: i64 = sys_lseek(fd, 0, 2) // SEEK_END -> file size 123 var start: i64 = 0 124 if size > cap { start = size - cap } 125 sys_lseek(fd, start, 0) // SEEK_SET -> tail 126 var total: i64 = 0; var go: i64 = 1 127 while go == 1 { 128 let n: i64 = sys_read(fd, (out as i64 + total) as *u8, cap - total) 129 if n <= 0 { go = 0 } 130 if n > 0 { total = total + n } 131 if total >= cap { go = 0 } 132 } 133 sys_close(fd) 134 return total 135} 136 137// fork/exec the harness on <name>, capturing stdout+stderr to DA_TMP. Returns child exit code. 138func da_build_organ(name: *u8, capbuf: *u8, caplen: *i64) -> i64 { 139 let tf: i64 = sys_openat_wr(DA_TMP, 0x1a4) // truncate for a fresh capture 140 if tf >= 0 { sys_close(tf) } 141 let pid: i64 = sys_fork() 142 if pid == 0 { 143 let ofd: i64 = sys_openat_wr(DA_TMP, 0x1a4) 144 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 145 let argv: *i64 = sys_mmap(64) as *i64 146 let envp: *i64 = sys_mmap(16) as *i64 147 envp[0] = 0 148 argv[0] = DA_HARNESS as i64 149 argv[1] = name as i64 150 argv[2] = 0 151 sys_execve(DA_HARNESS, argv, envp) 152 sys_exit(127) 153 return 127 154 } 155 if pid < 0 { caplen[0]=0; return 0 - 1 } 156 let status: *i64 = sys_mmap(16) as *i64 157 sys_wait4(pid, status, 0) 158 caplen[0] = da_read_tail(DA_TMP, capbuf, DA_OUTCAP) 159 return (status[0] >> 8) & 0xff 160} 161 162// find NUL-terminated needle in hay[0..n); returns offset or -1 163func da_indexof(hay: *u8, n: i64, needle: *u8) -> i64 { 164 let nl: i64 = da_slen(needle); if nl==0 { return 0 } 165 var i: i64 = 0 166 while i + nl <= n { var j: i64=0; var ok: i64=1; while j<nl { if hay[i+j]!=needle[j]{ok=0;j=nl} else {j=j+1} } if ok==1{return i} i=i+1 } 167 return 0 - 1 168} 169// extract header value (key includes trailing colon) into out2[off,len] within the header region. 1/0. 170func da_header(req: *u8, req_n: i64, key: *u8, out2: *i64) -> i64 { 171 var hend: i64 = da_body_off(req, req_n) 172 if hend < 0 { hend = req_n } 173 let p: i64 = da_indexof(req, hend, key) 174 if p < 0 { return 0 } 175 var vs: i64 = p + da_slen(key) 176 var go: i64 = 1 177 while go==1 { if vs<hend { if req[vs]==(32 as u8) { vs=vs+1 } else { go=0 } } else { go=0 } } 178 var ve: i64 = vs 179 var go2: i64 = 1 180 while go2==1 { if ve<hend { let c: i64 = req[ve] as i64; if c==13 { go2=0 } else { if c==10 { go2=0 } else { ve=ve+1 } } } else { go2=0 } } 181 out2[0]=vs; out2[1]=ve-vs 182 return 1 183} 184// verify the request carries a cap granting DA_CAP_TOOL. Returns CAPT_OK(1) or a negative deny code / 0=no-cap. 185func da_authz(req: *u8, req_n: i64) -> i64 { 186 let kbuf: *u8 = sys_mmap(256) 187 let klen: i64 = da_read_small(DA_CAP_KEYFILE, kbuf, 240) 188 var secret: *u8 = DA_CAP_SECRET 189 var secretlen: i64 = da_slen(DA_CAP_SECRET) 190 if klen > 0 { secret = kbuf; secretlen = klen } 191 let cap2: *i64 = sys_mmap(16) as *i64 192 if da_header(req, req_n, "X-Nishi-Cap:" as *u8, cap2) != 1 { return 0 } 193 let capp: *u8 = ((req as i64) + cap2[0]) as *u8 194 let capl: i64 = cap2[1] 195 if capl <= 0 { return 0 } 196 return capt_verify(secret, secretlen, capp, capl, DA_CAP_TOOL, da_slen(DA_CAP_TOOL), sys_now_realtime_sec()) 197} 198 199func da_http_head(out: *u8, o: i64, status: *u8) -> i64 { 200 o = da_cat(out, o, "HTTP/1.1 " as *u8) 201 o = da_cat(out, o, status) 202 o = da_cat(out, o, "\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n" as *u8) 203 return o 204} 205 206func da_path_is(path: *u8, plen: i64, lit: *u8) -> i64 { 207 let ll: i64 = da_slen(lit) 208 if ll != plen { return 0 } 209 var i: i64 = 0 210 while i < plen { if path[i] != lit[i] { return 0 } i = i + 1 } 211 return 1 212} 213 214func da_index(out: *u8) -> i64 { 215 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 216 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"version\":2,\"routes\":[\"GET /api/dev\",\"POST /api/dev/build {organ}\",\"POST /api/dev/gate {organ}\",\"POST /api/dev/run {organ,arg1..arg3}\",\"GET /api/dev/status\"]}\n" as *u8) 217 return o 218} 219 220func da_do_build(req: *u8, body: i64, req_n: i64, out: *u8, is_gate: i64) -> i64 { 221 // AUTH FIRST: no valid dev_build capability -> 401, BEFORE parsing/execing anything 222 if da_authz(req, req_n) != CAPT_OK { 223 var oa: i64 = da_http_head(out, 0, "401 Unauthorized" as *u8) 224 oa = da_cat(out, oa, "{\"error\":\"dev_build capability required (X-Nishi-Cap); no ambient authority\"}\n" as *u8) 225 return oa 226 } 227 let raw: *u8 = sys_mmap(256) 228 let rn: i64 = da_form_field(req, body, req_n, "organ" as *u8, raw) 229 let name: *u8 = sys_mmap(128) 230 let vn: i64 = da_valid_name(raw, rn, name) 231 if vn < 0 { 232 var o2: i64 = da_http_head(out, 0, "400 Bad Request" as *u8) 233 o2 = da_cat(out, o2, "{\"error\":\"organ name required, [a-z0-9_] only\"}\n" as *u8) 234 return o2 235 } 236 let cap: *u8 = sys_mmap(DA_OUTCAP + 16) 237 let clen: *i64 = sys_mmap(16) as *i64 238 let exit: i64 = da_build_organ(name, cap, clen) 239 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 240 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"organ\":\"" as *u8) 241 o = da_json_esc(out, o, name, vn) 242 o = da_cat(out, o, "\",\"exit\":" as *u8) 243 o = da_catn(out, o, exit) 244 if is_gate==1 { if exit==0 { o = da_cat(out, o, ",\"verdict\":\"GREEN\"" as *u8) } else { o = da_cat(out, o, ",\"verdict\":\"RED\"" as *u8) } } 245 o = da_cat(out, o, ",\"output\":\"" as *u8) 246 o = da_json_esc(out, o, cap, clen[0]) 247 o = da_cat(out, o, "\"}\n" as *u8) 248 sys_munmap(cap, DA_OUTCAP + 16) // free the 262KB capture buffer -- in-process server: no per-request VA leak 249 return o 250} 251 252func da_do_status(out: *u8) -> i64 { 253 let tail: *u8 = sys_mmap(DA_MAGIC_16384) 254 let tn: i64 = da_read_tail("knowledge/status/job_runner.log" as *u8, tail, DA_MAGIC_8192) 255 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 256 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"status\":\"" as *u8) 257 o = da_json_esc(out, o, tail, tn) 258 o = da_cat(out, o, "\"}\n" as *u8) 259 return o 260} 261 262// validate ONE arg token: [a-z0-9_.=-] only, 0..64 chars (empty = present-but-blank -> skipped). Returns len or -1. 263// Rejects space / ';' / '/' / backtick / '..' -> with fixed-argv exec, argument injection is structurally impossible. 264func da_valid_arg(s: *u8, n: i64, out: *u8) -> i64 { 265 if n < 0 { return 0 - 1 } 266 if n > 64 { return 0 - 1 } 267 var i: i64 = 0 268 while i < n { 269 let c: i64 = (s[i] as i64) & 0xff 270 var ok: i64 = 0 271 if c>=97 { if c<=122 { ok=1 } } // a-z 272 if c>=48 { if c<=57 { ok=1 } } // 0-9 273 if c==95 { ok=1 } // _ 274 if c==46 { ok=1 } // . 275 if c==61 { ok=1 } // = 276 if c==45 { ok=1 } // - 277 if ok==0 { return 0 - 1 } 278 out[i] = s[i] 279 i = i + 1 280 } 281 out[n] = 0 as u8 282 return n 283} 284 285// fork/exec a PRE-BUILT organ ./_offc/<name>.elf with up to 3 validated args, capture stdout+stderr. Returns exit code. 286func da_run_organ(name: *u8, vn: i64, a1: *u8, a1n: i64, a2: *u8, a2n: i64, a3: *u8, a3n: i64, capbuf: *u8, caplen: *i64) -> i64 { 287 let path: *u8 = sys_mmap(256) 288 var po: i64 = da_cat(path, 0, "./_offc/" as *u8) 289 var i: i64 = 0; while i < vn { path[po]=name[i]; po=po+1; i=i+1 } 290 po = da_cat(path, po, ".elf" as *u8) 291 path[po] = 0 as u8 292 let tf: i64 = sys_openat_wr(DA_TMP, 0x1a4); if tf >= 0 { sys_close(tf) } 293 let pid: i64 = sys_fork() 294 if pid == 0 { 295 let ofd: i64 = sys_openat_wr(DA_TMP, 0x1a4) 296 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 297 let argv: *i64 = sys_mmap(64) as *i64 298 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 299 var ai: i64 = 1 300 argv[0] = path as i64 301 if a1n > 0 { argv[ai] = a1 as i64; ai=ai+1 } 302 if a2n > 0 { argv[ai] = a2 as i64; ai=ai+1 } 303 if a3n > 0 { argv[ai] = a3 as i64; ai=ai+1 } 304 argv[ai] = 0 305 sys_execve(path, argv, envp) 306 sys_exit(127) 307 return 127 308 } 309 if pid < 0 { caplen[0]=0; return 0 - 1 } 310 let status: *i64 = sys_mmap(16) as *i64 311 sys_wait4(pid, status, 0) 312 caplen[0] = da_read_tail(DA_TMP, capbuf, DA_OUTCAP) 313 return (status[0] >> 8) & 0xff 314} 315 316// is <name> on the run allowlist (knowledge/dev_run_allow.conf, one organ per line)? FAIL-CLOSED (no conf -> deny 317// ALL). The security boundary that makes /api/dev/run safe: a dev_build cap can only run EXPLICITLY-listed organs 318// (gates, exec_engine, sweeps) -- NOT the deploy/root organs (nx_aw_hostctl / nx_aw_sudo) => no privilege-escalation. 319func da_run_allowed(name: *u8, vn: i64) -> i64 { 320 let b: *u8 = sys_mmap(DA_MAGIC_8192) 321 let n: i64 = da_read_small("knowledge/dev_run_allow.conf" as *u8, b, DA_MAGIC_8000) 322 if n == 0 { return 0 } // no/empty allowlist -> deny (fail-closed) 323 var ls: i64 = 0 324 var i: i64 = 0 325 while i <= n { 326 var atend: i64 = 0 327 if i==n { atend=1 } else { if b[i]==(10 as u8) { atend=1 } } 328 if atend==1 { 329 var le: i64 = i 330 if le>ls { if b[le-1]==(13 as u8) { le=le-1 } } // strip trailing CR 331 if (le-ls)==vn { var m: i64=1; var j: i64=0; while j<vn { if b[ls+j]!=name[j] { m=0; j=vn } else { j=j+1 } } if m==1 { return 1 } } 332 ls = i+1 333 } 334 i = i + 1 335 } 336 return 0 337} 338 339// POST /api/dev/run organ=<name>[&arg1=..&arg2=..&arg3=..] -> run the PRE-BUILT _offc organ + capture output. 340// This is the WSL-replacement for RUN (the exec engine, gates-with-args, sweeps): drive any installed organ off-LAN. 341func da_do_run(req: *u8, body: i64, req_n: i64, out: *u8) -> i64 { 342 if da_authz(req, req_n) != CAPT_OK { 343 var oa: i64 = da_http_head(out, 0, "401 Unauthorized" as *u8) 344 oa = da_cat(out, oa, "{\"error\":\"dev_build capability required (X-Nishi-Cap); no ambient authority\"}\n" as *u8) 345 return oa 346 } 347 let raw: *u8 = sys_mmap(256) 348 let rn: i64 = da_form_field(req, body, req_n, "organ" as *u8, raw) 349 let name: *u8 = sys_mmap(128) 350 let vn: i64 = da_valid_name(raw, rn, name) 351 if vn < 0 { 352 var o2: i64 = da_http_head(out, 0, "400 Bad Request" as *u8) 353 o2 = da_cat(out, o2, "{\"error\":\"organ name required, [a-z0-9_] only\"}\n" as *u8) 354 return o2 355 } 356 if da_run_allowed(name, vn) != 1 { 357 var o3: i64 = da_http_head(out, 0, "403 Forbidden" as *u8) 358 o3 = da_cat(out, o3, "{\"error\":\"organ not on the run allowlist (knowledge/dev_run_allow.conf); fail-closed\"}\n" as *u8) 359 return o3 360 } 361 let r1: *u8=sys_mmap(96); let a1: *u8=sys_mmap(96); let af1: i64=da_form_field(req,body,req_n,"arg1" as *u8,r1); var a1n: i64=0 362 if af1>=0 { a1n=da_valid_arg(r1,af1,a1) } 363 let r2: *u8=sys_mmap(96); let a2: *u8=sys_mmap(96); let af2: i64=da_form_field(req,body,req_n,"arg2" as *u8,r2); var a2n: i64=0 364 if af2>=0 { a2n=da_valid_arg(r2,af2,a2) } 365 let r3: *u8=sys_mmap(96); let a3: *u8=sys_mmap(96); let af3: i64=da_form_field(req,body,req_n,"arg3" as *u8,r3); var a3n: i64=0 366 if af3>=0 { a3n=da_valid_arg(r3,af3,a3) } 367 if a1n<0 { if a2n<0 { a2n=0 } } // (any present-but-invalid arg -> 400 below) 368 if a1n<0 { var ob: i64=da_http_head(out, 0, "400 Bad Request" as *u8); ob=da_cat(out,ob,"{\"error\":\"arg must be [a-z0-9_.=-], <=64\"}\n" as *u8); return ob } 369 if a2n<0 { var ob: i64=da_http_head(out, 0, "400 Bad Request" as *u8); ob=da_cat(out,ob,"{\"error\":\"arg must be [a-z0-9_.=-], <=64\"}\n" as *u8); return ob } 370 if a3n<0 { var ob: i64=da_http_head(out, 0, "400 Bad Request" as *u8); ob=da_cat(out,ob,"{\"error\":\"arg must be [a-z0-9_.=-], <=64\"}\n" as *u8); return ob } 371 let cap: *u8 = sys_mmap(DA_OUTCAP + 16) 372 let clen: *i64 = sys_mmap(16) as *i64 373 let exit: i64 = da_run_organ(name, vn, a1, a1n, a2, a2n, a3, a3n, cap, clen) 374 var o: i64 = da_http_head(out, 0, "200 OK" as *u8) 375 o = da_cat(out, o, "{\"api\":\"nishi-dev\",\"ran\":\"" as *u8) 376 o = da_json_esc(out, o, name, vn) 377 o = da_cat(out, o, "\",\"exit\":" as *u8) 378 o = da_catn(out, o, exit) 379 o = da_cat(out, o, ",\"output\":\"" as *u8) 380 o = da_json_esc(out, o, cap, clen[0]) 381 o = da_cat(out, o, "\"}\n" as *u8) 382 sys_munmap(cap, DA_OUTCAP + 16) // free the 262KB capture buffer -- in-process server: no per-request VA leak 383 return o 384} 385 386// ---- the pure router ---- 387func da_handle(req: *u8, req_n: i64, out: *u8) -> i64 { 388 // method = req[0..mlen) up to the first space 389 var mi: i64 = 0 390 var mlen: i64 = req_n 391 while mi < req_n { if (req[mi] as i64)==32 { mlen=mi; mi=req_n } else { mi=mi+1 } } 392 // path = from after that space to the next space 393 var ps: i64 = mlen + 1 394 var pe: i64 = req_n 395 var pi: i64 = ps 396 while pi < req_n { if (req[pi] as i64)==32 { pe=pi; pi=req_n } else { pi=pi+1 } } 397 let path: *u8 = ((req as i64) + ps) as *u8 398 let plen: i64 = pe - ps 399 let is_post: i64 = 0 + (req[0]==(80 as u8)) // 'P' = POST 400 let body: i64 = da_body_off(req, req_n) 401 402 if da_path_is(path, plen, "/api/dev" as *u8)==1 { return da_index(out) } 403 if da_path_is(path, plen, "/api/dev/" as *u8)==1 { return da_index(out) } 404 if da_path_is(path, plen, "/api/dev/status" as *u8)==1 { return da_do_status(out) } 405 if is_post==1 { if da_path_is(path, plen, "/api/dev/build" as *u8)==1 { return da_do_build(req, body, req_n, out, 0) } } 406 if is_post==1 { if da_path_is(path, plen, "/api/dev/gate" as *u8)==1 { return da_do_build(req, body, req_n, out, 1) } } 407 if is_post==1 { if da_path_is(path, plen, "/api/dev/run" as *u8)==1 { return da_do_run(req, body, req_n, out) } } 408 var o: i64 = da_http_head(out, 0, "404 Not Found" as *u8) 409 o = da_cat(out, o, "{\"error\":\"no such dev route\"}\n" as *u8) 410 return o 411}