code wiki / (root) / nx_golive.nx

nx_golive.nx source

↩ module page · 259 lines · 13746 B

1// nx_golive.nx -- ONE-COMMAND domain purchase->live DRIVER. Composes the proven executors 2// (nx_porkbun_domain's pbd_* + nx_acme_porkbun's DNS) into a single ordered, spend-safe, auto-quoting 3// walk that HALTS at the two operator gates. This is the "deploy it from here without any issues" 4// front door: the operator runs ONE command; the driver reads the live quote itself, checks it against 5// the cap, dry-runs the purchase server-side, and STOPS -- it will not spend or mutate the edge without 6// the explicit confirm tokens. 7// 8// nx_golive <domain> <credfile> <cap_cents> [confirm-spend] [confirm-edge] 9// 10// STAGES (each STOPS on failure; the pipeline never proceeds past a red step): 11// 1 CHECK live availability + AUTO-EXTRACT the quote (cents) from the registrar 12// 2 CAP quote <= cap_cents, else STOP (the spend ceiling is the operator's, passed in) 13// 3 DRYRUN server-side create dryRun:true -- full preflight, ZERO charge (SUCCESS => wouldSucceed) 14// -- GATE A: no `confirm-spend` => STOP here with the exact cost; re-run with the token to buy 15// 4 REGISTER real create (spends account credit); parse orderId 16// 5 WANIP ping -> our public IP (for the A records) 17// 6 DNS publish A @ + www -> our IP 18// 7 CERT ssl/retrieve the bundle (saved 0600; key never printed) 19// -- GATE B: no `confirm-edge` => STOP; edge SNI+route is operator-gated (contended NAS files) 20// 8 EDGE/VERIFY -- left to the never-brick mgmt plane + a live 200 check (operator-driven) 21// license_tier: ORIGINAL 22import "nx_porkbun_domain.nx" 23const K_MAGIC_2048: i64 = 2048 24 25func gl_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 26func gl_nn(v: i64) -> i64 { let bb: *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 as u8;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{bb[i]=t[k-1-i];i=i+1} sys_write(1,bb,k); return 0 } 27func gl_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 28func gl_streq(a: *u8, b: *u8) -> i64 { 29 var i: i64=0 30 while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } 31 if b[i]!=(0 as u8) { return 0 } 32 return 1 33} 34// extract the BODY region of a parsed HTTP response into off/len; 0 ok, -1 parse fail 35func gl_body(resp: *u8, total: i64, boff: *i64, blen: *i64) -> i64 { 36 let r: *i64 = nx_http_resp_alloc() 37 if nx_http_response_parse(resp, total, r)!=0 { return 0-1 } 38 boff[0]=r[6] 39 blen[0]=total-r[6] 40 return r[1] 41} 42// parse a JSON string value like "11.08" (decimal dollars) -> integer CENTS. Handles N, N.D, N.DD. 43func gl_dollars_to_cents(buf: *u8, off: i64, len: i64) -> i64 { 44 var dollars: i64=0 45 var cents: i64=0 46 var seen_dot: i64=0 47 var dec_digits: i64=0 48 var i: i64=0 49 while i<len { 50 let c: i64 = buf[off+i] as i64 51 if c==46 { seen_dot=1 } 52 else { 53 if c>=48 { if c<=57 { 54 if seen_dot==0 { dollars=dollars*10+(c-48) } 55 else { if dec_digits<2 { cents=cents*10+(c-48); dec_digits=dec_digits+1 } } 56 } } 57 } 58 i=i+1 59 } 60 if dec_digits==1 { cents=cents*10 } // "11.5" -> 50 cents 61 return dollars*100+cents 62} 63// find "key":"value" in the response BODY, return cents (or -1 if absent). key without quotes. 64func gl_json_cents(resp: *u8, total: i64, key: *u8, keylen: i64) -> i64 { 65 let boff: *i64 = sys_mmap(8) as *i64 66 let blen: *i64 = sys_mmap(8) as *i64 67 if gl_body(resp, total, boff, blen)<0 { return 0-1 } 68 let vo: *i64 = sys_mmap(8) as *i64 69 let vl: *i64 = sys_mmap(8) as *i64 70 if nx_acme_json_str(resp, boff[0], blen[0], key, keylen, vo, vl)!=1 { return 0-1 } 71 return gl_dollars_to_cents(resp, vo[0], vl[0]) 72} 73// does the response body carry "key":"want"? 1 yes / 0 no 74func gl_json_is(resp: *u8, total: i64, key: *u8, keylen: i64, want: *u8) -> i64 { 75 let boff: *i64 = sys_mmap(8) as *i64 76 let blen: *i64 = sys_mmap(8) as *i64 77 if gl_body(resp, total, boff, blen)<0 { return 0 } 78 let vo: *i64 = sys_mmap(8) as *i64 79 let vl: *i64 = sys_mmap(8) as *i64 80 if nx_acme_json_str(resp, boff[0], blen[0], key, keylen, vo, vl)!=1 { return 0 } 81 let wl: i64 = gl_slen(want) 82 if vl[0]!=wl { return 0 } 83 var i: i64=0 84 while i<wl { if resp[vo[0]+i]!=want[i] { return 0 } i=i+1 } 85 return 1 86} 87// copy a JSON string value into a NUL-terminated out buffer; returns length (0 if absent) 88func gl_json_copy(resp: *u8, total: i64, key: *u8, keylen: i64, out: *u8, cap: i64) -> i64 { 89 let boff: *i64 = sys_mmap(8) as *i64 90 let blen: *i64 = sys_mmap(8) as *i64 91 if gl_body(resp, total, boff, blen)<0 { return 0 } 92 let vo: *i64 = sys_mmap(8) as *i64 93 let vl: *i64 = sys_mmap(8) as *i64 94 if nx_acme_json_str(resp, boff[0], blen[0], key, keylen, vo, vl)!=1 { return 0 } 95 var n: i64 = vl[0] 96 if n>=cap { n=cap-1 } 97 var i: i64=0 98 while i<n { out[i]=resp[vo[0]+i]; i=i+1 } 99 out[n]=0 as u8 100 return n 101} 102// build + send the /domain/create/{domain} request (is_dry=1 adds dryRun:true). resp/rn filled. 103func gl_create(raw: *u8, ao: i64, an: i64, so: i64, sn2: i64, dom: *u8, quote: i64, is_dry: i64, 104 store: *TrustStore, now: i64, resp: *u8, rn: *i64) -> i64 { 105 let url: *u8 = sys_mmap(512) 106 var uo: i64 = pk_put(url, 0, "https://api.porkbun.com/api/json/v3/domain/create/" as *u8) 107 uo = pk_put(url, uo, dom) 108 url[uo]=0 as u8 109 let body: *u8 = sys_mmap(K_MAGIC_2048) 110 var bo: i64 = pk_creds(body, 0, (raw as i64 + ao) as *u8, an, (raw as i64 + so) as *u8, sn2) 111 bo = pk_put(body, bo, ",\"cost\":" as *u8) 112 bo = pbd_putn(body, bo, quote) 113 bo = pk_put(body, bo, ",\"agreeToTerms\":\"yes\"" as *u8) 114 if is_dry==1 { bo = pk_put(body, bo, ",\"dryRun\":true" as *u8) } 115 bo = pk_put(body, bo, "}" as *u8) 116 return nx_acme_req(url, uo, 1, "application/json" as *u8, 16, body, bo, store, now, resp, PBD_RESP_CAP, rn) 117} 118 119func main(argc: i64, argv: *i64) -> i64 { 120 if argc<4 { 121 gl_w("usage: nx_golive <domain> <credfile> <cap_cents> [confirm-spend] [confirm-edge]\n" as *u8) 122 gl_w(" runs CHECK + auto-quote + DRYRUN live; STOPS before spend/edge unless the tokens are given.\n" as *u8) 123 return 1 124 } 125 let dom: *u8 = argv[1] as *u8 126 let credfile: *u8 = argv[2] as *u8 127 // parse cap_cents 128 var cap: i64=0 129 let cs: *u8 = argv[3] as *u8 130 var ci: i64=0 131 while cs[ci]!=(0 as u8) { if cs[ci]>=(48 as u8) { if cs[ci]<=(57 as u8) { cap=cap*10+((cs[ci] as i64)-48) } } ci=ci+1 } 132 var confirm_spend: i64=0 133 var confirm_edge: i64=0 134 if argc>=5 { if gl_streq(argv[4] as *u8, "confirm-spend" as *u8)==1 { confirm_spend=1 } } 135 if argc>=6 { if gl_streq(argv[5] as *u8, "confirm-edge" as *u8)==1 { confirm_edge=1 } } 136 137 let store: *TrustStore = pbd_store() 138 if (store as i64)==0 { gl_w("STOP: trust store load failed (run from nxc2; needs data/mozilla_certdata.txt)\n" as *u8); return 1 } 139 let now: i64 = sys_now_realtime_sec() 140 let resp: *u8 = sys_mmap(PBD_RESP_CAP) 141 let rn: *i64 = sys_mmap(16) as *i64 142 let raw: *u8 = sys_mmap(K_MAGIC_2048) 143 let ao: *i64 = sys_mmap(8) as *i64 144 let an: *i64 = sys_mmap(8) as *i64 145 let so: *i64 = sys_mmap(8) as *i64 146 let sn2: *i64 = sys_mmap(8) as *i64 147 if pbd_creds(credfile, ao, an, so, sn2, raw)!=0 { gl_w("STOP: creds load failed (need apikey line1, secret line2)\n" as *u8); return 2 } 148 149 gl_w("=== nx_golive: " as *u8); gl_w(dom); gl_w(" cap=" as *u8); gl_nn(cap); gl_w(" cents ===\n" as *u8) 150 151 // ---- STAGE 1: CHECK + auto-quote ---- 152 gl_w("[1/8 CHECK] querying availability + price...\n" as *u8) 153 if pbd_check(raw, ao[0], an[0], so[0], sn2[0], dom, store, now, resp, rn)!=NX_PORKBUN_OK { 154 gl_w("STOP: checkDomain did not return SUCCESS. raw:\n" as *u8) 155 var dn: i64 = rn[0]; if dn>300 { dn=300 } if dn>0 { sys_write(1, resp, dn) } gl_w("\n" as *u8) 156 return 3 157 } 158 let avail: i64 = gl_json_is(resp, rn[0], "avail" as *u8, 5, "yes" as *u8) 159 let quote: i64 = gl_json_cents(resp, rn[0], "price" as *u8, 5) 160 gl_w(" available=" as *u8); if avail==1 { gl_w("yes" as *u8) } else { gl_w("NO" as *u8) } 161 gl_w(" quote=" as *u8); gl_nn(quote); gl_w(" cents\n" as *u8) 162 if avail!=1 { gl_w("STOP: domain not available for registration.\n" as *u8); return 3 } 163 if quote<=0 { gl_w("STOP: could not read a price quote from the registrar.\n" as *u8); return 3 } 164 165 // ---- STAGE 2: CAP ---- 166 gl_w("[2/8 CAP] quote vs cap...\n" as *u8) 167 if quote>cap { 168 gl_w("STOP: quote " as *u8); gl_nn(quote); gl_w(" > cap " as *u8); gl_nn(cap) 169 gl_w(" cents. Raise the cap to proceed (spend ceiling is yours).\n" as *u8) 170 return 4 171 } 172 gl_w(" OK: " as *u8); gl_nn(quote); gl_w(" <= " as *u8); gl_nn(cap); gl_w("\n" as *u8) 173 174 // ---- STAGE 3: DRYRUN (server preflight, no charge) ---- 175 gl_w("[3/8 DRYRUN] server-side create dryRun:true (no charge)...\n" as *u8) 176 if gl_create(raw, ao[0], an[0], so[0], sn2[0], dom, quote, 1, store, now, resp, rn)!=NX_ACME_HTTP_OK { 177 gl_w("STOP: dry-run transport failed.\n" as *u8); return 5 178 } 179 let dry_ok: i64 = gl_json_is(resp, rn[0], "status" as *u8, 6, "SUCCESS" as *u8) 180 if dry_ok!=1 { 181 gl_w("STOP: dry-run did NOT succeed (server preflight). raw:\n" as *u8) 182 var dn: i64 = rn[0]; if dn>300 { dn=300 } if dn>0 { sys_write(1, resp, dn) } gl_w("\n" as *u8) 183 return 5 184 } 185 gl_w(" DRY-RUN SUCCESS: this registration would succeed for " as *u8); gl_nn(quote); gl_w(" cents.\n" as *u8) 186 187 // ---- GATE A: spend ---- 188 if confirm_spend!=1 { 189 gl_w("\n=== GATE A (SPEND) ===\n" as *u8) 190 gl_w("Everything up to the purchase is GREEN. To actually buy " as *u8); gl_w(dom) 191 gl_w(" for " as *u8); gl_nn(quote); gl_w(" cents, re-run with the token:\n" as *u8) 192 gl_w(" nx_golive " as *u8); gl_w(dom); gl_w(" " as *u8); gl_w(credfile); gl_w(" " as *u8); gl_nn(cap); gl_w(" confirm-spend\n" as *u8) 193 gl_w("STOPPED at GATE A (no money spent, nothing created).\n" as *u8) 194 return 0 195 } 196 197 // ---- STAGE 4: REGISTER (real spend) ---- 198 gl_w("[4/8 REGISTER] confirm-spend given -- REAL registration...\n" as *u8) 199 if gl_create(raw, ao[0], an[0], so[0], sn2[0], dom, quote, 0, store, now, resp, rn)!=NX_ACME_HTTP_OK { 200 gl_w("STOP: register transport failed.\n" as *u8); return 6 201 } 202 if gl_json_is(resp, rn[0], "status" as *u8, 6, "SUCCESS" as *u8)!=1 { 203 gl_w("STOP: registration did NOT succeed. raw:\n" as *u8) 204 var dn: i64 = rn[0]; if dn>400 { dn=400 } if dn>0 { sys_write(1, resp, dn) } gl_w("\n" as *u8) 205 return 6 206 } 207 gl_w(" REGISTERED. " as *u8) 208 let oid: *u8 = sys_mmap(64) 209 if gl_json_copy(resp, rn[0], "orderId" as *u8, 7, oid, 64)>0 { gl_w("orderId=" as *u8); gl_w(oid) } 210 gl_w("\n" as *u8) 211 212 // ---- STAGE 5: WANIP ---- 213 gl_w("[5/8 WANIP] resolving our public IP...\n" as *u8) 214 let ip: *u8 = sys_mmap(64) 215 var ipn: i64 = 0 216 if pbd_ping(raw, ao[0], an[0], so[0], sn2[0], store, now, resp, rn)==NX_ACME_HTTP_OK { 217 ipn = gl_json_copy(resp, rn[0], "yourIp" as *u8, 6, ip, 64) 218 } 219 if ipn<=0 { gl_w("WARN: could not read yourIp; skipping DNS (publish A records manually).\n" as *u8) } 220 else { 221 gl_w(" public IP=" as *u8); gl_w(ip); gl_w("\n" as *u8) 222 // ---- STAGE 6: DNS ---- 223 gl_w("[6/8 DNS] publishing A @ + www -> our IP...\n" as *u8) 224 let dn: i64 = gl_slen(dom) 225 let r_root: i64 = nx_porkbun_set_a((raw as i64 + ao[0]) as *u8, an[0], (raw as i64 + so[0]) as *u8, sn2[0], dom, dn, "" as *u8, 0, ip, ipn, store, now, resp, PBD_RESP_CAP, rn) 226 let r_www: i64 = nx_porkbun_set_a((raw as i64 + ao[0]) as *u8, an[0], (raw as i64 + so[0]) as *u8, sn2[0], dom, dn, "www" as *u8, 3, ip, ipn, store, now, resp, PBD_RESP_CAP, rn) 227 gl_w(" A @ rc=" as *u8); gl_nn(r_root); gl_w(" A www rc=" as *u8); gl_nn(r_www); gl_w(" (0=OK; dns/create ADDS -- re-runs duplicate)\n" as *u8) 228 } 229 230 // ---- STAGE 7: CERT ---- 231 gl_w("[7/8 CERT] retrieving the TLS bundle...\n" as *u8) 232 if pbd_ssl(raw, ao[0], an[0], so[0], sn2[0], dom, store, now, resp, rn)==NX_PORKBUN_OK { 233 let boff: *i64 = sys_mmap(8) as *i64 234 let blen: *i64 = sys_mmap(8) as *i64 235 if gl_body(resp, rn[0], boff, blen)>=0 { 236 let outp: *u8 = sys_mmap(256) 237 var oo: i64 = 0 238 oo = pk_put(outp, 0, "/tmp/golive_" as *u8) 239 oo = pk_put(outp, oo, dom) 240 oo = pk_put(outp, oo, "_ssl.json" as *u8) 241 outp[oo]=0 as u8 242 let fd: i64 = sys_openat_wr(outp, 0x180) 243 if fd>=0 { sys_write(fd, (resp as i64 + boff[0]) as *u8, blen[0]); sys_close(fd) 244 gl_w(" bundle saved 0600 (" as *u8); gl_nn(blen[0]); gl_w(" bytes; key never printed): " as *u8); gl_w(outp); gl_w("\n" as *u8) } 245 } 246 } else { gl_w(" WARN: bundle not ready yet (LE issues async after DNS); retry `ssl` shortly.\n" as *u8) } 247 248 // ---- GATE B: edge ---- 249 gl_w("\n=== GATE B (EDGE) ===\n" as *u8) 250 if confirm_edge!=1 { 251 gl_w("Domain is REGISTERED and DNS/CERT are in flight. Edge SNI+route lands ON the sovereign host\n" as *u8) 252 gl_w("(contended NAS files) and stays OPERATOR-GATED: wire the SNI route + sites namespace via the\n" as *u8) 253 gl_w("never-brick mgmt plane, then verify a live 200. STOPPED at GATE B.\n" as *u8) 254 return 0 255 } 256 gl_w("confirm-edge given -- edge wiring is driven through the mgmt plane (not automated here to keep\n" as *u8) 257 gl_w("the contended-file safety); see the runbook. VERIFY a live 200 to finish.\n" as *u8) 258 return 0 259}