nx_wiki_https_daemon.nx source
↩ module page · 288 lines · 12217 B
1// nx_wiki_https_daemon.nx -- V-HOST-2: sovereign HTTPS wiki daemon.
2//
3// Composes existing TLS 1.3 handshake (sovereign Ed25519 OR ECDSA path) +
4// wiki HTTP router into a single daemon serving sovereign HTTPS for
5// the wiki at andelinwest.com / nishifamily.com / etc.
6//
7// FINAL substrate piece per NISHI_ANDELINWEST_DEPLOY_PLAYBOOK. After
8// this ships, the deploy is operator-side ops only:
9// 1. nx_cert_gen (V-HOST-4 SHIPPED) -> Ed25519 cert
10// 2. Porkbun A records (operator panel; 5 min)
11// 3. Router port-forward 443 -> substrate host
12// 4. Boot this daemon
13// 5. https://andelinwest.com/ LIVE
14//
15// COMPOSES (per "avoid duplicate primitives"):
16// nx_http_server (TCP listen + accept; existing primitive)
17// nx_tls13_server_session_run_ed25519 (V-HOST-2+1-2)
18// OR nx_tls13_server_session_run (existing ECDSA path)
19// nx_tls13_server_session_app_recv (decrypt request bytes)
20// nx_tls13_server_session_app_send (encrypt response bytes)
21// wiki/nx_wiki_routes.nx_wiki_route_dispatch (existing wiki router)
22// nx_csprng (server_random + x25519 priv)
23// nx_http_header_find (req parse; existing helper)
24//
25// V-HOST-2 SCOPE:
26// - Single-vhost daemon (one cert + one wiki state); per-vhost
27// dispatch is V-HOST-2.5 refactor when N > 1 sites need this daemon
28// - Defaults to Ed25519 cert path (V-HOST-2+1 modern)
29// - Reads cert + priv from operator-config paths
30// - Accept loop: per connection runs TLS handshake + dispatches one
31// HTTP request to wiki router + sends encrypted response
32// - Per Cardinal 14 graceful degradation: per-conn errors don't
33// kill the daemon
34//
35// V-HOST-2 NON-SCOPE (queued for V-HOST-2.5 + V-HOST-5):
36// - Multi-vhost dispatch (one daemon serving N domains)
37// - Keep-alive (single request per TLS session V1)
38// - Session resumption (PSK / 0-RTT)
39// - Unified per-vhost handler dispatch (currently wiki-only)
40//
41// Status: V-HOST-2. 2026-05-27.
42
43import "nx_syscalls.nx"
44import "nx_csprng.nx"
45import "nx_http_server.nx"
46import "nx_http_io.nx"
47import "nx_tls13_server_session.nx"
48import "nx_tls13_server_session_run.nx"
49import "nx_tls13_server_session_app_data.nx"
50// Wiki state + router (composed at startup; mirrors nx_wiki_main step 4-6f)
51import "hub/nx_admin_login_flow.nx"
52import "hub/nx_search_handler_flow.nx"
53import "nx_kv_store.nx"
54import "nx_hash_facade.nx"
55import "nx_search_inverted.nx"
56import "wiki/nx_wiki_routes.nx"
57import "wiki/nx_wiki_index_builder.nx"
58import "wiki/nx_wiki_archive_router.nx"
59import "wiki/nx_artifact_store.nx"
60
61// ===== Sealed verdict surface (codes 3800-3819) =================================================
62const NX_WHD_OK: i64 = 0
63const NX_WHD_BAD_INPUT: i64 = 3800
64const NX_WHD_CERT_LOAD_FAILED: i64 = 3801
65const NX_WHD_BIND_FAILED: i64 = 3802
66const NX_WHD_HS_FAILED: i64 = 3803
67const NX_WHD_RECV_FAILED: i64 = 3804
68const NX_WHD_PARSE_FAILED: i64 = 3805
69const NX_WHD_DISPATCH_FAILED: i64 = 3806
70const NX_WHD_SEND_FAILED: i64 = 3807
71
72// ===== Named constants (M7) =================================================
73const NX_WHD_LISTEN_PORT: i64 = 8443 // V1 demo; production binds 443 via cap_net_bind
74const NX_WHD_BACKLOG: i64 = 32
75const NX_WHD_REQ_REC_CAP: i64 = 16384 // 16 KB encrypted request
76const NX_WHD_PLAIN_CAP: i64 = 16384 // 16 KB decrypted request
77const NX_WHD_RESP_CAP: i64 = 1048576 // 1 MB response body
78const NX_WHD_OUT_REC_CAP: i64 = 1048768 // resp + TLS overhead
79const NX_WHD_REQUEST_BUDGET: i64 = 100000 // hard cap on total requests served per process
80
81// V-HOST-2 defaults; operator overrides via config (V-PROV-3 HOT-RELOAD)
82const NX_WHD_DEFAULT_CERT_PATH: *u8 = "/tmp/nx_cert_gen_demo_ed25519_cert.der" as *u8
83const NX_WHD_DEFAULT_PRIV_PATH: *u8 = "/tmp/nx_cert_gen_demo_ed25519_priv.bin" as *u8
84
85// Admin credentials (operator-private; V-PROV-7 sovereign hash CLI replaces this constant pattern)
86const NX_WHD_REALM: *u8 = "Nishi Wiki" as *u8
87const NX_WHD_REALM_N: i64 = 10
88const NX_WHD_ADMIN_USER: *u8 = "elderwesto" as *u8
89const NX_WHD_ADMIN_USER_N: i64 = 10
90
91// ===== Per-conn handle: TLS handshake + one HTTP req/resp =================================================
92
93func nx_whd_handle_one(
94 client_fd: i64,
95 cert_der: *u8, cert_der_len: i64,
96 ed25519_priv: *u8,
97 server_random: *u8, server_x25519_priv: *u8,
98 admin_cfg: *NxAdminLoginConfig,
99 doc_names: *u8, doc_names_count: i64,
100 random_16: *u8, now_s: i64,
101 search_flow: *NxSearchFlow, search_idx: *NxInvIndex,
102 doc_store: *NxWikiDocStore,
103 archive_store: *NxArchiveStore,
104 artifact_store: *NxArtifactStore
105) -> i64 {
106 // 1. Run TLS 1.3 handshake (Ed25519 sig per V-HOST-2+1)
107 let s_raw: i64 = nx_tls13_server_session_run_ed25519(
108 client_fd,
109 server_random, server_x25519_priv,
110 cert_der, cert_der_len,
111 ed25519_priv)
112 if s_raw <= 0 { return 0 - NX_WHD_HS_FAILED }
113 let s: *Tls13ServerSession = s_raw as *Tls13ServerSession
114
115 // 2. Read encrypted request record
116 let rec_buf: *u8 = sys_mmap(NX_WHD_REQ_REC_CAP)
117 let rec_n: i64 = sys_read(client_fd, rec_buf, NX_WHD_REQ_REC_CAP)
118 if rec_n <= 0 { return 0 - NX_WHD_RECV_FAILED }
119
120 // 3. Decrypt to plaintext HTTP
121 let plain: *u8 = sys_mmap(NX_WHD_PLAIN_CAP)
122 let plain_n: i64 = nx_tls13_server_session_app_recv(s, rec_buf, rec_n,
123 plain, NX_WHD_PLAIN_CAP)
124 if plain_n <= 0 { return 0 - NX_WHD_RECV_FAILED }
125
126 // 4. Parse HTTP request (URL + method)
127 let om: *i64 = (sys_mmap(8)) as *i64
128 let opo: *i64 = (sys_mmap(8)) as *i64
129 let opl: *i64 = (sys_mmap(8)) as *i64
130 let headers_end: *i64 = (sys_mmap(8)) as *i64
131 let parse_rc: i64 = nx_http_parse_request(plain, plain_n,
132 om, opo, opl, headers_end)
133 if parse_rc != NXS_OK { return 0 - NX_WHD_PARSE_FAILED }
134 let path_ptr: *u8 = (plain as i64 + opo[0]) as *u8
135 let path_n: i64 = opl[0]
136
137 // 5. Dispatch to wiki router (composes ALL wiki routes; per
138 // project-wiki-v1-complete-2026-05-27 zero 501s remaining)
139 let resp_buf: *u8 = sys_mmap(NX_WHD_RESP_CAP)
140 let resp_n: *i64 = (sys_mmap(8)) as *i64
141 resp_n[0] = 0
142 let disp_rc: i64 = nx_wiki_route_dispatch(
143 plain, headers_end[0],
144 path_ptr, path_n,
145 om[0],
146 admin_cfg,
147 now_s,
148 search_flow, search_idx, doc_store, archive_store, artifact_store,
149 resp_buf, NX_WHD_RESP_CAP,
150 resp_n)
151 if disp_rc != NX_WIKI_ROUTE_OK { return 0 - NX_WHD_DISPATCH_FAILED }
152
153 // 6. Encrypt response as TLS application_data record
154 let out_rec: *u8 = sys_mmap(NX_WHD_OUT_REC_CAP)
155 let out_n: i64 = nx_tls13_server_session_app_send(
156 s, resp_buf, resp_n[0], out_rec, NX_WHD_OUT_REC_CAP)
157 if out_n <= 0 { return 0 - NX_WHD_SEND_FAILED }
158
159 // 7. Write encrypted response to socket
160 if sys_write(client_fd, out_rec, out_n) != out_n {
161 return 0 - NX_WHD_SEND_FAILED
162 }
163
164 return NX_WHD_OK
165}
166
167// ===== Main daemon: setup wiki state + accept loop =================================================
168
169func main() -> i64 {
170 // ----- 1. Load cert + priv -----
171 let cert_len_box: *i64 = (sys_mmap(8)) as *i64
172 cert_len_box[0] = 0
173 let cert_der: *u8 = sys_read_file(NX_WHD_DEFAULT_CERT_PATH, cert_len_box)
174 if (cert_der as i64) == 0 { return NX_WHD_CERT_LOAD_FAILED }
175 let cert_der_len: i64 = cert_len_box[0]
176
177 let priv_len_box: *i64 = (sys_mmap(8)) as *i64
178 priv_len_box[0] = 0
179 let ed25519_priv: *u8 = sys_read_file(NX_WHD_DEFAULT_PRIV_PATH, priv_len_box)
180 if (ed25519_priv as i64) == 0 { return NX_WHD_CERT_LOAD_FAILED }
181 if priv_len_box[0] != 32 { return NX_WHD_CERT_LOAD_FAILED }
182
183 // ----- 2. Generate server_random + x25519 priv (per-process; rotated V+1) -----
184 let server_random: *u8 = sys_mmap(32)
185 let server_x25519_priv: *u8 = sys_mmap(32)
186 nx_csprng_fill(server_random, 32)
187 nx_csprng_fill(server_x25519_priv, 32)
188
189 // ----- 3. Wiki state setup (mirrors nx_wiki_main step 4-6f) -----
190 // Operator-config V-PROV-3 replaces hardcoded paths.
191 let admin_hash_path: *u8 = "/var/nishi-secrets/andelinwest_admin.hash" as *u8
192 let admin_hash_len_box: *i64 = (sys_mmap(8)) as *i64
193 admin_hash_len_box[0] = 0
194 let admin_hash: *u8 = sys_read_file(admin_hash_path, admin_hash_len_box)
195 if (admin_hash as i64) == 0 {
196 // No admin hash file -> daemon runs in PUBLIC-READ-ONLY mode
197 // (operator can wire admin auth via V-PROV-3 once nx_basic_auth
198 // hash CLI ships per V-PROV-7)
199 }
200
201 let sessions: *NxKvStore = (sys_mmap(64)) as *NxKvStore
202 nx_kv_store_init(sessions, 100, 1024)
203
204 let admin_cfg: *NxAdminLoginConfig = (sys_mmap(96)) as *NxAdminLoginConfig
205 nx_admin_login_config_init(admin_cfg,
206 NX_WHD_REALM, NX_WHD_REALM_N,
207 NX_WHD_ADMIN_USER, NX_WHD_ADMIN_USER_N,
208 admin_hash,
209 sessions,
210 86400)
211
212 let doc_names: *u8 = sys_mmap(4096)
213 let doc_names_count: i64 = 0
214 let random_16: *u8 = sys_mmap(16)
215
216 // Search flow (per nx_wiki_main pattern)
217 let search_flow: *NxSearchFlow = (sys_mmap(256)) as *NxSearchFlow
218 nx_search_flow_init(search_flow, 100000, 100, 10000)
219
220 // Doc store + builder (composes V-PROV-2 patterns; V1 uses 11 nishi-silicon docs)
221 let doc_store: *NxWikiDocStore = (sys_mmap(256)) as *NxWikiDocStore
222 nx_wiki_doc_store_init(doc_store, 100, 65536, 65536, 4194304)
223
224 let builder: *NxWikiIndexBuilder = (sys_mmap(64)) as *NxWikiIndexBuilder
225 nx_wiki_index_builder_init(builder, doc_store, 100)
226 // V1 seed doc: hardcoded landing page so daemon serves SOMETHING
227 nx_wiki_index_builder_add(builder,
228 "andelinwest.com" as *u8, 15,
229 "/" as *u8, 1,
230 "Andelin West sovereign Nishi site. First V-HOST-2 daemon GREEN." as *u8,
231 62)
232 nx_wiki_index_builder_finalize(builder)
233 let search_idx: *NxInvIndex = nx_wiki_index_builder_get_index(builder)
234
235 // Archive store (V1 empty; operator seeds via charter ยง10)
236 let archive_store: *NxArchiveStore = (sys_mmap(256)) as *NxArchiveStore
237 nx_archive_store_init(archive_store, 100)
238
239 // Artifact store (V2.0 pipeline; V1 uses empty store for V-HOST-2 minimal)
240 let artifact_store: *NxArtifactStore = (sys_mmap(512)) as *NxArtifactStore
241 nx_artifact_store_init(artifact_store, 100)
242
243 // ----- 4. Bind listen socket -----
244 let addr_buf: *u8 = sys_mmap(16)
245 nx_http_server_addr_any(addr_buf, NX_WHD_LISTEN_PORT)
246 let lv: *i64 = (sys_mmap(8)) as *i64
247 let lfd: i64 = nx_http_server_listen(addr_buf, NX_WHD_BACKLOG, lv)
248 if lfd < 0 { return NX_WHD_BIND_FAILED }
249
250 let banner: *u8 = "nx_wiki_https_daemon listening on 0.0.0.0:8443\n" as *u8
251 sys_write(1, banner, 47)
252
253 // ----- 5. Accept loop -----
254 var n_served: i64 = 0
255 var n_errors: i64 = 0
256 while n_served < NX_WHD_REQUEST_BUDGET {
257 let av: *i64 = (sys_mmap(8)) as *i64
258 let cfd: i64 = nx_http_server_accept_one(lfd, av)
259 if cfd < 0 {
260 n_errors = n_errors + 1
261 n_served = n_served + 1
262 }
263 if cfd >= 0 {
264 let now_ms: i64 = sys_now_ms()
265 let now_s: i64 = now_ms / 1000
266 nx_csprng_fill(random_16, 16)
267
268 let rc: i64 = nx_whd_handle_one(
269 cfd,
270 cert_der, cert_der_len,
271 ed25519_priv,
272 server_random, server_x25519_priv,
273 admin_cfg,
274 doc_names, doc_names_count,
275 random_16, now_s,
276 search_flow, search_idx, doc_store, archive_store, artifact_store)
277
278 // Per Cardinal 14: per-conn failures non-fatal
279 if rc != NX_WHD_OK { n_errors = n_errors + 1 }
280
281 sys_close(cfd)
282 n_served = n_served + 1
283 }
284 }
285
286 sys_close(lfd)
287 return NX_WHD_OK
288}