code wiki / bin / nx_sites_daemon.nx

nx_sites_daemon.nx source

↩ module page · 714 lines · 42666 B

1// nx_sites_daemon.nx -- sovereign multi-vhost HTTPS daemon (pure Nishi, 2// silicon-up). Serves andelinwest.com + nishifamily.com (+ /wiki) by 3// Host-header / path dispatch over the PROVEN TLS 1.3 + Ed25519 path. 4// 5// Operator 2026-05-28 (full autonomy): "get andelinwest.com and 6// nishifamily.com/wiki up ... near hundred percent site uptime." 7// 8// Reuses only the proven primitives (run_ed25519 handshake + app_recv + 9// app_send) the ed25519 test daemon validated end-to-end. Avoids the 10// mv daemon's nx_wiki_route_dispatch (which returns empty on the NAS). 11// Dispatch is simple substring matching on the decrypted request -- 12// reliable for placeholders. Real wiki engine swaps in later behind 13// the same vhost routing. 14// 15// M6 anti-pretend-stub: each page SELF-LABELS as a launch placeholder 16// and shows real structure (andelinwest names its compliance blockers; 17// the wiki lists the actual shipped substrate components). 18 19import "nx_syscalls.nx" 20import "nx_csprng.nx" 21import "nx_http_server.nx" 22import "nx_tls13_server_session.nx" 23import "nx_tls13_server_session_run.nx" 24import "nx_tls13_server_session_app_data.nx" 25import "nx_sites_telemetry.nx" 26import "wiki/nx_wiki_status.nx" 27import "wiki/nx_wiki_article.nx" 28import "wiki/nx_wiki_queue.nx" 29import "_hdl_build/nx_host_router.nx" 30// Route B: SNI -> per-domain wildcard-cert selection (shared with the gate). 31import "nx_sni_cert_select.nx" 32// L5 dynamic: reverse-proxy /research -> the sovereign library daemon (:8095). 33import "nx_http_proxy.nx" 34 35const NX_SD_PORT: i64 = 8443 36const NX_SD_BUDGET: i64 = 100000000 37// Fork-per-connection concurrency cap. Bounds live handler children so a 38// scanner flood can't fork-bomb the box; the parent block-reaps one child 39// when at the cap before forking again. 40const NX_SD_MAX_CHILDREN: i64 = 64 41// Browser-trusted Let's Encrypt ECDSA P-256 chain (SAN andelinwest.com + 42// nishifamily.com) + its P-256 private key. Replaces the Ed25519 43// self-signed cert that all browsers reject (Ed25519 server certs are 44// unsupported by Chrome/Firefox/Safari -> "SSL protocol error"). 45// CERT_PATH is the CONCATENATED-DER fullchain (leaf + LE intermediates), 46// leaf first; the TLS run splits it into one CertificateEntry per cert 47// so clients can build a path to a root in their trust store. 48const NX_SD_CERT_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_fullchain.der" as *u8 49const NX_SD_PRIV_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_ecdsa_key.bin" as *u8 50// Route B (SNI-aware TLS): TWO per-domain wildcard chains loaded at startup, one chosen 51// per handshake by the ClientHello SNI. Each is a concat-DER fullchain (leaf-first) + a 52// 32-byte P-256 ECDSA private scalar -- same on-disk format as the legacy pair above. 53// nishifamily = the DEFAULT (SAN *.nishifamily.com + nishifamily.com) 54// andelinwest = SAN *.andelinwest.com + andelinwest.com 55// Operator deploys the four files at these paths; absent files degrade fail-safe (see main). 56const NX_SD_CERT_NISHI: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_nishifamily_fullchain.der" as *u8 57const NX_SD_PRIV_NISHI: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_nishifamily_ecdsa_key.bin" as *u8 58const NX_SD_CERT_ANDELIN: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_andelinwest_fullchain.der" as *u8 59const NX_SD_PRIV_ANDELIN: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_andelinwest_ecdsa_key.bin" as *u8 60// MSG_PEEK (recvfrom flag 0x2): read the cleartext ClientHello WITHOUT consuming it, so the 61// proven handshake re-reads the same bytes -> the SNI peek is invisible to nx_tls13_server_session_run. 62const NX_SD_MSG_PEEK: i64 = 2 63const NX_SD_SNI_PEEK_CAP: i64 = 8192 64// Sovereign hosting sites table (Host -> doc root). Per-request file serve = HOT content. 65const NX_SD_SITES_CONF: *u8 = "/volume1/homes/elderwesto/nishihost/sites.conf" as *u8 66// SENSE layer: append-only per-connection telemetry log (self-improving 67// system). Opened once at startup; written fire-and-forget per request. 68const NX_SD_TELEMETRY_PATH: *u8 = "/volume1/homes/elderwesto/nishihost/sites_telemetry.log" as *u8 69// Keep-alive: max requests served on one TLS session before we close it 70// (bounds a single client's hold on a forked child); response build cap. 71const NX_SD_MAX_REQ_PER_CONN: i64 = 64 72const NX_SD_OUTCAP: i64 = 16384 73// Hoisted buffer for the live /wiki/status scorecard response (rendered 74// once at startup by the real wiki engine; served verbatim). 75const NX_SD_STATUS_CAP: i64 = 65536 76// Hoisted buffers for the Gitea x Wikipedia component articles + index. 77const NX_SD_ART_CAP: i64 = 65536 78// Per-request buffer for the DYNAMIC routes (work queue) rendered in the child. 79const NX_SD_DYN_CAP: i64 = 65536 80// Favicon: answer instantly with 204 (no body) so the browser's parallel 81// favicon fetch doesn't cost a second handshake / hang. 82const RESP_FAVICON: *u8 = "HTTP/1.1 204 No Content\r\nConnection: keep-alive\r\n\r\n" as *u8 83 84const BODY_ANDELIN: *u8 = "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Andelin West Law - Coming Soon</title><style>body{font-family:Georgia,serif;max-width:680px;margin:8vh auto;padding:0 24px;color:#1a1a2e;line-height:1.6}h1{font-size:2rem}.tag{color:#555}.note{margin-top:2rem;padding:16px 20px;background:#f4f4f8;border-left:4px solid #2a4d8f;font-size:.95rem}.foot{margin-top:3rem;font-size:.8rem;color:#888}</style></head><body><h1>Andelin West Law</h1><p class=\"tag\">A modern legal practice. Site launching soon.</p><div class=\"note\"><strong>Placeholder page.</strong> Served by the sovereign Nishi substrate. The full site is pending attorney review of state-bar advertising rules, ADA accessibility conformance, and the privacy notice before any client-facing or intake content goes live.</div><p class=\"foot\">Served bits-up by the Nishi stack &mdash; TLS 1.3 + Ed25519, no third-party web server.</p></body></html>\n" as *u8 85 86const BODY_WIKI: *u8 = "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Nishi Ecosystem Wiki</title><style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;max-width:760px;margin:6vh auto;padding:0 24px;color:#161622;line-height:1.65}h1{font-size:2.1rem}h2{font-size:1.1rem;margin-top:2rem;color:#2a4d8f}ul{padding-left:1.2rem}li{margin:.35rem 0}.note{padding:14px 18px;background:#eef2fb;border-left:4px solid #2a4d8f;border-radius:4px;font-size:.95rem}code{background:#f0f0f4;padding:1px 5px;border-radius:3px;font-size:.9em}.foot{margin-top:3rem;font-size:.8rem;color:#888}</style></head><body><h1>Nishi Ecosystem Wiki</h1><p class=\"note\"><strong>Launch placeholder &mdash; the live wiki engine is coming online.</strong> What you are reading is itself served bits-up by the substrate below: no nginx, no OpenSSL, no libc.</p><p style=\"margin:1.2rem 0\"><a href=\"/wiki/status\"><strong>&#9656; Live stack scorecard &rarr;</strong></a> &nbsp; <a href=\"/wiki/components\"><strong>&#9656; Component articles (Gitea&times;Wikipedia) &rarr;</strong></a></p><h2>Shipped substrate</h2><ul><li>Self-hosting NishiLang compiler (RV64 + x86_64), bits-up from C bootstrap</li><li>TLS 1.3 server + Ed25519 signatures (RFC 8446 / 8032) &mdash; serving this page now</li><li>SHA-256/512, ChaCha20-Poly1305, AES-GCM, HKDF, Argon2id &mdash; all KAT-verified</li><li>Sovereign secrets vault (HashiCorp-class, ChaCha20-Poly1305 at rest)</li><li>Multi-vhost HTTPS daemon with DHCP-stable routing + auto-healing watchdog</li></ul><h2>In progress</h2><ul><li>Full wiki engine (search index, doc store, markdown render)</li><li>Modern auth (OPAQUE PAKE + BIP39 recovery, no cookies)</li><li>Multi-agent concurrent editing (CRDT, sovereign)</li></ul><p class=\"foot\">nishifamily.com/wiki &mdash; bits-up sovereign hosting on the west NAS.</p></body></html>\n" as *u8 87 88const BODY_FAMILY: *u8 = "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>Nishi Family</title><style>body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;max-width:680px;margin:8vh auto;padding:0 24px;color:#161622;line-height:1.65;text-align:center}h1{font-size:2.3rem}.tag{color:#555;font-size:1.05rem}a{color:#2a4d8f}.foot{margin-top:3rem;font-size:.8rem;color:#888}</style></head><body><h1>Nishi Family</h1><p class=\"tag\">The family hub &mdash; home of the Nishi sovereign ecosystem.</p><p><a href=\"/wiki\">Explore the Nishi Ecosystem Wiki &rarr;</a></p><p class=\"foot\">Served bits-up by the Nishi stack &mdash; TLS 1.3 + Ed25519, no third-party web server.</p></body></html>\n" as *u8 89 90func sd_strlen(s: *u8) -> i64 { 91 var n: i64 = 0 92 while s[n] != 0 { n = n + 1 } 93 return n 94} 95 96// substring search: returns 1 if needle (nlen) occurs in hay[0..hlen). 97func sd_contains(hay: *u8, hlen: i64, needle: *u8, nlen: i64) -> i64 { 98 if nlen <= 0 { return 0 } 99 if hlen < nlen { return 0 } 100 var i: i64 = 0 101 let last: i64 = hlen - nlen 102 while i <= last { 103 var j: i64 = 0 104 var ok: i64 = 1 105 while j < nlen { 106 if hay[i + j] != needle[j] { ok = 0; j = nlen } else { j = j + 1 } 107 } 108 if ok == 1 { return 1 } 109 i = i + 1 110 } 111 return 0 112} 113 114// Return the index just past the first CRLFCRLF (HTTP header/body split), or 115// hlen if absent. Used to locate a POST body in the decrypted request. 116func sd_body_start(hay: *u8, hlen: i64) -> i64 { 117 if hlen < 4 { return hlen } 118 var i: i64 = 0 119 let last: i64 = hlen - 4 120 while i <= last { 121 if hay[i] == (0x0D as u8) { if hay[i + 1] == (0x0A as u8) { if hay[i + 2] == (0x0D as u8) { if hay[i + 3] == (0x0A as u8) { 122 return i + 4 123 } } } } 124 i = i + 1 125 } 126 return hlen 127} 128 129// --- HTTP/1.1 response construction (keep-alive requires Content-Length 130// so the client knows where the body ends and can reuse the connection) --- 131func sd_u64_to_dec(n: i64, buf: *u8) -> i64 { 132 if n == 0 { buf[0] = 48; return 1 } 133 var d: i64 = 0 134 var x: i64 = n 135 while x > 0 { d = d + 1; x = x / 10 } 136 var i: i64 = d - 1 137 x = n 138 while i >= 0 { 139 buf[i] = (48 + (x % 10)) as u8 140 x = x / 10 141 i = i - 1 142 } 143 return d 144} 145 146func sd_append(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { 147 var i: i64 = 0 148 while i < n { dst[off + i] = src[i]; i = i + 1 } 149 return off + n 150} 151 152func sd_append_str(dst: *u8, off: i64, s: *u8) -> i64 { 153 return sd_append(dst, off, s, sd_strlen(s)) 154} 155 156// Build a complete HTTP/1.1 keep-alive response (status + headers + 157// Content-Length + body) into out; returns total length. Built ONCE per 158// page at startup into a hoisted buffer, then sent verbatim per request. 159func sd_build_resp(out: *u8, ctype: *u8, body: *u8, body_len: i64) -> i64 { 160 var w: i64 = 0 161 w = sd_append_str(out, w, "HTTP/1.1 200 OK\r\nContent-Type: " as *u8) 162 w = sd_append_str(out, w, ctype) 163 w = sd_append_str(out, w, "\r\nContent-Length: " as *u8) 164 let dbuf: *u8 = sys_mmap(24) 165 let dn: i64 = sd_u64_to_dec(body_len, dbuf) 166 w = sd_append(out, w, dbuf, dn) 167 w = sd_append_str(out, w, "\r\nConnection: keep-alive\r\nX-Served-By: nishi-substrate\r\n\r\n" as *u8) 168 w = sd_append(out, w, body, body_len) 169 return w 170} 171 172// Load a concatenated-DER fullchain; returns the buffer (or 0 on failure) and writes its 173// length into len_box. Validates a sane minimum size (a real LE leaf is ~900+ bytes). 174func sd_load_chain(path: *u8, len_box: *i64) -> *u8 { 175 len_box[0] = 0 176 let p: *u8 = sys_read_file(path, len_box) 177 if (p as i64) == 0 { return 0 as *u8 } 178 if len_box[0] < 100 { return 0 as *u8 } 179 return p 180} 181 182// Load a 32-byte P-256 ECDSA private scalar; returns the buffer or 0 if absent / wrong size. 183func sd_load_key(path: *u8) -> *u8 { 184 let lb: *i64 = (sys_mmap(8)) as *i64 185 lb[0] = 0 186 let p: *u8 = sys_read_file(path, lb) 187 if (p as i64) == 0 { return 0 as *u8 } 188 if lb[0] != 32 { return 0 as *u8 } 189 return p 190} 191 192// ---- DATA-DRIVEN SNI cert table (R1b 2026-07-15) ------------------------------------------------- 193// Every domain we serve HTTPS for is one manifest row (suffix + fullchain-der + ecdsa-key), so adding 194// a domain = drop its le_<domain>_* + a line, NEVER an edge recompile. Row 0 = the DEFAULT cert. 195const NX_SD_CERT_MANIFEST: *u8 = "/volume1/homes/elderwesto/nishihost/certs/sni_certs.manifest" as *u8 196const NX_SD_CERT_MAX: i64 = 32 197 198// Parse a positive decimal from a NUL-terminated string (stops at first non-digit). Used for argv port. 199func sd_atoi(s: *u8) -> i64 { 200 var v: i64 = 0 201 var i: i64 = 0 202 var go: i64 = 1 203 while go == 1 { 204 let c: i64 = s[i] as i64 205 if c < 48 { go = 0 } 206 else { if c > 57 { go = 0 } 207 else { v = v * 10 + (c - 48); i = i + 1 } } 208 } 209 return v 210} 211 212// Copy one whitespace-delimited field from buf[pos..end) into out (NUL-terminated). Skips leading 213// spaces/tabs. Returns the position just past the field, or -1 if the line has no field (blank/'#'/EOL). 214func sd_field(buf: *u8, pos: i64, end: i64, out: *u8, out_cap: i64) -> i64 { 215 var p: i64 = pos 216 var skip: i64 = 1 217 while skip == 1 { 218 if p >= end { skip = 0 } 219 else { 220 let c: i64 = buf[p] as i64 221 if c == 32 { p = p + 1 } 222 else { if c == 9 { p = p + 1 } else { skip = 0 } } 223 } 224 } 225 if p >= end { return 0 - 1 } 226 let c0: i64 = buf[p] as i64 227 if c0 == 10 { return 0 - 1 } 228 if c0 == 13 { return 0 - 1 } 229 if c0 == 35 { return 0 - 1 } 230 var o: i64 = 0 231 var cp: i64 = 1 232 while cp == 1 { 233 if p >= end { cp = 0 } 234 else { 235 let c: i64 = buf[p] as i64 236 if c == 32 { cp = 0 } 237 else { if c == 9 { cp = 0 } 238 else { if c == 10 { cp = 0 } 239 else { if c == 13 { cp = 0 } 240 else { 241 if o < out_cap - 1 { out[o] = buf[p] as u8; o = o + 1 } 242 p = p + 1 243 } } } } 244 } 245 } 246 out[o] = 0 as u8 247 return p 248} 249 250// Advance past the current line (to the byte after the next '\n', or to end). 251func sd_next_line(buf: *u8, pos: i64, end: i64) -> i64 { 252 var p: i64 = pos 253 var go: i64 = 1 254 while go == 1 { 255 if p >= end { go = 0 } 256 else { if (buf[p] as i64) == 10 { p = p + 1; go = 0 } else { p = p + 1 } } 257 } 258 return p 259} 260 261// Parse the manifest into parallel arrays; loads each row's cert+key. Rows whose cert/key fail to load 262// are skipped (fail-safe). Returns the number of rows loaded (0 => caller uses the legacy fallback). 263func sd_load_cert_table(man: *u8, man_n: i64, 264 suf_ptrs: *i64, suf_lens: *i64, 265 cert_ptrs: *i64, cert_lens: *i64, key_ptrs: *i64, 266 max: i64) -> i64 { 267 var count: i64 = 0 268 var pos: i64 = 0 269 while pos < man_n { 270 if count >= max { pos = man_n } 271 else { 272 let sufb: *u8 = sys_mmap(256) 273 let p1: i64 = sd_field(man, pos, man_n, sufb, 256) 274 if p1 < 0 { pos = sd_next_line(man, pos, man_n) } 275 else { 276 let certp: *u8 = sys_mmap(512) 277 let p2: i64 = sd_field(man, p1, man_n, certp, 512) 278 if p2 < 0 { pos = sd_next_line(man, pos, man_n) } 279 else { 280 let keyp: *u8 = sys_mmap(512) 281 let p3: i64 = sd_field(man, p2, man_n, keyp, 512) 282 if p3 < 0 { pos = sd_next_line(man, pos, man_n) } 283 else { 284 let clb: *i64 = (sys_mmap(8)) as *i64 285 clb[0] = 0 286 let cbuf: *u8 = sd_load_chain(certp, clb) 287 let kbuf: *u8 = sd_load_key(keyp) 288 if (cbuf as i64) == 0 { pos = sd_next_line(man, pos, man_n) } 289 else { if (kbuf as i64) == 0 { pos = sd_next_line(man, pos, man_n) } 290 else { 291 var sl: i64 = 0 292 while sufb[sl] != (0 as u8) { sl = sl + 1 } 293 suf_ptrs[count] = sufb as i64 294 suf_lens[count] = sl 295 cert_ptrs[count] = cbuf as i64 296 cert_lens[count] = clb[0] 297 key_ptrs[count] = kbuf as i64 298 count = count + 1 299 pos = sd_next_line(man, pos, man_n) 300 } } 301 } 302 } 303 } 304 } 305 } 306 return count 307} 308 309func main(argc: i64, argv: *i64) -> i64 { 310 // ---- Route B: load BOTH per-domain wildcard chains at startup ---- 311 // The NISHI (nishifamily) pair is the DEFAULT served for any host that does not match a 312 // known domain (and whenever the SNI is absent/garbled). Fail-safe: if the new 313 // nishifamily files are not deployed yet, fall back to the LEGACY single chain so the 314 // existing single-domain handshake never regresses (rule 20 fail-fast only if NOTHING loads). 315 let cert_len_box: *i64 = (sys_mmap(8)) as *i64 316 var cert_nishi: *u8 = sd_load_chain(NX_SD_CERT_NISHI, cert_len_box) 317 var ecdsa_nishi: *u8 = sd_load_key(NX_SD_PRIV_NISHI) 318 if (cert_nishi as i64) == 0 { cert_nishi = sd_load_chain(NX_SD_CERT_PATH, cert_len_box) } 319 if (ecdsa_nishi as i64) == 0 { ecdsa_nishi = sd_load_key(NX_SD_PRIV_PATH) } 320 if (cert_nishi as i64) == 0 { return 2 } 321 if (ecdsa_nishi as i64) == 0 { return 3 } 322 let cert_nishi_len: i64 = cert_len_box[0] 323 324 // The ANDELIN (andelinwest) pair. Fail-safe: if its files are absent, serve the default 325 // pair instead (a valid TLS handshake still completes -- the cert just won't cover an 326 // andelinwest host -- rather than crash or drop the connection). 327 let andelin_len_box: *i64 = (sys_mmap(8)) as *i64 328 var cert_andelin: *u8 = sd_load_chain(NX_SD_CERT_ANDELIN, andelin_len_box) 329 var ecdsa_andelin: *u8 = sd_load_key(NX_SD_PRIV_ANDELIN) 330 var cert_andelin_len: i64 = andelin_len_box[0] 331 if (cert_andelin as i64) == 0 { cert_andelin = cert_nishi; cert_andelin_len = cert_nishi_len; ecdsa_andelin = ecdsa_nishi } 332 if (ecdsa_andelin as i64) == 0 { cert_andelin = cert_nishi; cert_andelin_len = cert_nishi_len; ecdsa_andelin = ecdsa_nishi } 333 334 // ---- DATA-DRIVEN cert table: load the manifest (row 0 = default). Fall back to the legacy 335 // nishi+andelin pair if the manifest is absent/empty so a fresh box never regresses (rule 20). ---- 336 let cert_suf_ptrs: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 337 let cert_suf_lens: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 338 let cert_buf_ptrs: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 339 let cert_buf_lens: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 340 let cert_key_ptrs: *i64 = (sys_mmap(NX_SD_CERT_MAX * 8)) as *i64 341 let man_box: *i64 = (sys_mmap(8)) as *i64 342 man_box[0] = 0 343 let man_buf: *u8 = sys_read_file(NX_SD_CERT_MANIFEST, man_box) 344 var cert_count: i64 = 0 345 if (man_buf as i64) != 0 { 346 cert_count = sd_load_cert_table(man_buf, man_box[0], cert_suf_ptrs, cert_suf_lens, cert_buf_ptrs, cert_buf_lens, cert_key_ptrs, NX_SD_CERT_MAX) 347 } 348 if cert_count == 0 { 349 cert_suf_ptrs[0] = ("nishifamily.com" as *u8) as i64 350 cert_suf_lens[0] = 15 351 cert_buf_ptrs[0] = cert_nishi as i64 352 cert_buf_lens[0] = cert_nishi_len 353 cert_key_ptrs[0] = ecdsa_nishi as i64 354 cert_suf_ptrs[1] = ("andelinwest.com" as *u8) as i64 355 cert_suf_lens[1] = 15 356 cert_buf_ptrs[1] = cert_andelin as i64 357 cert_buf_lens[1] = cert_andelin_len 358 cert_key_ptrs[1] = ecdsa_andelin as i64 359 cert_count = 2 360 } 361 sys_write(1, "nishi sites daemon: SNI cert table rows=" as *u8, 40) 362 let ccb: *u8 = sys_mmap(24) 363 let ccn: i64 = sd_u64_to_dec(cert_count, ccb) 364 sys_write(1, ccb, ccn) 365 sys_write(1, "\n" as *u8, 1) 366 367 // Load the sovereign hosting sites table once at startup. Graceful: if absent, 368 // sites_cfg_n stays 0 and every host uses the legacy baked routing (no regression). 369 let sd_cfg_box: *i64 = (sys_mmap(8)) as *i64 370 sd_cfg_box[0] = 0 371 let sites_cfg: *u8 = sys_read_file(NX_SD_SITES_CONF, sd_cfg_box) 372 var sites_cfg_n: i64 = 0 373 if (sites_cfg as i64) != 0 { sites_cfg_n = sd_cfg_box[0] } 374 375 // Bind port: argv[1] overrides the default (lets a test instance run on :8543 beside the live :8443). 376 var port: i64 = NX_SD_PORT 377 if argc > 1 { let ap: i64 = sd_atoi(argv[1] as *u8); if ap > 0 { port = ap } } 378 let addr_buf: *u8 = sys_mmap(16) 379 nx_http_server_addr_any(addr_buf, port) 380 let lv: *i64 = (sys_mmap(8)) as *i64 381 let lfd: i64 = nx_http_server_listen(addr_buf, 16, lv) 382 if lfd < 0 { return 4 } 383 sys_write(1, "nishi sites daemon: data-driven SNI cert table + sites.conf docroot, listening\n" as *u8, 78) 384 385 // Build full HTTP/1.1 keep-alive responses ONCE into hoisted buffers 386 // (constant memory; sent verbatim for every matching request). 387 let ctype_html: *u8 = "text/html; charset=utf-8" as *u8 388 let nb_andelin: i64 = sd_strlen(BODY_ANDELIN) 389 let nb_wiki: i64 = sd_strlen(BODY_WIKI) 390 let nb_family: i64 = sd_strlen(BODY_FAMILY) 391 let resp_andelin: *u8 = sys_mmap(nb_andelin + 256) 392 let resp_wiki: *u8 = sys_mmap(nb_wiki + 256) 393 let resp_family: *u8 = sys_mmap(nb_family + 256) 394 let n_andelin: i64 = sd_build_resp(resp_andelin, ctype_html, BODY_ANDELIN, nb_andelin) 395 let n_wiki: i64 = sd_build_resp(resp_wiki, ctype_html, BODY_WIKI, nb_wiki) 396 let n_family: i64 = sd_build_resp(resp_family, ctype_html, BODY_FAMILY, nb_family) 397 let n_favicon: i64 = sd_strlen(RESP_FAVICON) 398 399 // /wiki/status -- the LIVE sovereign scorecard, rendered ONCE at startup 400 // by the real wiki engine (nx_wiki_status_handle) into a hoisted buffer 401 // (constant memory; served verbatim per request). Self-contained: needs 402 // NO doc-store, so it works on the NAS where the content store is empty 403 // (the reason the full dispatcher returned empty here before). Graceful: 404 // on render failure, falls back to the static wiki placeholder. 405 var resp_wstatus: *u8 = sys_mmap(NX_SD_STATUS_CAP) 406 let wstatus_box: *i64 = (sys_mmap(8)) as *i64 407 wstatus_box[0] = 0 408 let wstatus_rc: i64 = nx_wiki_status_handle(resp_wstatus, NX_SD_STATUS_CAP, wstatus_box) 409 var n_wstatus: i64 = wstatus_box[0] 410 if wstatus_rc != 0 { resp_wstatus = resp_wiki; n_wstatus = n_wiki } 411 if n_wstatus <= 0 { resp_wstatus = resp_wiki; n_wstatus = n_wiki } 412 413 // Component articles (Gitea x Wikipedia) + index, each rendered ONCE at 414 // startup by the wiki engine into a hoisted buffer (constant memory; 415 // served verbatim). Self-contained -- no doc-store needed. Graceful 416 // fallback to the wiki placeholder on any render error. 417 var resp_art_crawler: *u8 = sys_mmap(NX_SD_ART_CAP) 418 let acr_box: *i64 = (sys_mmap(8)) as *i64 419 acr_box[0] = 0 420 let acr_rc: i64 = nx_wiki_article_handle("crawler" as *u8, 7, resp_art_crawler, NX_SD_ART_CAP, acr_box) 421 var n_art_crawler: i64 = acr_box[0] 422 if acr_rc != 0 { resp_art_crawler = resp_wiki; n_art_crawler = n_wiki } 423 if n_art_crawler <= 0 { resp_art_crawler = resp_wiki; n_art_crawler = n_wiki } 424 425 var resp_art_x25519: *u8 = sys_mmap(NX_SD_ART_CAP) 426 let axx_box: *i64 = (sys_mmap(8)) as *i64 427 axx_box[0] = 0 428 let axx_rc: i64 = nx_wiki_article_handle("x25519" as *u8, 6, resp_art_x25519, NX_SD_ART_CAP, axx_box) 429 var n_art_x25519: i64 = axx_box[0] 430 if axx_rc != 0 { resp_art_x25519 = resp_wiki; n_art_x25519 = n_wiki } 431 if n_art_x25519 <= 0 { resp_art_x25519 = resp_wiki; n_art_x25519 = n_wiki } 432 433 var resp_art_ranking: *u8 = sys_mmap(NX_SD_ART_CAP) 434 let ark_box: *i64 = (sys_mmap(8)) as *i64 435 ark_box[0] = 0 436 let ark_rc: i64 = nx_wiki_article_handle("ranking-math" as *u8, 12, resp_art_ranking, NX_SD_ART_CAP, ark_box) 437 var n_art_ranking: i64 = ark_box[0] 438 if ark_rc != 0 { resp_art_ranking = resp_wiki; n_art_ranking = n_wiki } 439 if n_art_ranking <= 0 { resp_art_ranking = resp_wiki; n_art_ranking = n_wiki } 440 441 var resp_art_wiki: *u8 = sys_mmap(NX_SD_ART_CAP) 442 let awe_box: *i64 = (sys_mmap(8)) as *i64 443 awe_box[0] = 0 444 let awe_rc: i64 = nx_wiki_article_handle("wiki-engine" as *u8, 11, resp_art_wiki, NX_SD_ART_CAP, awe_box) 445 var n_art_wiki: i64 = awe_box[0] 446 if awe_rc != 0 { resp_art_wiki = resp_wiki; n_art_wiki = n_wiki } 447 if n_art_wiki <= 0 { resp_art_wiki = resp_wiki; n_art_wiki = n_wiki } 448 449 var resp_components: *u8 = sys_mmap(NX_SD_ART_CAP) 450 let aci_box: *i64 = (sys_mmap(8)) as *i64 451 aci_box[0] = 0 452 let aci_rc: i64 = nx_wiki_components_handle(resp_components, NX_SD_ART_CAP, aci_box) 453 var n_components: i64 = aci_box[0] 454 if aci_rc != 0 { resp_components = resp_wiki; n_components = n_wiki } 455 if n_components <= 0 { resp_components = resp_wiki; n_components = n_wiki } 456 457 // /wiki/projects is discovered live by the standalone nx_pm_discover tool 458 // (the HTTP client hangs the daemon's compile graph). The daemon will 459 // serve the tool's emitted page from a file; until that file-serve is 460 // wired, /wiki/projects falls back to the wiki placeholder. 461 var resp_projects: *u8 = resp_wiki 462 var n_projects: i64 = n_wiki 463 464 // CONSTANT MEMORY: every per-connection buffer is allocated ONCE 465 // here and reused for every connection. NishiLang is GC-free + has 466 // no hidden allocator -- so allocating inside the accept loop would 467 // be a programmer-side leak (the language won't reclaim it). Hoist 468 // everything; the daemon's RSS is then fixed for its whole lifetime 469 // and cannot grow with request count. 470 let server_random: *u8 = sys_mmap(32) 471 let server_x25519_priv: *u8 = sys_mmap(32) 472 let rec_buf: *u8 = sys_mmap(20480) 473 let plain: *u8 = sys_mmap(20480) 474 let out_rec: *u8 = sys_mmap(NX_SD_OUTCAP) 475 // Route B: hoisted SNI-peek buffers (constant memory; children get a COW copy on write). 476 let sni_peek: *u8 = sys_mmap(NX_SD_SNI_PEEK_CAP) 477 let sni_host: *u8 = sys_mmap(256) 478 // Dynamic-route render buffer (work queue / request 303). Hoisted in the 479 // parent; each forked child gets its own COW copy. 480 let dyn_buf: *u8 = sys_mmap(NX_SD_DYN_CAP) 481 // SENSE layer (self-improving system): peer-addr buffer for 482 // LAN/internet classification, a hoisted telemetry line buffer, and 483 // one append-mode log fd held open for the daemon's whole lifetime 484 // (no per-connection open/close in the hot path). A failed open 485 // (tel_fd < 0) simply disables telemetry -- it never blocks serving. 486 let sock_addr: *u8 = sys_mmap(64) 487 let sock_len: *i64 = (sys_mmap(8)) as *i64 488 let tel_buf: *u8 = sys_mmap(256) 489 let tel_fd: i64 = sys_openat_append(NX_SD_TELEMETRY_PATH, 420) 490 491 // FORK-PER-CONNECTION: each accepted connection is handled in its own 492 // child, which exits when done. The OS reclaims everything the child 493 // allocated -- so the P-256 field ops' per-operation allocations cannot 494 // accumulate in a long-lived process (the prior OOM -> supervisor- 495 // restart cycle). Bonus: connections are served CONCURRENTLY rather 496 // than one-at-a-time. csprng is fork-safe (getrandom / urandom per 497 // call), so children never share key material. Concurrency is bounded 498 // by NX_SD_MAX_CHILDREN; the parent reaps exited children every accept. 499 // Listen-socket timeout so accept() returns periodically when idle -- 500 // lets the reap loop clear finished children even with no new traffic 501 // (otherwise the parent blocks in accept() while children zombie). 502 sys_set_socket_timeout(lfd, 5) 503 let reap_status: *i64 = (sys_mmap(8)) as *i64 504 var served: i64 = 0 505 var live: i64 = 0 506 while served < NX_SD_BUDGET { 507 // Reap exited children (non-blocking) every iteration -- including 508 // idle accept-timeouts -- so zombies never accumulate. 509 while sys_wait4(0 - 1, reap_status, 1) > 0 { live = live - 1 } 510 511 sock_len[0] = 16 512 let cfd: i64 = sys_accept_with_addr(lfd, sock_addr, sock_len) 513 if cfd < 0 { continue } // accept timeout (idle) or transient error 514 515 // At the cap: block-reap one child before forking another (throttle). 516 if live >= NX_SD_MAX_CHILDREN { 517 if sys_wait4(0 - 1, reap_status, 0) > 0 { live = live - 1 } 518 } 519 520 let pid: i64 = sys_fork() 521 if pid == 0 { 522 // ---- CHILD: handle exactly this connection, then exit ---- 523 sys_close(lfd) 524 // SENSE: classify the access path (observational only). 525 let acc_lan: i64 = nx_access_is_lan(sock_addr) 526 // PUBLIC-EXPOSURE HARDENING: 10s cap on every blocking read/write 527 // so a silent scanner can't pin this child forever. 528 sys_set_socket_timeout(cfd, 10) 529 // TCP_NODELAY (IPPROTO_TCP=6, TCP_NODELAY=1): disable Nagle on the 530 // connection. MEASURED: the PK crypto is only ~9ms/handshake yet 531 // curl saw ~150ms -- the ~140ms gap is Nagle holding our multi- 532 // record handshake flight until the client's delayed ACK (~40ms 533 // each). Without this, every page pays that stall. Per-socket, 534 // not inherited from the listener, so it must be set on cfd. 535 let nodelay: *u8 = sys_mmap(4) 536 nodelay[0] = 1 as u8; nodelay[1] = 0 as u8; nodelay[2] = 0 as u8; nodelay[3] = 0 as u8 537 sys_setsockopt(cfd, 6, 1, nodelay, 4) 538 nx_csprng_fill(server_random, 32) 539 nx_csprng_fill(server_x25519_priv, 32) 540 // ---- Route B: SNI-aware cert selection ---- 541 // Peek the cleartext ClientHello WITHOUT consuming it (MSG_PEEK), pick the 542 // per-domain wildcard pair by the SNI, then run the UNCHANGED handshake serving 543 // that pair. The peek never consumes bytes, so nx_tls13_server_session_run reads 544 // the exact same ClientHello it always did -> zero change to the proven handshake. 545 // Any peek failure / absent / unknown SNI -> the default (nishifamily) pair. 546 let pk_n: i64 = sys_recvfrom(cfd, sni_peek, NX_SD_SNI_PEEK_CAP, NX_SD_MSG_PEEK, 0 as *u8, 0 as *i64) 547 // DATA-DRIVEN: pick the cert-table row from the SNI (row 0 = default; unknown/absent -> 0). 548 var cert_idx: i64 = 0 549 if pk_n > 0 { cert_idx = scs_pick_from_clienthello_table(sni_peek, pk_n, sni_host, 256, cert_suf_ptrs, cert_suf_lens, cert_count) } 550 if cert_idx < 0 { cert_idx = 0 } 551 if cert_idx >= cert_count { cert_idx = 0 } 552 var sel_cert: *u8 = cert_buf_ptrs[cert_idx] as *u8 553 var sel_cert_len: i64 = cert_buf_lens[cert_idx] 554 var sel_key: *u8 = cert_key_ptrs[cert_idx] as *u8 555 let t_hs0: i64 = sys_now_us() 556 let hs_rc: i64 = nx_tls13_server_session_run( 557 cfd, server_random, server_x25519_priv, 558 sel_cert, sel_cert_len, sel_key) 559 let hs_us: i64 = sys_now_us() - t_hs0 560 var vhost_id: i64 = 0 561 var served_bytes: i64 = 0 562 if hs_rc > 0 { 563 let s: *Tls13ServerSession = hs_rc as *Tls13ServerSession 564 // KEEP-ALIVE: serve multiple requests on this one TLS session 565 // (one handshake per page, not one per file). Loop until the 566 // client closes / idle-times-out (cfd has a 10s timeout) or we 567 // hit the per-connection request cap. 568 var keep: i64 = 1 569 var nreq: i64 = 0 570 while keep == 1 { 571 if nreq >= NX_SD_MAX_REQ_PER_CONN { 572 keep = 0 573 } else { 574 let rec_n: i64 = sys_read(cfd, rec_buf, 20480) 575 if rec_n <= 0 { 576 keep = 0 577 } else { 578 let plain_n: i64 = nx_tls13_server_session_app_recv(s, rec_buf, rec_n, plain, 20480) 579 if plain_n <= 0 { 580 keep = 0 581 } else { 582 // SAME EXPERIENCE LAN+INTERNET: dispatch by 583 // Host/path ONLY (never by acc_lan). Favicon 584 // first -- a favicon req to andelinwest.com also 585 // contains "andelinwest". 586 var resp: *u8 = resp_family 587 var resp_n: i64 = n_family 588 let is_favicon: i64 = sd_contains(plain, plain_n, "favicon" as *u8, 7) 589 if is_favicon == 1 { 590 resp = RESP_FAVICON; resp_n = n_favicon; vhost_id = 9 591 } else { 592 // DYNAMIC routes (rendered per-request in this child): the work 593 // queue read + the POST request append. These read/write the 594 // sovereign journal at request time, so they cannot be hoisted. 595 var dyn_done: i64 = 0 596 let is_postreq: i64 = sd_contains(plain, plain_n, "POST /wiki/request" as *u8, 18) 597 let is_queue: i64 = sd_contains(plain, plain_n, "/wiki/queue" as *u8, 11) 598 if is_postreq == 1 { 599 let bstart: i64 = sd_body_start(plain, plain_n) 600 let dnq: *i64 = (sys_mmap(8)) as *i64 601 dnq[0] = 0 602 let prc: i64 = nx_wiki_request_handle((plain as i64 + bstart) as *u8, plain_n - bstart, dyn_buf, NX_SD_DYN_CAP, dnq) 603 if prc == 0 { resp = dyn_buf; resp_n = dnq[0]; vhost_id = 10; dyn_done = 1 } 604 } 605 if dyn_done == 0 { if is_queue == 1 { 606 let dnq2: *i64 = (sys_mmap(8)) as *i64 607 dnq2[0] = 0 608 let qrc: i64 = nx_wiki_queue_handle(dyn_buf, NX_SD_DYN_CAP, dnq2) 609 if qrc == 0 { resp = dyn_buf; resp_n = dnq2[0]; vhost_id = 11; dyn_done = 1 } 610 } } 611 // SOVEREIGN LIBRARY: /research(/*) -> reverse-proxy to the library 612 // daemon (:8095, 16K-taxon full-text). Dynamic backend, not a docroot. 613 // Additive + fail-safe: any non-/research path falls through unchanged. 614 if dyn_done == 0 { 615 let rpath: *u8 = sys_mmap(2048) 616 hr_req_path(plain, plain_n, rpath, 2048) 617 let rpre: *u8 = "/research" as *u8 618 var is_research: i64 = 1 619 var ri: i64 = 0 620 while ri < 9 { if rpath[ri] != rpre[ri] { is_research = 0; ri = 9 } else { ri = ri + 1 } } 621 if is_research == 1 { let rc9: i64 = rpath[9] as i64; if rc9 == 0 { is_research = 1 } else { if rc9 == 47 { is_research = 1 } else { is_research = 0 } } } 622 if is_research == 1 { 623 resp = dyn_buf 624 resp_n = nx_http_proxy_relay("127.0.0.1" as *u8, 9, 8095, "/research" as *u8, 9, plain, plain_n, dyn_buf, NX_SD_DYN_CAP) 625 vhost_id = 25 626 dyn_done = 1 627 } 628 } 629 // SOVEREIGN HOSTING: any host in sites.conf is served file-based 630 // (hot content -- push a file, live next request). Hosts NOT in the 631 // table fall through to the legacy baked routing below (the wiki). 632 if dyn_done == 0 { 633 if hr_known_host(sites_cfg, sites_cfg_n, plain, plain_n) == 1 { 634 resp = dyn_buf 635 resp_n = hr_serve(sites_cfg, sites_cfg_n, plain, plain_n, dyn_buf, NX_SD_DYN_CAP) 636 vhost_id = 20 637 dyn_done = 1 638 } 639 } 640 // HOT wiki dashboard: /wiki/roadmap served from a FILE per-request (living -- 641 // re-push the file -> live next request). Checked before the baked /wiki route so it wins. 642 if dyn_done == 0 { 643 if sd_contains(plain, plain_n, "/wiki/roadmap" as *u8, 13) == 1 { 644 let rlb: *i64 = (sys_mmap(8)) as *i64; rlb[0] = 0 645 let rbuf: *u8 = sys_read_file("/volume1/homes/elderwesto/nishihost/wiki/roadmap.html" as *u8, rlb) 646 if (rbuf as i64) != 0 { resp = dyn_buf; resp_n = sd_build_resp(dyn_buf, ctype_html, rbuf, rlb[0]); vhost_id = 30; dyn_done = 1 } 647 } 648 } 649 // HOT /maps: nishifamily.com/maps -- the sovereign map surface (nx_geo_mapsvg 650 // renders the SVG world map; no third-party map lib). Served from a FILE per 651 // request (re-render -> live next request), same pattern as /wiki/roadmap. 652 if dyn_done == 0 { 653 if sd_contains(plain, plain_n, "/maps" as *u8, 5) == 1 { 654 let mlb: *i64 = (sys_mmap(8)) as *i64; mlb[0] = 0 655 let mbuf: *u8 = sys_read_file("/volume1/homes/elderwesto/nishihost/sites/nishifamily/maps/index.html" as *u8, mlb) 656 if (mbuf as i64) != 0 { resp = dyn_buf; resp_n = sd_build_resp(dyn_buf, ctype_html, mbuf, mlb[0]); vhost_id = 31; dyn_done = 1 } 657 } 658 } 659 if dyn_done == 0 { 660 let is_andelin: i64 = sd_contains(plain, plain_n, "andelinwest" as *u8, 11) 661 let is_wiki: i64 = sd_contains(plain, plain_n, "/wiki" as *u8, 5) 662 let is_wstatus: i64 = sd_contains(plain, plain_n, "/wiki/status" as *u8, 12) 663 let is_cidx: i64 = sd_contains(plain, plain_n, "/wiki/components" as *u8, 16) 664 let is_proj: i64 = sd_contains(plain, plain_n, "/wiki/projects" as *u8, 14) 665 let is_cr: i64 = sd_contains(plain, plain_n, "/wiki/component/crawler" as *u8, 23) 666 let is_xx: i64 = sd_contains(plain, plain_n, "/wiki/component/x25519" as *u8, 22) 667 let is_rk: i64 = sd_contains(plain, plain_n, "/wiki/component/ranking-math" as *u8, 28) 668 let is_we: i64 = sd_contains(plain, plain_n, "/wiki/component/wiki-engine" as *u8, 27) 669 // Same-experience LAN+internet: dispatch by Host/path only. The 670 // sequential ifs run least-specific first so the most-specific 671 // path (a component slug) wins the overwrite. 672 if is_andelin == 1 { 673 resp = resp_andelin; resp_n = n_andelin; vhost_id = 1 674 } else { 675 if is_wiki == 1 { resp = resp_wiki; resp_n = n_wiki; vhost_id = 2 } 676 if is_wstatus == 1 { resp = resp_wstatus; resp_n = n_wstatus; vhost_id = 3 } 677 if is_cidx == 1 { resp = resp_components; resp_n = n_components; vhost_id = 8 } 678 if is_proj == 1 { resp = resp_projects; resp_n = n_projects; vhost_id = 12 } 679 if is_cr == 1 { resp = resp_art_crawler; resp_n = n_art_crawler; vhost_id = 4 } 680 if is_xx == 1 { resp = resp_art_x25519; resp_n = n_art_x25519; vhost_id = 5 } 681 if is_rk == 1 { resp = resp_art_ranking; resp_n = n_art_ranking; vhost_id = 6 } 682 if is_we == 1 { resp = resp_art_wiki; resp_n = n_art_wiki; vhost_id = 7 } 683 } 684 } 685 } 686 let out_n: i64 = nx_tls13_server_session_app_send(s, resp, resp_n, out_rec, NX_SD_OUTCAP) 687 if out_n > 0 { 688 sys_write(cfd, out_rec, out_n) 689 served_bytes = served_bytes + resp_n 690 nreq = nreq + 1 691 } else { 692 keep = 0 693 } 694 } 695 } 696 } 697 } 698 } 699 sys_close(cfd) 700 // SENSE: append one additive telemetry record (O_APPEND -> atomic 701 // across concurrent children; fire-and-forget). 702 nx_telemetry_emit(tel_fd, tel_buf, sys_now_us(), acc_lan, vhost_id, 703 hs_us, served_bytes, hs_rc) 704 sys_exit(0) 705 } 706 707 // ---- PARENT: drop our copy of the conn fd; children reaped at loop top ---- 708 sys_close(cfd) 709 if pid > 0 { live = live + 1 } 710 served = served + 1 711 } 712 sys_close(lfd) 713 return 0 714}