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 — " 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>🛠 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> · <code>header|Brand|Button text|#link</code> · <code>hero|Big headline|Subtext|Button|#link</code> · <code>card|Service name|One-line description</code> (each card becomes a page) · <code>step|What happens</code> · <code>sig|A trust point</code> · <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 →</a>'}if(x.ok&&p=='/site/publish'){PL.innerHTML='<a href=\"/\" target=_blank>See it live →</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>🛠 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 →</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, "\">↶ Revision history</a> · 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, "\">← Editor</a> <a href=\"/preview/\" target=_blank>Preview</a> <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 — 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, "\">← 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}