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}