code wiki / _hdl_build / nx_siteedit_daemon.nx

nx_siteedit_daemon.nx source

↩ module page · 721 lines · 43623 B

1// nx_siteedit_daemon.nx -- the SITE-VISUALS editor for admin.<domain>/site: the client edits their 2// site's .site BLUEPRINT (title/hero/cards/trust/footer = the site's DATA), previews the regenerated 3// whole-site at /preview/, and publishes it to the live docroot -- all through the SAME canonical 4// Modern Auth realm as the doc portal (same keysfile/storefile/realm => ONE client account works on 5// both; login happens against POST /admin/login on the same host, this daemon only VALIDATES tokens). 6// GET /site -> editor SPA shell (public shell; every action below needs the session) 7// GET /site/config -> the current .site blueprint bytes 8// POST /site/save -> body = the new blueprint; VALIDATED (title|/hero|/card|/footer| present, 9// bounded, no NUL) -> prior config preserved as <config>.v<epoch> (additive, 10// rule 13) -> atomic rename into place. Never a silent partial write. 11// POST /site/preview -> sb_build_site(config, <docroot>/preview, domain) -> site visible at 12// https://<domain>/preview/ (served hot; visitors' pages untouched) 13// POST /site/publish -> preview-build FIRST (proves the config builds), then live docroot pages 14// (.prev-index.html backup kept for instant revert) -- the client's call. 15// Composes: nx_site_build (sb_build_site, gated 8/8) + nx_status_daemon sd_* + Modern Auth validate. 16// Loopback HTTP daemon behind proxy_routes row `admin.<d> /site <port>`. Every response carries 17// Content-Length (the buffered-proxy contract). license_tier: ORIGINAL 18import "nx_status_daemon.nx" 19import "hub/nx_modern_auth_flow.nx" 20import "nx_syscalls.nx" 21import "_hdl_build/nx_site_build.nx" 22import "nx_cms_visual_builder.nx" 23import "nx_siteedit_history.nx" 24const SED_MAGIC_3600: i64 = 3600 25const SED_MAGIC_86400: i64 = 86400 26const SED_MAGIC_8192: i64 = 8192 27 28const SED_REQCAP: i64 = 65536 29const SED_OUTCAP: i64 = 262144 30const SED_CFGCAP: i64 = 32768 31 32// full HTTP response with Content-Length 33func sed_resp(out: *u8, status: *u8, ctype: *u8, body: *u8, blen: i64) -> i64 { 34 var o: i64 = 0 35 o = sd_cat(out, o, "HTTP/1.1 " as *u8) 36 o = sd_cat(out, o, status) 37 o = sd_cat(out, o, "\r\nContent-Type: " as *u8) 38 o = sd_cat(out, o, ctype) 39 o = sd_cat(out, o, "\r\nConnection: close\r\nContent-Length: " as *u8) 40 o = sd_catn(out, o, blen) 41 o = sd_cat(out, o, "\r\n\r\n" as *u8) 42 var i: i64 = 0 43 while i < blen { out[o] = body[i]; o = o + 1; i = i + 1 } 44 return o 45} 46 47// blueprint sanity: bounded, no NUL, and the load-bearing lines exist. 1 = ok. 48func sed_cfg_ok(b: *u8, n: i64) -> i64 { 49 if n < 20 { return 0 } 50 if n > SED_CFGCAP { return 0 } 51 var i: i64 = 0 52 while i < n { if (b[i] as i64) == 0 { return 0 } i = i + 1 } 53 var has_title: i64 = 0 54 var has_hero: i64 = 0 55 var has_card: i64 = 0 56 var has_footer: i64 = 0 57 var ls: i64 = 0 58 i = 0 59 while i <= n { 60 var eol: i64 = 0 61 if i == n { eol = 1 } 62 if i < n { if (b[i] as i64) == 10 { eol = 1 } } 63 if eol == 1 { 64 if sb_lm(b, ls, i, "title" as *u8, 5) >= 0 { has_title = 1 } 65 if sb_lm(b, ls, i, "hero" as *u8, 4) >= 0 { has_hero = 1 } 66 if sb_lm(b, ls, i, "card" as *u8, 4) >= 0 { has_card = 1 } 67 if sb_lm(b, ls, i, "footer" as *u8, 6) >= 0 { has_footer = 1 } 68 ls = i + 1 69 } 70 i = i + 1 71 } 72 if has_title == 1 { if has_hero == 1 { if has_card == 1 { if has_footer == 1 { return 1 } } } } 73 return 0 74} 75 76// write b[0..n) to <path>.new then atomic-rename over <path>. 0 ok / -1 fail. 77func sed_write_atomic(path: *u8, b: *u8, n: i64) -> i64 { 78 let np: *u8 = sys_mmap(600) 79 var o: i64 = sd_cat(np, 0, path) 80 o = sd_cat(np, o, ".new" as *u8) 81 np[o] = 0 as u8 82 let fd: i64 = sys_openat_wr(np, 0x1a4) 83 if fd < 0 { return 0 - 1 } 84 let w: i64 = sys_write(fd, b, n) 85 sys_close(fd) 86 if w != n { return 0 - 1 } 87 if sys_renameat(np, path) != 0 { return 0 - 1 } 88 return 0 89} 90 91// preserve the current config as <path>.v<epoch> (additive history). best-effort. 92func sed_version_keep(path: *u8) -> i64 { 93 let szp: *i64 = sys_mmap(16) as *i64 94 szp[0] = 0 95 let cur: *u8 = sys_read_file(path, szp) 96 if (cur as i64) == 0 { return 0 } 97 let vp: *u8 = sys_mmap(600) 98 var o: i64 = sd_cat(vp, 0, path) 99 o = sd_cat(vp, o, ".v" as *u8) 100 o = sd_catn(vp, o, sys_now_realtime_sec()) 101 vp[o] = 0 as u8 102 let fd: i64 = sys_openat_wr(vp, 0x1a4) 103 if fd < 0 { return 0 - 1 } 104 sys_write(fd, cur, szp[0]) 105 sys_close(fd) 106 return 0 107} 108 109// keep a .prev-index.html copy of the live homepage before publish (instant manual revert). 110func sed_backup_index(docroot: *u8) -> i64 { 111 let ip: *u8 = sys_mmap(600) 112 var o: i64 = sd_cat(ip, 0, docroot) 113 o = sd_cat(ip, o, "/index.html" as *u8) 114 ip[o] = 0 as u8 115 let szp: *i64 = sys_mmap(16) as *i64 116 szp[0] = 0 117 let cur: *u8 = sys_read_file(ip, szp) 118 if (cur as i64) == 0 { return 0 } 119 let bp: *u8 = sys_mmap(600) 120 o = sd_cat(bp, 0, docroot) 121 o = sd_cat(bp, o, "/.prev-index.html" as *u8) 122 bp[o] = 0 as u8 123 let fd: i64 = sys_openat_wr(bp, 0x1a4) 124 if fd < 0 { return 0 - 1 } 125 sys_write(fd, cur, szp[0]) 126 sys_close(fd) 127 return 0 128} 129 130// the editor SPA (public shell; actions carry X-Nishi-Session; login posts to /admin/login = the 131// doc-portal daemon on the SAME host, so ONE client account drives both portals). 132func sed_shell(domain: *u8, out: *u8) -> i64 { 133 let body: *u8 = sys_mmap(SED_OUTCAP) 134 var b: i64 = 0 135 b = sd_cat(body, b, "<!DOCTYPE html><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Site editor &mdash; " as *u8) 136 b = sd_cat(body, b, domain) 137 b = sd_cat(body, b, "</title><style>body{font-family:-apple-system,Segoe UI,sans-serif;max-width:900px;margin:5vh auto;padding:0 20px;color:#1c1c1e}input,textarea{width:100%;padding:9px;margin:.35rem 0;box-sizing:border-box;border:1px solid #ccc;border-radius:7px;font-family:ui-monospace,Consolas,monospace}textarea{min-height:340px}button{padding:9px 16px;border:0;border-radius:7px;background:#2a4d8f;color:#fff;font-size:.95rem;margin:.2rem .3rem .2rem 0;cursor:pointer}#pub{background:#0a6}.e{color:#b00;min-height:1.2em}.ok{color:#0a6}.leg{background:#f6f6f9;border:1px solid #dcdce2;border-radius:8px;padding:.8rem 1rem;font-size:.85rem}code{background:#eee;padding:1px 4px;border-radius:3px}</style>" as *u8) 138 b = sd_cat(body, b, "<div id=login><h2>&#128736; Site editor</h2><p>Sign in with your portal account (same as the document portal).</p><form id=lf method=post action=/site/login><input type=hidden name=ui value=1><input id=h name=handle placeholder=handle autocomplete=username autofocus><input id=p name=passphrase type=password placeholder=passphrase autocomplete=current-password><button type=submit id=b>Sign in</button></form><p id=e class=e></p></div>" as *u8) 139 b = sd_cat(body, b, "<div id=ed hidden><h2>Edit your site</h2><div class=leg>One line per element, fields separated by <code>|</code>:<br><code>title|Browser tab title</code> &middot; <code>header|Brand|Button text|#link</code> &middot; <code>hero|Big headline|Subtext|Button|#link</code> &middot; <code>card|Service name|One-line description</code> (each card becomes a page) &middot; <code>step|What happens</code> &middot; <code>sig|A trust point</code> &middot; <code>footer|Firm, location, phone</code></div>" as *u8) 140 b = sd_cat(body, b, "<textarea id=cfg spellcheck=false></textarea><br><button id=sv>Save</button><button id=pv>Preview</button><button id=pub>Publish live</button><p id=r class=e></p><p id=plink></p></div>" as *u8) 141 b = sd_cat(body, b, "<script>var L=document.getElementById('login'),ED=document.getElementById('ed'),E=document.getElementById('e'),R=document.getElementById('r'),PL=document.getElementById('plink');function H(){return{'X-Nishi-Session':sessionStorage.nx_sess||''}}" as *u8) 142 b = sd_cat(body, b, "function loadCfg(){fetch('/site/config',{headers:H()}).then(function(r){if(!r.ok)throw 0;return r.text()}).then(function(t){document.getElementById('cfg').value=t;L.hidden=true;ED.hidden=false}).catch(function(){E.textContent='Session invalid: sign in.'})}" as *u8) 143 b = sd_cat(body, b, "document.getElementById('lf').onsubmit=function(){E.textContent='';var q='handle='+encodeURIComponent(h.value)+'&passphrase='+encodeURIComponent(p.value);fetch('/site/login',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:q}).then(function(r){if(r.ok)return r.json();throw 0}).then(function(j){sessionStorage.nx_sess=j.token;loadCfg()}).catch(function(){E.textContent='Wrong handle or passphrase.'});return false};" as *u8) 144 b = sd_cat(body, b, "function act(p,okmsg){R.textContent='';PL.innerHTML='';fetch(p,{method:'POST',headers:H(),body:document.getElementById('cfg').value}).then(function(r){return r.text().then(function(t){return{ok:r.ok,t:t}})}).then(function(x){R.className=x.ok?'ok':'e';R.textContent=x.t;if(x.ok&&p=='/site/preview'){PL.innerHTML='<a href=\"/preview/\" target=_blank>Open the preview &rarr;</a>'}if(x.ok&&p=='/site/publish'){PL.innerHTML='<a href=\"/\" target=_blank>See it live &rarr;</a>'}}).catch(function(){R.className='e';R.textContent='Request failed.'})}" as *u8) 145 b = sd_cat(body, b, "document.getElementById('sv').onclick=function(){act('/site/save')};document.getElementById('pv').onclick=function(){act('/site/save');setTimeout(function(){act('/site/preview')},400)};document.getElementById('pub').onclick=function(){if(confirm('Publish these changes to the LIVE site?')){act('/site/save');setTimeout(function(){act('/site/publish')},400)}};</script>" as *u8) 146 return sed_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, body, b) 147} 148 149// ===== NISHI-FIRST no-JS site editor (2026-07-05 doctrine). Real forms carrying the no-cookie session in 150// ?s= (shared nx_sa_validate_qs); the client edits + previews + publishes with ZERO JS. Login mints on 151// the SHARED realm ctx (same account as the doc portal). NO Set-Cookie (C1 preserved). ===== 152 153// escape & and < for safe embedding of the config into a <textarea>. returns new offset. 154func sed_esc(src: *u8, n: i64, out: *u8, start: i64) -> i64 { 155 var o: i64 = start 156 var i: i64 = 0 157 while i < n { 158 let c: i64 = src[i] as i64 159 if c == 38 { out[o] = 38 as u8; out[o+1] = 97 as u8; out[o+2] = 109 as u8; out[o+3] = 112 as u8; out[o+4] = 59 as u8; o = o + 5 } 160 else { if c == 60 { out[o] = 38 as u8; out[o+1] = 108 as u8; out[o+2] = 116 as u8; out[o+3] = 59 as u8; o = o + 4 } 161 else { out[o] = c as u8; o = o + 1 } } 162 i = i + 1 163 } 164 return o 165} 166 167// splice raw (%-encoded) session token into body at offset b; returns new offset. 168func sed_splice(body: *u8, b: i64, sraw: *u8, srn: i64) -> i64 { 169 var o: i64 = b 170 var i: i64 = 0 171 while i < srn { body[o] = sraw[i]; o = o + 1; i = i + 1 } 172 return o 173} 174 175// no-JS landing after a form login -> link into the editor carrying the session in ?s=. 176func sed_landing(out: *u8, b64: *u8, b64n: i64) -> i64 { 177 let body: *u8 = sys_mmap(SED_OUTCAP) 178 var b: i64 = 0 179 b = sd_cat(body, b, "<!DOCTYPE html><meta charset=utf-8><title>Signed in</title><body style=\"font-family:sans-serif;max-width:900px;margin:6vh auto;padding:0 20px\"><h2>&#128736; Signed in</h2><p>No-JS site editor (nishi-first).</p><p><a href=\"/site/ed?s=" as *u8) 180 b = nx_sa_tok_urlenc(b64, b64n, body, b) 181 b = sd_cat(body, b, "\">Open the editor &rarr;</a></p>" as *u8) 182 return sed_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, body, b) 183} 184 185// the VISUAL no-JS editor (R-CANVAS v1, default at /site/ed): the blueprint as a per-field FORM 186// (nx_cms_visual_builder) -- field-level WYSIWYG editing, zero client JS; Save posts to /site/vsave where 187// the server REBUILDS the blueprint and runs the SAME validation/versioning as every other save path. 188func sed_editor_page(cfgpath: *u8, sraw: *u8, srn: i64, out: *u8) -> i64 { 189 let body: *u8 = sys_mmap(SED_OUTCAP) 190 var b: i64 = 0 191 b = sd_cat(body, b, "<!DOCTYPE html><html lang=\"en\"><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Site editor</title><body style=\"font-family:sans-serif;max-width:900px;margin:4vh auto;padding:0 20px;color:rgb(28,28,30)\"><h2>Edit your site</h2><p style=\"font-size:.85rem;color:rgb(90,96,115)\">Every field below is a piece of your live site. Save, then Preview, then Publish. Prefer the raw one-line-per-element view? <a href=\"/site/edraw?s=" as *u8) 192 b = sed_splice(body, b, sraw, srn) 193 b = sd_cat(body, b, "\">Open raw mode</a>.</p>" as *u8) 194 let szp: *i64 = sys_mmap(16) as *i64 195 szp[0] = 0 196 let cur: *u8 = sys_read_file(cfgpath, szp) 197 var cn: i64 = 0 198 if (cur as i64) != 0 { cn = szp[0] } 199 b = vb_form_page(cur, cn, sraw, srn, body, b) 200 // Preview + Publish forms (operate on the SAVED config; same endpoints as the raw flow) 201 b = sd_cat(body, b, "<form method=post action=\"/site/preview2?s=" as *u8) 202 b = sed_splice(body, b, sraw, srn) 203 b = sd_cat(body, b, "\" style=\"display:inline\"><button type=submit style=\"padding:9px 16px;border:0;border-radius:7px;background:rgb(85,85,102);color:rgb(255,255,255)\">Preview</button></form> " as *u8) 204 b = sd_cat(body, b, "<form method=post action=\"/site/publish2?s=" as *u8) 205 b = sed_splice(body, b, sraw, srn) 206 b = sd_cat(body, b, "\" style=\"display:inline\" onsubmit=\"return confirm('Publish to the LIVE site?')\"><button type=submit style=\"padding:9px 16px;border:0;border-radius:7px;background:rgb(0,170,102);color:rgb(255,255,255)\">Publish live</button></form>" as *u8) 207 b = sd_cat(body, b, "<p style=\"margin-top:16px;font-size:.85rem\"><a href=\"/site/history?s=" as *u8) 208 b = sed_splice(body, b, sraw, srn) 209 b = sd_cat(body, b, "\">&#8630; Revision history</a> &middot; every save is a restorable snapshot.</p>" as *u8) 210 return sed_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, body, b) 211} 212 213// the RAW no-JS editor page (fallback at /site/edraw): current blueprint in a <textarea> + Save form; 214// Preview + Publish forms operate on the SAVED config (same as the JS flow). All carry ?s=. 215func sed_editor_raw(cfgpath: *u8, sraw: *u8, srn: i64, out: *u8) -> i64 { 216 let body: *u8 = sys_mmap(SED_OUTCAP) 217 var b: i64 = 0 218 b = sd_cat(body, b, "<!DOCTYPE html><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Site editor</title><body style=\"font-family:sans-serif;max-width:900px;margin:4vh auto;padding:0 20px\"><h2>Edit your site</h2><div style=\"background:#f6f6f9;border:1px solid #dcdce2;border-radius:8px;padding:.8rem 1rem;font-size:.85rem\">One line per element, fields separated by <code>|</code>: title| header| hero| card| step| sig| footer|</div>" as *u8) 219 // Save form (textarea prefilled with the current config) 220 b = sd_cat(body, b, "<form method=post action=\"/site/save2?s=" as *u8) 221 b = sed_splice(body, b, sraw, srn) 222 b = sd_cat(body, b, "\"><textarea name=cfg spellcheck=false style=\"width:100%;min-height:340px;font-family:ui-monospace,Consolas,monospace;padding:9px;border:1px solid #ccc;border-radius:7px\">" as *u8) 223 let szp: *i64 = sys_mmap(16) as *i64 224 szp[0] = 0 225 let cur: *u8 = sys_read_file(cfgpath, szp) 226 if (cur as i64) != 0 { b = sed_esc(cur, szp[0], body, b) } 227 b = sd_cat(body, b, "</textarea><p><button type=submit style=\"padding:9px 16px;border:0;border-radius:7px;background:#2a4d8f;color:#fff\">Save</button></p></form>" as *u8) 228 // Preview + Publish forms (no body; build from the SAVED config) 229 b = sd_cat(body, b, "<form method=post action=\"/site/preview2?s=" as *u8) 230 b = sed_splice(body, b, sraw, srn) 231 b = sd_cat(body, b, "\" style=\"display:inline\"><button type=submit style=\"padding:9px 16px;border:0;border-radius:7px;background:#556;color:#fff\">Preview</button></form> " as *u8) 232 b = sd_cat(body, b, "<form method=post action=\"/site/publish2?s=" as *u8) 233 b = sed_splice(body, b, sraw, srn) 234 b = sd_cat(body, b, "\" style=\"display:inline\" onsubmit=\"return confirm('Publish to the LIVE site?')\"><button type=submit style=\"padding:9px 16px;border:0;border-radius:7px;background:#0a6;color:#fff\">Publish live</button></form>" as *u8) 235 return sed_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, body, b) 236} 237 238// no-JS action RESULT page: the outcome + links back to the editor / preview / live. 239func sed_result_page(msg: *u8, msglen: i64, sraw: *u8, srn: i64, out: *u8) -> i64 { 240 let body: *u8 = sys_mmap(SED_OUTCAP) 241 var b: i64 = 0 242 b = sd_cat(body, b, "<!DOCTYPE html><meta charset=utf-8><title>Site editor</title><body style=\"font-family:sans-serif;max-width:900px;margin:5vh auto;padding:0 20px\"><p>" as *u8) 243 var i: i64 = 0 244 while i < msglen { body[b] = msg[i]; b = b + 1; i = i + 1 } 245 b = sd_cat(body, b, "</p><p><a href=\"/site/ed?s=" as *u8) 246 b = sed_splice(body, b, sraw, srn) 247 b = sd_cat(body, b, "\">&larr; Editor</a> &nbsp; <a href=\"/preview/\" target=_blank>Preview</a> &nbsp; <a href=\"/\" target=_blank>Live site</a></p>" as *u8) 248 return sed_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, body, b) 249} 250 251// ---- THE ROUTER: request bytes -> response bytes (the gate drives this directly). ---- 252// "N seconds/minutes/hours/days ago" for a past epoch (revision-history readability). 253func sed_relage(now: i64, past: i64, body: *u8, b: i64) -> i64 { 254 var d: i64 = now - past 255 if d < 0 { d = 0 } 256 var o: i64 = b 257 if d < 60 { o = sd_catn(body, o, d); o = sd_cat(body, o, " seconds ago" as *u8) } 258 else { if d < SED_MAGIC_3600 { o = sd_catn(body, o, d / 60); o = sd_cat(body, o, " minutes ago" as *u8) } 259 else { if d < SED_MAGIC_86400 { o = sd_catn(body, o, d / SED_MAGIC_3600); o = sd_cat(body, o, " hours ago" as *u8) } 260 else { o = sd_catn(body, o, d / SED_MAGIC_86400); o = sd_cat(body, o, " days ago" as *u8) } } } 261 return o 262} 263 264// GET /site/history -- browse every saved snapshot (sh_list) newest-first, each with view + restore. 265func sed_history_page(cfgpath: *u8, sraw: *u8, srn: i64, out: *u8) -> i64 { 266 let body: *u8 = sys_mmap(SED_OUTCAP) 267 var b: i64 = 0 268 b = sd_cat(body, b, "<!DOCTYPE html><html lang=\"en\"><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Revision history</title><body style=\"font-family:sans-serif;max-width:900px;margin:4vh auto;padding:0 20px;color:rgb(28,28,30)\"><h2>Revision history</h2><p style=\"font-size:.85rem;color:rgb(90,96,115)\">Every save keeps a snapshot. Restore is additive &mdash; the current version is snapshotted first, so any restore is itself undoable.</p>" as *u8) 269 let eps: *i64 = sys_mmap(8 * 300) 270 let cnt: i64 = sh_list(cfgpath, eps, 300) 271 sh_sort_desc(eps, cnt) 272 let now: i64 = sys_now_realtime_sec() 273 if cnt == 0 { b = sd_cat(body, b, "<p>No saved versions yet. Edit and Save to build history.</p>" as *u8) } 274 var i: i64 = 0 275 while i < cnt { 276 let ep: i64 = eps[i] 277 b = sd_cat(body, b, "<div style=\"border:1px solid rgb(224,224,230);border-radius:8px;padding:10px 14px;margin:8px 0;display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap\"><span>Saved " as *u8) 278 b = sed_relage(now, ep, body, b) 279 b = sd_cat(body, b, " <span style=\"color:rgb(150,155,170);font-size:.8rem\">(v" as *u8) 280 b = sd_catn(body, b, ep) 281 b = sd_cat(body, b, ")</span></span><span><a style=\"margin-right:12px\" href=\"/site/vview?s=" as *u8) 282 b = sed_splice(body, b, sraw, srn) 283 b = sd_cat(body, b, "&v=" as *u8) 284 b = sd_catn(body, b, ep) 285 b = sd_cat(body, b, "\">view</a><form method=post style=\"display:inline\" action=\"/site/restore?s=" as *u8) 286 b = sed_splice(body, b, sraw, srn) 287 b = sd_cat(body, b, "&v=" as *u8) 288 b = sd_catn(body, b, ep) 289 b = sd_cat(body, b, "\"><button type=submit style=\"padding:7px 13px;border:0;border-radius:6px;background:rgb(85,85,102);color:rgb(255,255,255)\">Restore</button></form></span></div>" as *u8) 290 i = i + 1 291 } 292 b = sd_cat(body, b, "<p style=\"margin-top:18px\"><a href=\"/site/ed?s=" as *u8) 293 b = sed_splice(body, b, sraw, srn) 294 b = sd_cat(body, b, "\">&larr; Back to editor</a></p></body></html>" as *u8) 295 return sed_resp(out, "200 OK" as *u8, "text/html; charset=utf-8" as *u8, body, b) 296} 297 298// POST /site/restore?s=&v=<epoch> -- restore a snapshot. ADDITIVE: snapshot CURRENT first (sed_version_keep), 299// validate the snapshot, then atomic-write it as the working config. A mis-restore is itself undoable. 300func sed_do_restore(cfgpath: *u8, path: *u8, pn: i64, sraw: *u8, srn: i64, out: *u8) -> i64 { 301 let msg: *u8 = sys_mmap(300) 302 var ml: i64 = 0 303 let ep: i64 = sh_qs_epoch(path, pn) 304 if ep <= 0 { ml = sd_cat(msg, 0, "Restore failed: no version selected." as *u8); return sed_result_page(msg, ml, sraw, srn, out) } 305 let vp: *u8 = sys_mmap(700) 306 sh_version_path(cfgpath, ep, vp) 307 let szp: *i64 = sys_mmap(16) as *i64 308 szp[0] = 0 309 let content: *u8 = sys_read_file(vp, szp) 310 if (content as i64) == 0 { ml = sd_cat(msg, 0, "Restore failed: that snapshot no longer exists." as *u8); return sed_result_page(msg, ml, sraw, srn, out) } 311 if sed_cfg_ok(content, szp[0]) == 0 { ml = sd_cat(msg, 0, "Restore refused: the snapshot is not a valid site config." as *u8); return sed_result_page(msg, ml, sraw, srn, out) } 312 sed_version_keep(cfgpath) 313 if sed_write_atomic(cfgpath, content, szp[0]) == 0 { 314 ml = sd_cat(msg, 0, "Restored (your previous state was snapshotted first). Preview, then Publish." as *u8) 315 } else { ml = sd_cat(msg, 0, "Restore failed writing the config." as *u8) } 316 return sed_result_page(msg, ml, sraw, srn, out) 317} 318 319// GET /site/vview?s=&v=<epoch> -- see a snapshot's raw blueprint before restoring (text/plain). 320func sed_view_version(cfgpath: *u8, path: *u8, pn: i64, out: *u8) -> i64 { 321 let eb: *u8 = sys_mmap(64) 322 let ep: i64 = sh_qs_epoch(path, pn) 323 if ep <= 0 { let en: i64 = sd_cat(eb, 0, "no version selected" as *u8); return sed_resp(out, "400 Bad Request" as *u8, "text/plain; charset=utf-8" as *u8, eb, en) } 324 let vp: *u8 = sys_mmap(700) 325 sh_version_path(cfgpath, ep, vp) 326 let szp: *i64 = sys_mmap(16) as *i64 327 szp[0] = 0 328 let content: *u8 = sys_read_file(vp, szp) 329 if (content as i64) == 0 { let en2: i64 = sd_cat(eb, 0, "snapshot not found" as *u8); return sed_resp(out, "404 Not Found" as *u8, "text/plain; charset=utf-8" as *u8, eb, en2) } 330 return sed_resp(out, "200 OK" as *u8, "text/plain; charset=utf-8" as *u8, content, szp[0]) 331} 332 333func sed_handle(ctx: *NxAuthContext, domain: *u8, cfgpath: *u8, docroot: *u8, req: *u8, req_n: i64, out: *u8) -> i64 { 334 let poff: *i64 = sys_mmap(8) as *i64 335 let plen: *i64 = sys_mmap(8) as *i64 336 poff[0] = 0 337 plen[0] = 0 338 sd_find_path(req, req_n, poff, plen) 339 let path: *u8 = ((req as i64) + poff[0]) as *u8 340 let pn: i64 = plen[0] 341 let is_post: i64 = (req[0] == 80 as u8) as i64 342 var o: i64 = 0 343 344 // ===== NISHI-FIRST no-JS routes (early-return; ?s= session, mint on the shared realm). Matched BEFORE 345 // the header-gated /site/ actions so their /site/ prefix doesn't fall into them. ===== 346 if sd_starts(path, pn, "/site/login" as *u8) == 1 { 347 if is_post == 1 { 348 let body_off: i64 = sd_body_off(req, req_n) 349 let fb: *u8 = ((req as i64) + body_off) as *u8 350 let fbn: i64 = req_n - body_off 351 let hoff: *i64 = sys_mmap(8) as *i64 352 let hnf: *i64 = sys_mmap(8) as *i64 353 let poff2: *i64 = sys_mmap(8) as *i64 354 let pnf: *i64 = sys_mmap(8) as *i64 355 var got: i64 = 0 356 if sd_form_field(fb, fbn, "handle" as *u8, 6, hoff, hnf) == 1 { 357 if sd_form_field(fb, fbn, "passphrase" as *u8, 10, poff2, pnf) == 1 { got = 1 } 358 } 359 var uiflag: i64 = 0 360 let uoff: *i64 = sys_mmap(8) as *i64 361 let un: *i64 = sys_mmap(8) as *i64 362 if sd_form_field(fb, fbn, "ui" as *u8, 2, uoff, un) == 1 { uiflag = 1 } 363 var ok: i64 = 0 364 if got == 1 { 365 let hbuf: *u8 = sys_mmap(256) 366 let pbuf: *u8 = sys_mmap(512) 367 let hsrc: *u8 = ((fb as i64) + hoff[0]) as *u8 368 let psrc: *u8 = ((fb as i64) + poff2[0]) as *u8 369 let h_dec: i64 = sd_urldecode(hsrc, hnf[0], hbuf, 255) 370 let p_dec: i64 = sd_urldecode(psrc, pnf[0], pbuf, 511) 371 if h_dec > 0 { if p_dec > 0 { 372 let tok: *u8 = sys_mmap(NX_MAUTH_SESSION_TOKEN_BYTES) 373 let tok_n: *i64 = sys_mmap(8) as *i64 374 tok_n[0] = 0 375 if nx_modern_auth_login(ctx, hbuf, h_dec, pbuf, p_dec, tok, NX_MAUTH_SESSION_TOKEN_BYTES, tok_n) == NX_MAUTH_OK { 376 let b64: *u8 = sys_mmap(256) 377 let b64_n: i64 = b64_encode(tok, NX_MAUTH_SESSION_TOKEN_BYTES, b64) 378 if uiflag == 1 { o = sed_landing(out, b64, b64_n) } 379 else { 380 o = sd_cat(out, o, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: " as *u8) 381 o = sd_catn(out, o, 12 + b64_n) 382 o = sd_cat(out, o, "\r\n\r\n{\"token\":\"" as *u8) 383 var z: i64 = 0 384 while z < b64_n { out[o] = b64[z]; o = o + 1; z = z + 1 } 385 o = sd_cat(out, o, "\"}" as *u8) 386 } 387 ok = 1 388 } 389 } } 390 } 391 if ok == 0 { 392 if uiflag == 1 { o = sd_cat(out, 0, "HTTP/1.1 401 Unauthorized\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nContent-Length: 101\r\n\r\n<!DOCTYPE html><meta charset=utf-8><p>Wrong handle or passphrase.</p><p><a href=\"/\">Try again</a></p>" as *u8) } 393 else { o = sd_emit_401_json(out) } 394 } 395 } else { o = sed_shell(domain, out) } 396 return o 397 } 398 // NOTE: /site/edraw MUST be matched before /site/ed (prefix). 399 if sd_starts(path, pn, "/site/edraw" as *u8) == 1 { 400 let now_er: i64 = sys_now_realtime_sec() 401 let hbr: *u8 = sys_mmap(128) 402 let hbrn: *i64 = sys_mmap(8) as *i64 403 if nx_sa_validate_qs(ctx, path, pn, now_er, hbr, 128, hbrn) == NX_MAUTH_OK { 404 let voffr: *i64 = sys_mmap(8) as *i64 405 let srnr: i64 = nx_sa_qs_raw(path, pn, voffr) 406 let srawr: *u8 = ((path as i64) + voffr[0]) as *u8 407 return sed_editor_raw(cfgpath, srawr, srnr, out) 408 } 409 return sd_emit_401_json(out) 410 } 411 if sd_starts(path, pn, "/site/ed" as *u8) == 1 { 412 let now_e: i64 = sys_now_realtime_sec() 413 let hb: *u8 = sys_mmap(128) 414 let hbn: *i64 = sys_mmap(8) as *i64 415 if nx_sa_validate_qs(ctx, path, pn, now_e, hb, 128, hbn) == NX_MAUTH_OK { 416 let voff: *i64 = sys_mmap(8) as *i64 417 let srn: i64 = nx_sa_qs_raw(path, pn, voff) 418 let sraw: *u8 = ((path as i64) + voff[0]) as *u8 419 return sed_editor_page(cfgpath, sraw, srn, out) 420 } 421 return sd_emit_401_json(out) 422 } 423 // VISUAL editor save: rebuild the blueprint from the posted form fields, then the SAME validation, 424 // versioning, and atomic write as save2 (the safety story is shared, not forked). 425 if sd_starts(path, pn, "/site/vsave" as *u8) == 1 { 426 if is_post == 1 { 427 let now_vs: i64 = sys_now_realtime_sec() 428 let hbv: *u8 = sys_mmap(128) 429 let hbvn: *i64 = sys_mmap(8) as *i64 430 if nx_sa_validate_qs(ctx, path, pn, now_vs, hbv, 128, hbvn) == NX_MAUTH_OK { 431 let voffv: *i64 = sys_mmap(8) as *i64 432 let srnv: i64 = nx_sa_qs_raw(path, pn, voffv) 433 let srawv: *u8 = ((path as i64) + voffv[0]) as *u8 434 let body_offv: i64 = sd_body_off(req, req_n) 435 let fbv: *u8 = ((req as i64) + body_offv) as *u8 436 let fbvn: i64 = req_n - body_offv 437 let cfgv: *u8 = sys_mmap(SED_OUTCAP) 438 let v_len: i64 = vb_rebuild(fbv, fbvn, cfgv) 439 let msgv: *u8 = sys_mmap(256) 440 var mlv: i64 = 0 441 if v_len > 0 { 442 if sed_cfg_ok(cfgv, v_len) == 1 { 443 sed_version_keep(cfgpath) 444 if sed_write_atomic(cfgpath, cfgv, v_len) == 0 { 445 mlv = sd_cat(msgv, 0, "Saved from the visual editor (previous version preserved). Preview, then Publish." as *u8) 446 } else { mlv = sd_cat(msgv, 0, "Save failed writing the config." as *u8) } 447 } else { mlv = sd_cat(msgv, 0, "Refused: the site needs a title, a hero headline, at least one card, and a footer." as *u8) } 448 } else { mlv = sd_cat(msgv, 0, "Refused: no fields submitted." as *u8) } 449 return sed_result_page(msgv, mlv, srawv, srnv, out) 450 } 451 return sd_emit_401_json(out) 452 } 453 return sd_emit_401_json(out) 454 } 455 // Revision history: browse (GET), restore (POST, additive), view a snapshot (GET). All session-gated like save. 456 if sd_starts(path, pn, "/site/history" as *u8) == 1 { 457 let now_h: i64 = sys_now_realtime_sec() 458 let hbh: *u8 = sys_mmap(128) 459 let hbhn: *i64 = sys_mmap(8) as *i64 460 if nx_sa_validate_qs(ctx, path, pn, now_h, hbh, 128, hbhn) == NX_MAUTH_OK { 461 let voffh: *i64 = sys_mmap(8) as *i64 462 let srnh: i64 = nx_sa_qs_raw(path, pn, voffh) 463 let srawh: *u8 = ((path as i64) + voffh[0]) as *u8 464 return sed_history_page(cfgpath, srawh, srnh, out) 465 } 466 return sd_emit_401_json(out) 467 } 468 if sd_starts(path, pn, "/site/restore" as *u8) == 1 { 469 if is_post == 1 { 470 let now_r2: i64 = sys_now_realtime_sec() 471 let hbr2: *u8 = sys_mmap(128) 472 let hbr2n: *i64 = sys_mmap(8) as *i64 473 if nx_sa_validate_qs(ctx, path, pn, now_r2, hbr2, 128, hbr2n) == NX_MAUTH_OK { 474 let voffr2: *i64 = sys_mmap(8) as *i64 475 let srnr2: i64 = nx_sa_qs_raw(path, pn, voffr2) 476 let srawr2: *u8 = ((path as i64) + voffr2[0]) as *u8 477 return sed_do_restore(cfgpath, path, pn, srawr2, srnr2, out) 478 } 479 return sd_emit_401_json(out) 480 } 481 return sd_emit_401_json(out) 482 } 483 if sd_starts(path, pn, "/site/vview" as *u8) == 1 { 484 let now_vv: i64 = sys_now_realtime_sec() 485 let hbvv: *u8 = sys_mmap(128) 486 let hbvvn: *i64 = sys_mmap(8) as *i64 487 if nx_sa_validate_qs(ctx, path, pn, now_vv, hbvv, 128, hbvvn) == NX_MAUTH_OK { 488 return sed_view_version(cfgpath, path, pn, out) 489 } 490 return sd_emit_401_json(out) 491 } 492 if sd_starts(path, pn, "/site/save2" as *u8) == 1 { 493 if is_post == 1 { 494 let now_s2: i64 = sys_now_realtime_sec() 495 let hb: *u8 = sys_mmap(128) 496 let hbn: *i64 = sys_mmap(8) as *i64 497 if nx_sa_validate_qs(ctx, path, pn, now_s2, hb, 128, hbn) == NX_MAUTH_OK { 498 let voff: *i64 = sys_mmap(8) as *i64 499 let srn: i64 = nx_sa_qs_raw(path, pn, voff) 500 let sraw: *u8 = ((path as i64) + voff[0]) as *u8 501 let body_off: i64 = sd_body_off(req, req_n) 502 let fb: *u8 = ((req as i64) + body_off) as *u8 503 let fbn: i64 = req_n - body_off 504 let coff: *i64 = sys_mmap(8) as *i64 505 let cnf: *i64 = sys_mmap(8) as *i64 506 let msg: *u8 = sys_mmap(256) 507 var ml: i64 = 0 508 if sd_form_field(fb, fbn, "cfg" as *u8, 3, coff, cnf) == 1 { 509 let csrc: *u8 = ((fb as i64) + coff[0]) as *u8 510 let cfgb: *u8 = sys_mmap(SED_CFGCAP) 511 let c_dec: i64 = sd_urldecode(csrc, cnf[0], cfgb, SED_CFGCAP - 1) 512 if sed_cfg_ok(cfgb, c_dec) == 1 { 513 sed_version_keep(cfgpath) 514 if sed_write_atomic(cfgpath, cfgb, c_dec) == 0 { 515 ml = sd_cat(msg, 0, "Saved (previous version preserved). Preview, then Publish." as *u8) 516 } else { ml = sd_cat(msg, 0, "Save failed writing the config." as *u8) } 517 } else { ml = sd_cat(msg, 0, "Refused: keep title| hero| a card| and footer| lines. Nothing changed." as *u8) } 518 } else { ml = sd_cat(msg, 0, "Refused: no config submitted." as *u8) } 519 return sed_result_page(msg, ml, sraw, srn, out) 520 } 521 return sd_emit_401_json(out) 522 } 523 return sd_emit_401_json(out) 524 } 525 if sd_starts(path, pn, "/site/preview2" as *u8) == 1 { 526 if is_post == 1 { 527 let now_p2: i64 = sys_now_realtime_sec() 528 let hb: *u8 = sys_mmap(128) 529 let hbn: *i64 = sys_mmap(8) as *i64 530 if nx_sa_validate_qs(ctx, path, pn, now_p2, hb, 128, hbn) == NX_MAUTH_OK { 531 let voff: *i64 = sys_mmap(8) as *i64 532 let srn: i64 = nx_sa_qs_raw(path, pn, voff) 533 let sraw: *u8 = ((path as i64) + voff[0]) as *u8 534 let pd: *u8 = sys_mmap(600) 535 var po: i64 = sd_cat(pd, 0, docroot) 536 po = sd_cat(pd, po, "/preview" as *u8) 537 pd[po] = 0 as u8 538 let nc: i64 = sb_build_site(cfgpath, pd, domain, 0) 539 let msg: *u8 = sys_mmap(256) 540 var ml: i64 = 0 541 if nc >= 1 { ml = sd_cat(msg, 0, "Preview built at /preview/ (live pages untouched)." as *u8) } else { ml = sd_cat(msg, 0, "Preview failed: the config did not build." as *u8) } 542 return sed_result_page(msg, ml, sraw, srn, out) 543 } 544 return sd_emit_401_json(out) 545 } 546 return sd_emit_401_json(out) 547 } 548 if sd_starts(path, pn, "/site/publish2" as *u8) == 1 { 549 if is_post == 1 { 550 let now_pub: i64 = sys_now_realtime_sec() 551 let hb: *u8 = sys_mmap(128) 552 let hbn: *i64 = sys_mmap(8) as *i64 553 if nx_sa_validate_qs(ctx, path, pn, now_pub, hb, 128, hbn) == NX_MAUTH_OK { 554 let voff: *i64 = sys_mmap(8) as *i64 555 let srn: i64 = nx_sa_qs_raw(path, pn, voff) 556 let sraw: *u8 = ((path as i64) + voff[0]) as *u8 557 let pd2: *u8 = sys_mmap(600) 558 var po2: i64 = sd_cat(pd2, 0, docroot) 559 po2 = sd_cat(pd2, po2, "/preview" as *u8) 560 pd2[po2] = 0 as u8 561 let nc2: i64 = sb_build_site(cfgpath, pd2, domain, 0) 562 let msg: *u8 = sys_mmap(256) 563 var ml: i64 = 0 564 if nc2 >= 1 { 565 sed_backup_index(docroot) 566 let nc3: i64 = sb_build_site(cfgpath, docroot, domain, 0) 567 if nc3 >= 1 { ml = sd_cat(msg, 0, "Published live (previous homepage kept as .prev-index.html)." as *u8) } else { ml = sd_cat(msg, 0, "Publish failed building the live docroot." as *u8) } 568 } else { ml = sd_cat(msg, 0, "Publish refused: the config did not build cleanly. Live untouched." as *u8) } 569 return sed_result_page(msg, ml, sraw, srn, out) 570 } 571 return sd_emit_401_json(out) 572 } 573 return sd_emit_401_json(out) 574 } 575 576 // every /site/<action> is session-gated; bare /site serves the public shell. 577 var action: i64 = 0 578 if pn > 5 { if sd_starts(path, pn, "/site/" as *u8) == 1 { action = 1 } } 579 if action == 1 { 580 let now_s: i64 = sys_now_realtime_sec() 581 if nx_sa_validate(ctx, req, req_n, now_s) == NX_MAUTH_OK { 582 let frag: *u8 = sys_mmap(SED_OUTCAP) 583 if sd_starts(path, pn, "/site/config" as *u8) == 1 { 584 let szp: *i64 = sys_mmap(16) as *i64 585 szp[0] = 0 586 let cur: *u8 = sys_read_file(cfgpath, szp) 587 if (cur as i64) != 0 { 588 o = sed_resp(out, "200 OK" as *u8, "text/plain; charset=utf-8" as *u8, cur, szp[0]) 589 } else { 590 let fb: i64 = sd_cat(frag, 0, "no config yet" as *u8) 591 o = sed_resp(out, "404 Not Found" as *u8, "text/plain" as *u8, frag, fb) 592 } 593 } else { if sd_starts(path, pn, "/site/save" as *u8) == 1 { 594 var saved: i64 = 0 595 if is_post == 1 { 596 let body_off: i64 = sd_body_off(req, req_n) 597 let body: *u8 = ((req as i64) + body_off) as *u8 598 let body_n: i64 = req_n - body_off 599 if sed_cfg_ok(body, body_n) == 1 { 600 sed_version_keep(cfgpath) 601 if sed_write_atomic(cfgpath, body, body_n) == 0 { 602 var fb2: i64 = sd_cat(frag, 0, "SAVED " as *u8) 603 fb2 = sd_catn(frag, fb2, body_n) 604 fb2 = sd_cat(frag, fb2, " bytes (previous version preserved). Now Preview, then Publish." as *u8) 605 o = sed_resp(out, "200 OK" as *u8, "text/plain" as *u8, frag, fb2) 606 saved = 1 607 } 608 } 609 } 610 if saved == 0 { 611 let fb3: i64 = sd_cat(frag, 0, "REFUSED: config must keep title| hero| at least one card| and footer| lines (bounded, no NUL). Nothing was changed." as *u8) 612 o = sed_resp(out, "400 Bad Request" as *u8, "text/plain" as *u8, frag, fb3) 613 } 614 } else { if sd_starts(path, pn, "/site/preview" as *u8) == 1 { 615 var prev_ok: i64 = 0 616 if is_post == 1 { 617 let pd: *u8 = sys_mmap(600) 618 var po: i64 = sd_cat(pd, 0, docroot) 619 po = sd_cat(pd, po, "/preview" as *u8) 620 pd[po] = 0 as u8 621 let nc: i64 = sb_build_site(cfgpath, pd, domain, 0) 622 if nc >= 1 { 623 var fb4: i64 = sd_cat(frag, 0, "PREVIEW BUILT: index + " as *u8) 624 fb4 = sd_catn(frag, fb4, nc) 625 fb4 = sd_cat(frag, fb4, " service pages at /preview/ (live pages untouched)." as *u8) 626 o = sed_resp(out, "200 OK" as *u8, "text/plain" as *u8, frag, fb4) 627 prev_ok = 1 628 } 629 } 630 if prev_ok == 0 { 631 let fb5: i64 = sd_cat(frag, 0, "PREVIEW FAILED: the config did not build. Live pages untouched." as *u8) 632 o = sed_resp(out, "400 Bad Request" as *u8, "text/plain" as *u8, frag, fb5) 633 } 634 } else { if sd_starts(path, pn, "/site/publish" as *u8) == 1 { 635 var pub_ok: i64 = 0 636 if is_post == 1 { 637 // prove the config builds (preview dir), THEN touch the live docroot. 638 let pd2: *u8 = sys_mmap(600) 639 var po2: i64 = sd_cat(pd2, 0, docroot) 640 po2 = sd_cat(pd2, po2, "/preview" as *u8) 641 pd2[po2] = 0 as u8 642 let nc2: i64 = sb_build_site(cfgpath, pd2, domain, 0) 643 if nc2 >= 1 { 644 sed_backup_index(docroot) 645 let nc3: i64 = sb_build_site(cfgpath, docroot, domain, 0) 646 if nc3 >= 1 { 647 var fb6: i64 = sd_cat(frag, 0, "PUBLISHED LIVE: index + " as *u8) 648 fb6 = sd_catn(frag, fb6, nc3) 649 fb6 = sd_cat(frag, fb6, " service pages + sitemap + robots. Previous homepage kept as .prev-index.html." as *u8) 650 o = sed_resp(out, "200 OK" as *u8, "text/plain" as *u8, frag, fb6) 651 pub_ok = 1 652 } 653 } 654 } 655 if pub_ok == 0 { 656 let fb7: i64 = sd_cat(frag, 0, "PUBLISH REFUSED: the config did not build cleanly. Live pages untouched." as *u8) 657 o = sed_resp(out, "400 Bad Request" as *u8, "text/plain" as *u8, frag, fb7) 658 } 659 } else { 660 let fb8: i64 = sd_cat(frag, 0, "no such site action" as *u8) 661 o = sed_resp(out, "404 Not Found" as *u8, "text/plain" as *u8, frag, fb8) 662 } } } } 663 } else { o = sd_emit_401_json(out) } 664 } else { o = sed_shell(domain, out) } 665 return o 666} 667 668func main(argc: i64, argv: *i64) -> i64 { 669 if argc < 9 { sys_write(2, "usage: nx_siteedit_daemon <port> <keysfile> <storefile> <realm> <domain> <configpath> <docroot> <budget>\n" as *u8, 105); return 1 } 670 let port: i64 = sd_atoi(argv[1] as *u8) 671 let keysfile: *u8 = argv[2] as *u8 672 let storefile: *u8 = argv[3] as *u8 673 let realm: *u8 = argv[4] as *u8 674 let domain: *u8 = argv[5] as *u8 675 let cfgpath: *u8 = argv[6] as *u8 676 let docroot: *u8 = argv[7] as *u8 677 let budget: i64 = sd_atoi(argv[8] as *u8) 678 let realm_n: i64 = sd_len(realm) 679 680 let oprf_seed: *u8 = sys_mmap(32) 681 let akp: *u8 = sys_mmap(32) 682 let akb: *u8 = sys_mmap(33) 683 let edp: *u8 = sys_mmap(32) 684 let edb: *u8 = sys_mmap(32) 685 if nx_uas_server_keys_load_or_init(keysfile, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK { sys_write(2, "FATAL: server-key bundle\n" as *u8, 24); return 2 } 686 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 687 if nx_auth_context_init(ctx, realm, realm_n, realm, realm_n, storefile as i64, oprf_seed, edp, edb, 900, SED_MAGIC_8192, 1, 1, 5, 1) != NX_MAUTH_OK { sys_write(2, "FATAL: context init\n" as *u8, 20); return 3 } 688 689 let addr: *u8 = sys_mmap(16) 690 if nx_http_server_addr_loopback(addr, port) != 16 { sys_write(2, "FATAL: addr_loopback != 16\n" as *u8, 27); return 4 } 691 let lv: *i64 = sys_mmap(8) as *i64 692 let lfd: i64 = nx_http_server_listen(addr, 64, lv) 693 if lfd < 0 { sys_write(2, "FATAL: listen failed (port busy or denied)\n" as *u8, 43); return 4 } 694 sys_write(1, "nx_siteedit_daemon listening loopback (admin.<domain>/site behind the SNI router)\n" as *u8, 83) 695 696 let req: *u8 = sys_mmap(SED_REQCAP) 697 let out: *u8 = sys_mmap(SED_OUTCAP) 698 var served: i64 = 0 699 while served < budget { 700 let av: *i64 = sys_mmap(8) as *i64 701 let cfd: i64 = nx_http_server_accept_one(lfd, av) 702 if cfd < 0 { served = served + 1 } 703 if cfd >= 0 { 704 let om: *i64 = sys_mmap(8) as *i64 705 let opo: *i64 = sys_mmap(8) as *i64 706 let opl: *i64 = sys_mmap(8) as *i64 707 let ocl: *i64 = sys_mmap(8) as *i64 708 let obo: *i64 = sys_mmap(8) as *i64 709 let orn: *i64 = sys_mmap(8) as *i64 710 let rrc: i64 = nx_http_server_read_request(cfd, req, SED_REQCAP, om, opo, opl, ocl, obo, orn) 711 if rrc == NXS_OK { 712 let oN: i64 = sed_handle(ctx, domain, cfgpath, docroot, req, orn[0], out) 713 nx_http_server_send_response_nokeep_close(cfd, out, oN) 714 } 715 sys_close(cfd) 716 served = served + 1 717 } 718 } 719 sys_close(lfd) 720 return 0 721}