code wiki / _hdl_build / nx_hostctl.nx

nx_hostctl.nx source

↩ module page · 3789 lines · 262144 B

1// nx_hostctl.nx -- the PURE-NISHI host control plane (operator: "no .sh or any non nishi stuff in the 2// mix... pure enterprise S-class hosting and deployment"). Retires nishi_supervisor.sh. Two subcommands, 3// argv-driven, all via sovereign syscalls -- NO shell, NO cp/mv/pkill: 4// supervise : fork/execve the sites daemon, record its PID, wait4; respawn on death (crash OR deploy-kill). 5// deploy : atomically publish the staged *.new artifacts (sys_renameat), +x the new binary (nx_chmod), 6// then nx_kill the running daemon -> supervise respawns the NEW binary. Brief restart only 7// (FD-passing/SCM_RIGHTS is the future zero-downtime rung). CONTENT hot-update needs NO deploy: 8// the router serves the doc-root file per request, so pushing index.html is live next request. 9// selftest : LOCAL Engineer gate of the deploy primitives (atomic rename + pidfile + kill) -- no NAS. 10// license_tier: ORIGINAL 11// 12// SUPERVISION UPGRADE 2026-06-16 (closes hosting_research gaps #5 health-probe + #4 restart-loop-guard): 13// cmd_supervise no longer trusts PID-liveness alone. For PLAINTEXT daemons it BLACK-BOX health-probes the 14// port (nx_health_probe) and restarts a HUNG daemon (process up + bound but not serving) -- the failure 15// systemd PID-liveness and a LISTEN check both miss. Every restart (crash OR hang) is gated by the 16// crash-loop guard (nx_restart_guard) so a daemon that keeps dying/wedging backs off instead of spinning. 17// The TLS daemon (sites.elf:8443) stays PID-only -- a plaintext GET to a TLS port would false-HUNG; a 18// TLS-aware serving probe is the next rung (noted, not faked). 19import "nx_syscalls.nx" 20import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 21import "nx_health_probe.nx" // hp_probe / HP_HUNG -- real serving health 22import "nx_restart_guard.nx" // rg_should_restart / rg_backoff_ms -- crash-loop guard 23import "nx_os_introspect.nx" // pon_port_owner_pid -- the os-axis /proc perception rung (who REALLY holds a port) 24import "nx_fd_hygiene.nx" // nx_fd_scrub_inherited -- drop inherited fds so a leaked listen socket never propagates into forked daemons 25import "nx_proc_snapshot.nx" // ps_alive_or_direct / ps_refresh -- ONE /proc walk per poll (seq1318) 26// seq961: how long a crawl may show NO log progress before the guard treats it as stalled and reaps it. 27// 600s is far above a polite fetch cycle (a healthy crawl writes a TLS trace per page) and far below the 28// hours a wedged lock would otherwise cost. Data-driven threshold, not a magic number buried in a branch. 29const HC_CRAWL_STALE_S: i64 = 600 30const HC_MAGIC_65536: i64 = 65536 31const HC_MAGIC_8192: i64 = 8192 32const HC_MAGIC_60000: i64 = 60000 33const HC_MAGIC_300000: i64 = 300000 34const HC_MAGIC_8791: i64 = 8791 35const HC_MAGIC_5000: i64 = 5000 36const HC_MAGIC_5000000: i64 = 5000000 37const HC_MAGIC_300000000: i64 = 300000000 38const HC_MAGIC_4096: i64 = 4096 39const HC_MAGIC_8443: i64 = 8443 40const HC_MAGIC_18791: i64 = 18791 41const HC_MAGIC_18190: i64 = 18190 42const HC_MAGIC_18090: i64 = 18090 43const HC_MAGIC_8080: i64 = 8080 44const HC_MAGIC_18793: i64 = 18793 45const HC_MAGIC_18098: i64 = 18098 46const HC_MAGIC_18456: i64 = 18456 47const HC_MAGIC_8456: i64 = 8456 48const HC_MAGIC_15274: i64 = 15274 49const HC_MAGIC_18465: i64 = 18465 50const HC_MAGIC_18466: i64 = 18466 51const HC_MAGIC_8446: i64 = 8446 52const HC_MAGIC_18096: i64 = 18096 53const HC_MAGIC_16384: i64 = 16384 54const HC_MAGIC_7443: i64 = 7443 55const HC_MAGIC_18792: i64 = 18792 56const HC_MAGIC_9091: i64 = 9091 57const HC_MAGIC_8097: i64 = 8097 58const HC_MAGIC_18795: i64 = 18795 59const HC_MAGIC_18794: i64 = 18794 60const HC_MAGIC_18796: i64 = 18796 61const HC_MAGIC_18797: i64 = 18797 62const HC_MAGIC_18099: i64 = 18099 63const HC_MAGIC_18097: i64 = 18097 64const HC_MAGIC_18120: i64 = 18120 65const HC_MAGIC_7702: i64 = 7702 66const HC_MAGIC_6881: i64 = 6881 67const HC_MAGIC_8445: i64 = 8445 68const HC_MAGIC_8027: i64 = 8027 69const HC_MAGIC_8028: i64 = 8028 70const HC_MAGIC_8102: i64 = 8102 71const HC_MAGIC_8029: i64 = 8029 72const HC_MAGIC_8055: i64 = 8055 73const HC_MAGIC_8447: i64 = 8447 74const HC_MAGIC_1195: i64 = 1195 75const HC_MAGIC_5080: i64 = 5080 76const HC_MAGIC_15000: i64 = 15000 77const HC_MAGIC_1500: i64 = 1500 78const HC_MAGIC_2500: i64 = 2500 79const HC_MAGIC_4000: i64 = 4000 80const HC_MAGIC_1200: i64 = 1200 81const HC_MAGIC_2000: i64 = 2000 82const HC_MAGIC_131072: i64 = 131072 83const HC_MAGIC_3600000: i64 = 3600000 84const HC_MAGIC_262144: i64 = 262144 85const HC_MAGIC_2048: i64 = 2048 86const HC_MAGIC_40302: i64 = 40302 87const HC_MAGIC_1048576: i64 = 1048576 88const HC_MAGIC_16000: i64 = 16000 89const HC_MAGIC_16777232: i64 = 16777232 90const HC_MAGIC_16777216: i64 = 16777216 91const HC_MAGIC_90000: i64 = 90000 92const HC_MAGIC_1024: i64 = 1024 93const HC_MAGIC_65535: i64 = 65535 94const HC_MAGIC_8191: i64 = 8191 95const HC_MAGIC_2026: i64 = 2026 96const HC_MAGIC_25000: i64 = 25000 97const HC_MAGIC_86400000: i64 = 86400000 98const HC_MAGIC_3000: i64 = 3000 99const HC_MAGIC_6000: i64 = 6000 100 101const HC_ELF: *u8 = "/volume1/homes/elderwesto/nishihost/sites.elf" as *u8 102const HC_ELF_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/sites.elf.new" as *u8 103const HC_ELF_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/sites.elf.prev" as *u8 // rollback copy (never-brick) 104const HC_REDIRECT: *u8 = "/volume1/homes/elderwesto/nishihost/redirect.elf" as *u8 105const HC_VROOM: *u8 = "/volume1/homes/elderwesto/nishihost/nx_vroom_daemon.elf" as *u8 106const HC_SNIROUTER:*u8 = "/volume1/homes/elderwesto/nishihost/sni_router.elf" as *u8 // sovereign public-edge SNI router (:7443, TLS passthrough) 107const HC_XLATE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_translate_daemon.elf" as *u8 108const HC_XLATE_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_translate_daemon.sov.elf.new" as *u8 109const HC_XLATE_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_translate_daemon.elf.prev" as *u8 110const HC_SIGKILL: i64 = 9 111const HC_CLOCK: *u8 = "/volume1/homes/elderwesto/nishihost/nx_clock_tickless.elf" as *u8 // the ONE consolidated scheduler (const, NOT an inline argv literal -- see the .nx gotcha note below) 112const HC_CONF: *u8 = "/volume1/homes/elderwesto/nishihost/sites.conf" as *u8 113const HC_CONF_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/sites.conf.new" as *u8 114const HC_DOCROOT: *u8 = "/volume1/homes/elderwesto/nishihost/sites/andelinwest" as *u8 115const HC_INDEX: *u8 = "/volume1/homes/elderwesto/nishihost/sites/andelinwest/index.html" as *u8 116const HC_INDEX_NEW:*u8 = "/volume1/homes/elderwesto/nishihost/andelinwest_index.html.new" as *u8 117const HC_PID: *u8 = "/volume1/homes/elderwesto/nishihost/sites.pid" as *u8 118// TLS cert paths (sites.elf loads these at startup; certswap installs a renewed cert + restarts to reload) 119const HC_CERT_LIVE: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_fullchain.der" as *u8 120const HC_CERT_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_fullchain.der.new" as *u8 121const HC_CERT_BAK: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_fullchain.der.bak" as *u8 122const HC_KEY_LIVE: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_ecdsa_key.bin" as *u8 123const HC_KEY_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_ecdsa_key.bin.new" as *u8 124const HC_KEY_BAK: *u8 = "/volume1/homes/elderwesto/nishihost/certs/le_ecdsa_key.bin.bak" as *u8 125// the auth'd library reader (S-class permanence: supervise it like every other sovereign daemon) 126const HC_READER: *u8 = "/volume1/homes/elderwesto/nishi-reader/nx_media_server_auth.elf" as *u8 127const HC_READER_DIR: *u8 = "/volume1/homes/elderwesto/nishi-reader" as *u8 128const HC_READER_KEYS: *u8 = "/volume1/homes/elderwesto/nishihost/auth/keys" as *u8 129const HC_READER_STORE:*u8 = "/volume1/homes/elderwesto/nishihost/auth/store" as *u8 130// the supervisor's OWN binary path (for sovereign self-update via `selfswap`). PID 8636 runs "./nx_hostctl 131// supervise" from cwd nishihost, so the live binary is nishihost/nx_hostctl (no .elf). 132const HC_SELF: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl" as *u8 133const HC_SELF_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl.new" as *u8 134// The name /api/build ACTUALLY stages (<target>.sov.elf.new). Without this, the natural API-first 135// sequence "build hostctl then /api/deploy target=hostctl" ran selfswap with HC_SELF_NEW absent -- 136// see the fail-safe guard in cmd_selfswap and debt seq992/seq983. 137const HC_SELF_NEW_SOV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl.sov.elf.new" as *u8 138const HC_SELF_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_hostctl.prev" as *u8 // never-brick (#26): keep the old supervisor binary for rollback (selfswap had NONE) 139const HC_SUP_LOCK: *u8 = "/volume1/homes/elderwesto/nishihost/supervisor.heartbeat" as *u8 // single-supervisor lease: epoch heartbeat (getpid is BROKEN on this backend -> can't pid-exclude self) 140// reader argv literals MUST be consts, not inline "..." as *u8 as i64 in the argv[] array: an inline literal 141// stored into an i64 array doesn't materialize as a valid pointer (.nx gotcha) -> execve gets garbage argv -> 142// EFAULT -> child exits 127 silently (no banner). HC_READER* consts work; these must too. 143const HC_REALM: *u8 = "Nishi Wiki" as *u8 144const HC_PORT: *u8 = "8791" as *u8 145const HC_SUPERVISE: *u8 = "supervise" as *u8 // selfswap's re-exec argv[1] -- same const-not-inline-literal rule 146const HC_RUNSITE: *u8 = "runsite" as *u8 // startsite's hc_dfork_exec arg -> cmd_runsite execs sites.elf (sovereign, no sh -c) 147// SOVEREIGN daemonization -- NO OS tools (operator: "nishi ecosystem only from the hardware rung up"). The OS 148// `setsid` program survived ONLY because it DOUBLE-forks + EXECVEs a fresh image. We do the SAME in pure .nx: 149// fork + nx_setsid (the setsid SYSCALL) + fork + execve(nx_hostctl <arg>) -- a FRESH execve image, NOT a direct 150// cmd_supervise() call (the inherited image dies on the channel teardown -- PROVEN). That is hc_dfork_exec. 151const HC_SLEEPER: *u8 = "sleeper" as *u8 // daemontest payload: a sovereign sleep loop (no OS /bin/sleep) 152// NAS-RESIDENT MIRROR (operator: "i want the NAS doing these things not the laptop where we dev"). Shallow-execve 153// target + its bin, launched DETACHED via hc_dfork_exec so it survives the SSH channel teardown and streams to 154// LOCAL /volume1 disk (no 9p, no WSL). Additive; disturbs no supervised daemon. 155const HC_RUNMIRROR: *u8 = "runmirror" as *u8 156const HC_MIRROR_BIN: *u8 = "/volume1/ai/apertus/nx_apertus_mirror_nas.elf" as *u8 157// PARALLEL concurrent download ON THE NAS (the max-speed gap WSL couldn't run -- its service crashed on concurrent 158// fork/TLS; the NAS's real Linux handles it). Same shallow-execve/detached pattern as the mirror action. 159const HC_RUNMIRRORPAR: *u8 = "runmirrorpar" as *u8 160const HC_MIRRORPAR_BIN: *u8 = "/volume1/ai/apertus/nx_par_pull_nas.elf" as *u8 161// MAKE-IT-REAL: publish a real NAS file into the hub distribution index (enabler -> magnet+download+api+index). 162const HC_DISTPUB_BIN: *u8 = "/volume1/ai/dist/nx_dist_publish_run.elf" as *u8 163const HC_RUNDISTPUB: *u8 = "rundistpub" as *u8 164// /dist DOWNLOAD server (loopback :18099, content-addressed) + a loopback self-test probe. argv literals MUST be 165// consts (an inline "..." as *u8 as i64 in argv[] corrupts) -- hence the HC_A_* / HC_DIST_PROBE_PATH consts. 166const HC_DISTSERVE_BIN: *u8 = "/volume1/ai/dist/nx_dist_serve.elf" as *u8 167const HC_RUNDISTSERVE: *u8 = "rundistserve" as *u8 168const HC_RUNDISTPROBE: *u8 = "rundistprobe" as *u8 169const HC_A_SERVE: *u8 = "serve" as *u8 170const HC_A_PROBE: *u8 = "probe" as *u8 171const HC_A_PORT: *u8 = "18120" as *u8 172const HC_DIST_PROBE_PATH: *u8 = "/dist/509b6d9d22987cf7b1067a7cc6b74b88cf0c8c2739748fe5e85a4da0f69ebf1a/model.safetensors.index.json" as *u8 173// The reader spawn goes through /bin/sh -c (exactly how the system launches vroom: a `setsid ... &` line). 174// The pure fork+sys_chdir+execve path silently failed under the supervisor (no banner) despite chdir + argv 175// both proving correct in isolation -- some .nx codegen edge in the deep forked-child call path. sh does the 176// cd + arg-parse robustly; `exec` REPLACES the sh so the reader is the supervisor's DIRECT child -> the loop's 177// wait4 reaps it on death and LOGS its sig/code (instrumenting WHY the supervised reader exits, when manual 178// launch of the same line stays up). >>auth.log keeps its banner/errors. cmdline stays the reader's so 179// proc_alive finds it. (Once the exit cause is known + fixed, may revert to a detached setsid form.) 180const HC_SH: *u8 = "/bin/sh" as *u8 181const HC_DASHC: *u8 = "-c" as *u8 182const HC_READER_CMD: *u8 = "cd /volume1/homes/elderwesto/nishi-reader && exec ./nx_media_server_auth.elf /volume1/homes/elderwesto/nishihost/auth/keys /volume1/homes/elderwesto/nishihost/auth/store 'Nishi Wiki' 8791 >> auth.log 2>&1" as *u8 183// --- the NSFW gallery stack (serve :18090 + OPAQUE gateway :18190), at /volume1/ai/galx -- supervised here 184// alongside the site so the bash while-loop supervisor is retired (operator: ONE sovereign supervisor). Spawned 185// via sh -c (exec) for robust cwd+args, exactly like the reader. allow_register=0 (operator-only; the gallery is 186// private -- existing accounts still log in, only NEW signups are blocked = the intended closed state). 187const HC_GALX_GW: *u8 = "/volume1/ai/galx/nx_gallery_gateway.elf" as *u8 188const HC_GALX_GW_NEW: *u8 = "/volume1/ai/galx/nx_gallery_gateway.elf.new" as *u8 189// seq1049: rollback artifact + the build-staged twin. The gateway is the AUTH surface for /gallery -- the one 190// binary that must never be promoted without a banked restore point, and it was the one binary that had none. 191const HC_GALX_GW_PREV: *u8 = "/volume1/ai/galx/nx_gallery_gateway.elf.prev" as *u8 192const HC_GALX_GW_SOVNEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_gallery_gateway.sov.elf.new" as *u8 193const HC_GALX_SERVE: *u8 = "/volume1/ai/galx/nx_gallery_serve.elf" as *u8 194const HC_GALX_SERVE_NEW: *u8 = "/volume1/ai/galx/nx_gallery_serve.elf.new" as *u8 195const HC_GALX_SERVE_CMD: *u8 = "cd /volume1/ai/galx && exec ./nx_gallery_serve.elf 18090 >>/tmp/galx_serve.log 2>&1" as *u8 196// argon2id KSF cost = m=65536 t=3 p=4 -- the ORIGINAL gallery params, RESTORED. Do NOT lower to the login's 197// 19456/2/1: registration is CLOSED ({"error":"registration closed"}), so accounts can't be re-enrolled, and the 198// operator's EXISTING gallery account was registered under THESE params -- OPAQUE re-derives the password with the 199// exact KSF params, so changing them => "invalid credentials". The >10s login was the v2 PROXY keep-alive bug 200// (FIXED: sd2_force_close + keep=0 + write-all), NEVER the KSF cost (galxauthprobe answered in 0ms). 201const HC_GALX_GW_CMD: *u8 = "cd /volume1/ai/galx && exec ./nx_gallery_gateway.elf 18190 knowledge/status/galx_gw_keys knowledge/status/galx_gw_store 1000000000 18090 0 65536 3 4 >>/tmp/galx_gw.log 2>&1" as *u8 202// --- the Nishi Family WIKI gateway (:18791): OPAQUE login + the gated 1195-page wiki, at /volume1/ai/wiki. 203// Supervised here so it auto-restarts forever (no more manual setsid/kill -> the permanent F->S fix). 204const HC_WIKI_GW: *u8 = "/volume1/ai/wiki/nx_wiki_gw.elf" as *u8 205const HC_WIKI_GW_NEW: *u8 = "/volume1/ai/wiki/nx_wiki_gw.elf.new" as *u8 206const HC_WIKI_GW_CMD: *u8 = "cd /volume1/ai/wiki && exec ./nx_wiki_gw.elf 18791 keys store 1000000000 65536 3 4 pages >>/tmp/wiki_gw.log 2>&1" as *u8 207// hub OPAQUE gateway (:18792): leveled /hub via the maturity registry. keys/store = the :9091 /login daemon's 208// (realm nishi_site_admin) so it validates the SAME no-cookie X-Nishi-Session tokens; the gateway reads them read-only. 209const HC_HUB_GW: *u8 = "/volume1/ai/hub/nx_hub_gw.elf" as *u8 210const HC_HUB_GW_NEW: *u8 = "/volume1/ai/hub/nx_hub_gw.elf.new" as *u8 211// Relationship OS daemon (:8027 loopback -- the /relate app). Staged in nishihost cwd like every mgmt target. 212const HC_RELATE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_relate_daemon.elf" as *u8 213const HC_RELATE_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_relate_daemon.elf.new" as *u8 214const HC_RELATE_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_relate_daemon.elf.prev" as *u8 215const HC_RELATE_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_relate_daemon.elf >>/tmp/relate_daemon.log 2>&1" as *u8 216// Ethical CLEAN-SERVE daemon (:8102 -- /clean, SSRF-guarded fetch proxy). Staged in nishihost cwd like every target. 217const HC_CLEANSERVE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_clean_serve_daemon.elf" as *u8 218const HC_CLEANSERVE_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_clean_serve_daemon.elf.new" as *u8 219const HC_DOCPORTAL: *u8 = "/volume1/homes/elderwesto/nishihost/nx_docportal_admin_daemon.elf" as *u8 220const HC_DOCPORTAL_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_docportal_admin_daemon.sov.elf.new" as *u8 221const HC_DOCPORTAL_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_docportal_admin_daemon.elf.prev" as *u8 222const HC_CLEANSERVE_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_clean_serve_daemon.elf.prev" as *u8 223const HC_CLEANSERVE_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_clean_serve_daemon.elf 8102 >>/tmp/cleanserve.log 2>&1" as *u8 224// --- the WORKER MESH: sovereign job console. serve :8029 (backend = console + SSE gen pipeline + gated image) + 225// OPAQUE gateway :8055 (sites /mesh -> here; gated exactly like the gallery, store = a copy of galx_gw_{keys,store}, 226// allow_register=0). Supervised here so the setsid daemons survive crash + reboot (closes the F: unsupervised gap). 227const HC_MESH_SERVE_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_mesh_serve.elf 8029 >>/tmp/mesh_serve.log 2>&1" as *u8 228const HC_MESH_GW_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_mesh_gateway.elf 8055 meshgw_keys meshgw_store 100000 8029 0 65536 3 4 >>/tmp/mesh_gw.log 2>&1" as *u8 229const HC_PROJECT_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_project_serve.elf 8028 opaque_keys.bin opaque_store.log 1000000000 knowledge/project/serve.conf nishi_uid_handle.tsv 19456 2 1 >>/tmp/project_gw.log 2>&1" as *u8 230const HC_HUB_GW_CMD: *u8 = "cd /volume1/ai/hub && exec ./nx_hub_gw.elf 18792 /volume1/homes/elderwesto/nishihost/opaque_keys.bin /volume1/homes/elderwesto/nishihost/opaque_store.log 1000000000 65536 3 4 >>/tmp/hub_gw.log 2>&1" as *u8 231// Sovereign MANAGEMENT API daemon (:18098 loopback HTTP). Reached as <domain>/api via proxy_routes.conf so 232// the ecosystem is operated over the wire (WebFetch), not the shell dance. Args: port keys store realm snap budget. 233// DEDICATED vault-sealed mgmt admin (operator chose "arm a vault-sealed mgmt admin"): its OWN keys+store+realm 234// (mgmt_keys + mgmt_store + realm 'nishi-mgmt', canonical KSF 8192/1/1) -- ISOLATED from the shared auth/store so 235// the control plane NEVER touches the library/operator admin. Handle 'mgmtops', passphrase sealed in the vault 236// (mgmt-admin-pass.nv). /api/login with mgmtops -> token drives /api/{restart,reconcile,deploy,...} over TLS (no shell). 237const HC_MGMT_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_mgmt_api.elf 2>/dev/null; exec ./nx_mgmt_api.elf 18098 /volume1/homes/elderwesto/nishihost/opaque_keys.bin /volume1/homes/elderwesto/nishihost/opaque_store.log nishi_site_admin mgmt_snap.json 1000000000 >>/tmp/mgmt_api.log 2>&1" as *u8 238// admin.<domain> doc-portal ADMIN daemon (:8456 loopback HTTP): OWNER self-service -- invite-gated OPAQUE 239// self-registration (/admin/register) + login + doc upload. Reverse-proxied as admin.<d>/ via proxy_routes.conf. 240// Per-domain keys+store+realm => isolated accounts (andelinwest_docportal here); invites in <store>.invites. 241// Args: port keys store realm budget. Loopback-only; the sites daemon is the only public ingress. 242// budget 20000000: the docportal is a USER-FACING search daemon -- a low budget means it recycles mid-use 243// and the ~15s supervisor respawn gap is a visible hang ("takes forever", operator 2026-07-03). Its per- 244// request mmap leak is FIXED (dsv_respond* now munmap the body), so a high budget no longer grows RSS; and 245// vm.overcommit_memory=1 (hc_assert_overcommit) prevents the fork-wedge regardless. So: recycle rarely. 246const HC_ADMIN_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_docportal_admin_daemon.elf 2>/dev/null; exec ./nx_docportal_admin_daemon.elf 18456 /volume1/homes/elderwesto/nishihost/andelinwest_docportal_keys /volume1/homes/elderwesto/nishihost/andelinwest_docportal_store andelinwest_docportal 20000000 >>/tmp/docportal_admin.log 2>&1" as *u8 247const HC_MAILPORTAL_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_email_portal_daemon.elf 2>/dev/null; exec ./nx_email_portal_daemon.elf 18465 /volume1/homes/elderwesto/nishihost/andelinwest_mail_keys /volume1/homes/elderwesto/nishihost/andelinwest_mail_store andelinwest_mail andelinwest.com /volume1/homes/elderwesto/nishihost/mail_aw/ 20000000 >>/tmp/mail_portal.log 2>&1" as *u8 248const HC_SITEEDIT_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_siteedit_daemon.elf 2>/dev/null; exec ./nx_siteedit_daemon.elf 18466 /volume1/homes/elderwesto/nishihost/andelinwest_docportal_keys /volume1/homes/elderwesto/nishihost/andelinwest_docportal_store andelinwest_docportal andelinwest.com /volume1/homes/elderwesto/nishihost/andelinwest.site /volume1/homes/elderwesto/nishihost/sites/andelinwest 20000000 >>/tmp/siteedit.log 2>&1" as *u8 249// CONTINUOUS M&E: nx_health_eval `loop` = the FUNCTIONAL/QUALITY evaluator (nx_https_get probes of the live 250// surface, writes sites/nishifamily/health.json + ledger trend every 300s). Supervised = it runs FOREVER 251// (respawned if it ever dies) -- this is what kills "fire and pray": the surface is re-measured every poll, 252// not when a human remembers. PID-liveness guard (no port -- it makes outbound TLS, listens on nothing). 253const HC_HEALTH_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_health_eval.elf 2>/dev/null; exec ./nx_health_eval.elf loop 300 >>/tmp/health_eval.log 2>&1" as *u8 254// VIDEO SIGNALING/MEDIA RELAY (:8445 loopback, reached as wss://<d>/signal/<room> via the sites proxy). 255// Task #26 near-miss closed: it was UNSUPERVISED (kill -> dead video, no respawn). Now guarded like every 256// other service; reconcile cycles it onto a freshly deployed ELF (clients auto-reconnect lanes in ~1.2s). 257const HC_SIG2_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_signaling_v2.elf 2>/dev/null; exec ./nx_signaling_v2.elf >>/tmp/signaling_v2.log 2>&1" as *u8 258// media/torrent daemon (:8097): the sovereign acquirer (torrent + direct + HLS download -> gallery), migrated 259// from the dev laptop to the NAS hub. The spawn runs the PORTABLE launcher nx_media_env with the "hub" profile, 260// which deploys the bundle's worker elfs -> /tmp then execs nx_torrent_daemon with the hub's area roots. Until 261// the bundle is deployed to /volume1/ai/torrent the exec just fails -> the guard backs off (fail-safe, no harm). 262const HC_TORRENT_CMD: *u8 = "cd /volume1/ai/torrent && exec ./nx_media_env.sov.elf hub >>/tmp/torrentd.log 2>&1" as *u8 263// torrent OPAQUE gateway (:18793): the public auth front for /torrent (sites_daemon /torrent -> here -> daemon 264// :8097). Runs from /volume1 (exec-friendly). Same keys/store/costs as the :9091 login + hub gw (admin realm). 265const HC_TORRENT_GW_CMD: *u8 = "cd /volume1/ai/torrent && exec ./nx_torrent_gw.elf 18793 /volume1/homes/elderwesto/nishihost/opaque_keys.bin /volume1/homes/elderwesto/nishihost/opaque_store.log 1000000000 65536 3 4 >>/tmp/torrentgw.log 2>&1" as *u8 266// --- Elder AI GEN: orchestrator (:18795, NAS-side gen-img: UI + batch dispatch -> laptop 5080 + gallery 267// ingest) + OPAQUE gateway (:18794, family realm -> he_has_access(/gen) -> orchestrator). sites /gen -> 268// gateway :18794, exactly like /torrent. Gateway keys/store = the :9091 login daemon's (SSO). Fail-safe: 269// backs off until the elfs are deployed to /volume1/ai/gen (gendeploy). The orchestrator dispatches to the 270// laptop engine: address resolved from knowledge/swarm_nodes.conf via nx_swarm_endpoint_lib 271// (role gpu-image), firewall-allowed NAS-only. Was hardcoded .193 here; the box is .192 and this 272// comment lied for as long as the drift lasted -- seq1485. 273const HC_GEN_GW_CMD: *u8 = "cd /volume1/ai/gen && exec ./nx_gen_gateway_daemon.elf 18794 /volume1/homes/elderwesto/nishihost/opaque_keys.bin /volume1/homes/elderwesto/nishihost/opaque_store.log 1000000000 18795 0 19456 2 1 86400 >>/tmp/gen_gw.log 2>&1" as *u8 274const HC_GEN_ORCH_CMD: *u8 = "cd /volume1/ai/gen && exec ./nx_gen_orchestrator_daemon.elf 18795 192 168 8 192 7861 /volume1/ai/gen/store- /volume1/ai/gen/blob- /volume1/ai/gen/cids.tsv laptop-5080 5000 >>/tmp/gen_orch.log 2>&1" as *u8 275// SYNTH STUDIO daemon (:18796 loopback, sites /synth/api -> here via proxy_routes.conf). Renders motion/creatures 276// on demand (nx_synth_serve). Fail-safe: backs off until nx_synth_serve_daemon.elf lands in /volume1/ai/synth. 277const HC_SYNTH_CMD: *u8 = "cd /volume1/ai/synth && exec ./nx_synth_serve_daemon.elf 18796 >>/tmp/synth.log 2>&1" as *u8 278// DEV/CI API daemon (:18797 loopback, sites /api/dev -> here via proxy_routes.conf). build/gate/run organs over 279// HTTP, cap-gated (X-Nishi-Cap dev_build). Fail-safe: backs off until nx_dev_api_serve.elf lands in /volume1/ai/devapi. 280const HC_DEVAPI_CMD: *u8 = "cd /volume1/ai/devapi && chmod +x ./nx_dev_api_serve.elf ./_offc/*.elf 2>/dev/null; exec ./nx_dev_api_serve.elf serve 18797 >>/tmp/devapi.log 2>&1" as *u8 281// login OPAQUE daemon (:9091, the token MINTER -- now mints 24h sessions). keys/store relative to cwd (nishihost). 282// logintest spawns the .new on THROWAWAY :9099 to prove it starts BEFORE logindeploy touches live :9091 (no lockout). 283const HC_LOGIN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_opaque_login.elf" as *u8 284const HC_LOGIN_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/nx_opaque_login.elf.new" as *u8 285const HC_LOGIN_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/nx_opaque_login.elf.prev" as *u8 286// argon2id KSF cost = m=19456 KiB (19 MiB) t=2 p=1 -- the OWASP-2024 interactive-login FLOOR. ~5x faster login 287// than the old 64MiB/t3/p4 (the operator's latency complaint); still a standard memory-hard config for a 288// single-operator media realm. CHANGING these REQUIRES re-registering accounts (KSF params are baked at register). 289// argv[8]=wikidir argv[9]=HR ROSTER (2026-07-30). The roster is what olgd_signup_ok consults for the 290// 'only the family' half of the signup gate; with argv[9] absent hrstore is null and EVERY signup is denied 291// by construction, so the gate could never admit anyone. argv[8] must be passed to reach argv[9], and it is 292// passed as the EXACT compiled-in default so this change is behaviour-preserving for the wiki-serving path. 293// ⚠THAT DEFAULT IS A LAPTOP PATH (/mnt/c/Users/elder/...) baked into a production daemon and it does not 294// exist on the NAS -- preserved verbatim here deliberately rather than silently "fixed" in a supervisor edit; 295// filed separately. The roster file itself is ABSENT on purpose: hra_is_invited returns 0 for a missing store, 296// so signup stays FAIL-CLOSED until an operator provisions invites. Wiring the slot is a technical step; 297// deciding WHO is invited is not mine to make. 298const HC_LOGIN_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_opaque_login.elf 9091 opaque_keys.bin opaque_store.log 1000000000 19456 2 1 /mnt/c/Users/elder/nishi-core/nxc2/knowledge/wiki_pages /volume1/homes/elderwesto/nishihost/nishi_hr.log >>/tmp/login.log 2>&1" as *u8 299const HC_LOGIN_TEST_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./nx_opaque_login.elf.new 9099 opaque_keys.bin opaque_store.log 50 19456 2 1 >>/tmp/logintest.out 2>&1" as *u8 300// --- SOVEREIGN mTLS reverse-proxy TEST launch (NO /bin/sh): hc_dfork_exec(HC_RUNMTLSPROXY) -> cmd_runmtlsproxy 301// shallow-execve's the proxy from the fresh main-dispatch image, exactly like cmd_runsite. :9444 -> loopback 302// wiki_gw :18791; the LE cert is read from an absolute path; the proxy is left RUNNING for a browser test. 303const HC_RUNMTLSPROXY: *u8 = "runmtlsproxy" as *u8 304const HC_MTLS_PROXY_BIN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_aw_mtls_proxy.elf" as *u8 305const HC_MP_PORT: *u8 = "9444" as *u8 306const HC_MP_BACKEND: *u8 = "18791" as *u8 307// --- platformtest: sovereign-launch the carved andelinwest CLIENT daemon (:8453, LE ECDSA) + the SNI router 308// (:8444 -> backend 8443 sites.elf; routes by the property registry) on INTERNAL ports -- :443 untouched. 309const HC_RUNANDELIN: *u8 = "runandelin" as *u8 310const HC_ANDELIN_BIN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_andelinwest_daemon.elf" as *u8 311const HC_RUNSNIR: *u8 = "runsnir" as *u8 312const HC_SNIR_BIN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_sni_router.elf" as *u8 313const HC_SNIR_PORT: *u8 = "8444" as *u8 314const HC_SNIR_BACKEND: *u8 = "8443" as *u8 315const HC_NFTREAD_BIN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_nft_read.elf" as *u8 // sovereign netfilter R0: read-only nat-table probe (grounds legacy-vs-nft) 316const HC_NETTAP_BIN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_netscope_capture.elf" as *u8 // NX-NETSCOPE L-CAPTURE: the sovereign wire tap (AF_PACKET -> nx_packet_dissect) 317// sites.elf spawn via sh -c (exec) -- the SAME proven mechanism the gallery/reader use. The pure-.nx 318// fork+setsid+execve(sites.elf) path hits the deep-forked-child codegen edge (sites.elf dies); sh's exec 319// dodges it. cd sets cwd so sites.elf finds its relative certs/. (Full sovereignty here is blocked ONLY by 320// that nx_cc codegen edge -- a separate compiler fix.) 321const HC_SITES_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && exec ./sites.elf >>/tmp/sites_run.log 2>&1" as *u8 322// --- SOVEREIGN LIBRARY backend (nishifamily.com/research -> :8095, 16K-taxon full-text catalog). 323// Runs from its own dir so it resolves knowledge/libstore-* + libindex.nxinv relatively; sites.elf's 324// /research route reverse-proxies to it. libdeploy: promote nx_lib_httpd.elf.new -> .elf, kill any old 325// (FULL cmdline -- name is 16 chars, comm-truncation would silently miss it), relaunch detached. ADDITIVE: 326// the supervise loop is untouched (a crash leaves /research empty via the proxy fail-safe; nothing else). 327const HC_LIB_ELF: *u8 = "/volume1/homes/elderwesto/nishihost/lib/nx_lib_httpd.elf" as *u8 328const HC_LIB_ELF_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/lib/nx_lib_httpd.elf.new" as *u8 329const HC_LIB_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost/lib && exec ./nx_lib_httpd.elf >>/tmp/lib_run.log 2>&1" as *u8 330// genprobe: one-shot LIVE frontend proof -- runs nx_gen_live_probe.elf (drives the deployed /gen gateway end-to-end) 331// and captures its PASS/FAIL to a file the subcommand reads back (relayed to the operator via nx_aw_hostctl). 332const HC_GENPROBE_CMD: *u8 = "exec /volume1/ai/gen/nx_gen_live_probe.elf >/volume1/ai/gen/genprobe.out 2>&1" as *u8 333// Sovereign agent-facing TOOLS API (:18096 loopback HTTP -> /api/tools + /mcp). R0 of the MCP-benchmark-and-beyond 334// arc: exposes nx_tool_registry as an MCP-compatible + superset surface. Reached as <domain>/api/tools + /mcp via 335// proxy_routes.conf. ADDITIVE isolated service -- backs off until nx_tools_api_serve.elf is deployed (fail-safe). 336const HC_TAPI_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_tools_api_serve.elf 2>/dev/null; mkdir -p knowledge 2>/dev/null; exec ./nx_tools_api_serve.elf serve 18096 >>/tmp/tools_api.log 2>&1" as *u8 337// Finance dashboard server (:18097 loopback HTTP -> serves fin_stack.html). Reached as <domain>/finance (gated) 338// via proxy_routes.conf. ADDITIVE isolated service -- backs off until nx_fin_serve.elf is deployed (fail-safe). 339const HC_FIN_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_fin_serve.elf 2>/dev/null; exec ./nx_fin_serve.elf serve 18097 >>/tmp/fin_serve.log 2>&1" as *u8 340// /dist content-addressed DOWNLOAD server (:18120 loopback -> sites.elf /dist). ADDITIVE isolated service; backs 341// off until nx_dist_serve.elf is deployed to /volume1/ai/dist -> fail-safe. Makes the download transport DURABLE. 342const HC_DIST_CMD: *u8 = "cd /volume1/ai/dist && chmod +x ./nx_dist_serve.elf 2>/dev/null; exec ./nx_dist_serve.elf serve 18120 >>/tmp/dist_serve.log 2>&1" as *u8 343// BitTorrent SEEDER (:6881, INBOUND peers) -- the UPLOAD/SHARE half of the torrent stack. Serves every torrent 344// registered in /volume1/ai/torrent/seed_index.conf (info_hash -> file). ADDITIVE isolated service; backs off 345// until nx_torrent_seed.elf is deployed -> fail-safe. NEVER-BRICK: writes zero state, only net sends + file reads. 346const HC_SEED_CMD: *u8 = "cd /volume1/ai/torrent && chmod +x ./nx_torrent_seed.elf 2>/dev/null; exec ./nx_torrent_seed.elf serve 6881 /volume1/ai/torrent/seed_index.conf >>/tmp/seed.log 2>&1" as *u8 347// DHT ANNOUNCER: periodically announce_peer every registered info_hash FROM the NAS (BEP-5 infers our IP from 348// the packet source) so strangers DISCOVER the seeder via the DHT. ADDITIVE; backs off until deployed -> fail-safe. 349const HC_SEEDANN_CMD: *u8 = "cd /volume1/ai/torrent && chmod +x ./nx_seed_announce_all.elf 2>/dev/null; exec ./nx_seed_announce_all.elf /volume1/ai/torrent/seed_index.conf >>/tmp/seedann.log 2>&1" as *u8 350// torstat: run nx_torrent_seedeval on EVERY active download (media/<btih>) -> SCARCE vs IGNORING vs COMPLETE 351// per torrent -- diagnoses a stuck DL: are missing pieces held by peers (pull/unchoke bug) or absent (dead swarm)? 352const HC_TORSTAT_CMD: *u8 = "cd /volume1/ai/torrent && set -f; chmod +x ./nx_torrent_seedeval.elf 2>/dev/null; : > /tmp/torstat.out; for area in media gallery; do while read b; do echo === $area/$b === >> /tmp/torstat.out; ./nx_torrent_seedeval.elf $area/$b >> /tmp/torstat.out 2>&1; done < $area/torrents.idx 2>/dev/null; done; echo === SEED REGISTRY - what we SHARE back === >> /tmp/torstat.out; cat seed_index.conf >> /tmp/torstat.out 2>/dev/null; echo === seeding $(wc -l < seed_index.conf 2>/dev/null) torrents === >> /tmp/torstat.out" as *u8 353// MULTIPLAYER game relay (:7702 loopback HTTP -> /mineworld-mp page + /pstate POST + /roster GET; shared-mmap 354// rooms, fork-per-connection). Reached via proxy_routes.conf. ADDITIVE isolated service -- backs off until 355// nx_mp_serve.elf is deployed (fail-safe). Worst failure = multiplayer down; touches nothing else (never-brick). 356const HC_MP_CMD: *u8 = "cd /volume1/homes/elderwesto/nishihost && chmod +x ./nx_mp_serve.elf 2>/dev/null; mkdir -p web_assets/_game_build 2>/dev/null; exec ./nx_mp_serve.elf >>/tmp/mp_serve.log 2>&1" as *u8 357 358func hc_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 359func hc_putn(v: i64) -> i64 { 360 let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m} var k: i64=0; if m==0{t[0]=48 as u8;k=1} 361 while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 362 let b: *u8=sys_mmap(28); var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k) 363 sys_munmap(t,28); sys_munmap(b,28); return 0 364} 365func hc_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8) { if a[i]!=b[i] { return 0 } i=i+1 } if b[i]!=(0 as u8) { return 0 } return 1 } 366 367// write a decimal pid to a path 368func hc_write_pid(path: *u8, pid: i64) -> i64 { 369 let buf: *u8 = sys_mmap(28); var m: i64=pid; let t: *u8=sys_mmap(28); var k: i64=0 370 if m==0 { t[0]=48; k=1 } 371 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 372 var i: i64=0; while i<k { buf[i]=t[k-1-i]; i=i+1 } 373 let fd: i64 = sys_openat_wr(path, 0x1a4) 374 if fd<0 { return 0-1 } 375 sys_write(fd, buf, k); sys_close(fd); return 0 376} 377// read a decimal pid from a path (0 if absent/empty) 378func hc_read_pid(path: *u8) -> i64 { 379 let box: *i64 = sys_mmap(16) as *i64; box[0]=0 380 let d: *u8 = sys_read_file(path, box) 381 if (d as i64)==0 { return 0 } 382 let n: i64 = box[0]; var v: i64=0; var i: i64=0 383 while i<n { let c: u8=d[i]; if c>=(48 as u8) { if c<=(57 as u8) { v=v*10+((c-(48 as u8)) as i64) } } i=i+1 } 384 return v 385} 386 387// SINGLE-SUPERVISOR LEASE (getpid is BROKEN here -> can't pid-exclude self in a /proc scan, so use an EPOCH 388// HEARTBEAT): the live supervisor writes `now` to HC_SUP_LOCK every poll. A supervisor starting up that sees a 389// FRESH heartbeat (<90s = 6x the 15s poll, tolerates slow polls) DEFERS instead of racing -- the by-construction 390// prevention of the dueling-supervisor outage. reconcile/selfswap CLEAR the lease (write 0) AFTER killing the old 391// one, so the INTENDED relaunch always claims (never false-defers on a dead supervisor's stale heartbeat). 392func hc_sup_touch() -> i64 { return hc_write_pid(HC_SUP_LOCK, sys_now_realtime_sec()) } 393func hc_sup_clear() -> i64 { return hc_write_pid(HC_SUP_LOCK, 0) } // mark stale -> the next supervisor claims 394func hc_sup_fresh() -> i64 { 395 let e: i64 = hc_read_pid(HC_SUP_LOCK) 396 if e <= 0 { return 0 } // absent / cleared -> free to claim 397 let age: i64 = sys_now_realtime_sec() - e 398 if age < 0 { return 0 } // clock skew -> treat stale, never falsely defer (zero-supervisor is the worst outcome) 399 if age < 90 { return 1 } // a live supervisor heartbeat -> defer 400 return 0 // stale (holder crashed >90s ago) -> claim 401} 402 403// spawn the sites daemon; return its pid 404func hc_spawn() -> i64 { 405 let pid: i64 = sys_fork() 406 if pid==0 { 407 let argv: *i64 = sys_mmap(16) as *i64; argv[0]=HC_ELF as i64; argv[1]=0 408 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 409 sys_execve_clean(HC_ELF, argv, envp) 410 sys_exit(127) 411 } 412 return pid 413} 414 415func hc_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 416func hc_atoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8) { let c: u8=s[i]; if c<(48 as u8){return v} if c>(57 as u8){return v} v=v*10+((c-(48 as u8)) as i64); i=i+1 } return v } 417func hc_contains(hay: *u8, hn: i64, needle: *u8, nn: i64) -> i64 { 418 if nn==0 { return 1 } 419 var i: i64=0 420 while i+nn<=hn { var k: i64=0; var ok: i64=1; while k<nn { if hay[i+k]!=needle[k]{ok=0;k=nn} k=k+1 } if ok==1 { return 1 } i=i+1 } 421 return 0 422} 423// BOUNDED raw read of /proc/<pid>/cmdline into a caller-owned buf (reused across PIDs) -> total bytes. CRITICAL: 424// the proc scanners MUST NOT use sys_read_file here -- it mmaps 4 GiB PER CALL and never frees, so scanning 425// ~200 /proc entries exhausts virtual memory and the next fork fails ENOMEM (the supervisor then can't respawn 426// daemons + dies = the F-class root). This reader allocates nothing; the caller's fixed buffer is reused. 427func hc_read_cmdline(path: *u8, buf: *u8, cap: i64) -> i64 { 428 let fd: i64 = sys_openat_rd(path) 429 if fd < 0 { return 0 } 430 var total: i64 = 0 431 var n: i64 = sys_read(fd, buf, cap) 432 while n > 0 { total = total + n; if total >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + total) as *u8, cap - total) } } 433 sys_close(fd) 434 return total 435} 436 437// Sovereign replacement for pkill: scan /proc, kill every process whose /proc/<pid>/cmdline contains 438// `needle`. No shell. Uses getdents64 + read_file + nx_kill (the now-working sovereign kill). 439func proc_kill_by_name(needle: *u8, sig: i64) -> i64 { 440 let nn: i64 = hc_slen(needle) 441 let fd: i64 = sys_openat_rd("/proc" as *u8) 442 if fd < 0 { return 0 } 443 let buf: *u8 = sys_mmap(HC_MAGIC_65536) 444 let path: *u8 = sys_mmap(256) 445 let clbox: *i64 = sys_mmap(16) as *i64; let clbuf: *u8 = sys_mmap(HC_MAGIC_8192) 446 var killed: i64 = 0 447 var run: i64 = 1 448 while run == 1 { 449 let n: i64 = sys_getdents64(fd, buf, HC_MAGIC_65536) 450 if n <= 0 { run = 0 } else { 451 var off: i64 = 0 452 while off < n { 453 let rec: *u8 = ((buf as i64 + off) as *u8) 454 let reclen: i64 = dirent_reclen(rec) 455 if reclen <= 0 { off = n } else { 456 let name: *u8 = dirent_name(rec) 457 if name[0] >= (48 as u8) { if name[0] <= (57 as u8) { 458 var p: i64 = 0 459 let pre: *u8 = "/proc/" as *u8 460 var a: i64 = 0; while pre[a]!=(0 as u8){path[p]=pre[a];p=p+1;a=a+1} 461 a = 0; while name[a]!=(0 as u8){path[p]=name[a];p=p+1;a=a+1} 462 let suf: *u8 = "/cmdline" as *u8 463 a = 0; while suf[a]!=(0 as u8){path[p]=suf[a];p=p+1;a=a+1} 464 path[p] = 0 as u8 465 let cln: i64 = hc_read_cmdline(path, clbuf, HC_MAGIC_8192) 466 if cln > 0 { if hc_contains(clbuf, cln, needle, nn) == 1 { 467 nx_kill(hc_atoi(name), sig) 468 killed = killed + 1 469 } } 470 } } 471 off = off + reclen 472 } 473 } 474 } 475 } 476 sys_close(fd) 477 sys_munmap(buf, HC_MAGIC_65536); sys_munmap(path, 256); sys_munmap(clbox as *u8, 16); sys_munmap(clbuf, HC_MAGIC_8192) // FREE scan buffers (no leak) 478 return killed 479} 480 481// Kill every process whose /proc/<pid>/cmdline contains BOTH needles. CRITICAL: a single broad needle like 482// "supervise" also matches the OS's s6-supervise service processes (openssh/nginx/docker/gitea/calibre-web/ 483// lanraragi/redis/...) -- killing those would take down SSH and every container. Requiring "nx_hostctl" AND 484// "supervise" TOGETHER uniquely targets our own supervisor ("./nx_hostctl supervise") and nothing else: the 485// "selfswap" process has nx_hostctl but not supervise; s6-supervise has supervise but not nx_hostctl. 486func proc_kill_by_two(n1: *u8, n2: *u8, sig: i64) -> i64 { 487 let l1: i64 = hc_slen(n1); let l2: i64 = hc_slen(n2) 488 let fd: i64 = sys_openat_rd("/proc" as *u8) 489 if fd < 0 { return 0 } 490 let buf: *u8 = sys_mmap(HC_MAGIC_65536); let path: *u8 = sys_mmap(256); let clbox: *i64 = sys_mmap(16) as *i64; let clbuf: *u8 = sys_mmap(HC_MAGIC_8192) 491 var killed: i64 = 0; var run: i64 = 1 492 while run == 1 { 493 let n: i64 = sys_getdents64(fd, buf, HC_MAGIC_65536) 494 if n <= 0 { run = 0 } else { 495 var off: i64 = 0 496 while off < n { 497 let rec: *u8 = ((buf as i64 + off) as *u8) 498 let reclen: i64 = dirent_reclen(rec) 499 if reclen <= 0 { off = n } else { 500 let name: *u8 = dirent_name(rec) 501 if name[0] >= (48 as u8) { if name[0] <= (57 as u8) { 502 var p: i64 = 0; let pre: *u8 = "/proc/" as *u8 503 var a: i64 = 0; while pre[a]!=(0 as u8){path[p]=pre[a];p=p+1;a=a+1} 504 a = 0; while name[a]!=(0 as u8){path[p]=name[a];p=p+1;a=a+1} 505 let suf: *u8 = "/cmdline" as *u8 506 a = 0; while suf[a]!=(0 as u8){path[p]=suf[a];p=p+1;a=a+1} 507 path[p] = 0 as u8 508 let cln: i64 = hc_read_cmdline(path, clbuf, HC_MAGIC_8192) 509 if cln > 0 { 510 if hc_contains(clbuf, cln, n1, l1) == 1 { if hc_contains(clbuf, cln, n2, l2) == 1 { 511 nx_kill(hc_atoi(name), sig) 512 killed = killed + 1 513 } } 514 } 515 } } 516 off = off + reclen 517 } 518 } 519 } 520 } 521 sys_close(fd) 522 sys_munmap(buf, HC_MAGIC_65536); sys_munmap(path, 256); sys_munmap(clbox as *u8, 16); sys_munmap(clbuf, HC_MAGIC_8192) // FREE scan buffers (no leak) 523 return killed 524} 525 526// READ-ONLY: 1 if any process cmdline contains `needle` (no kill). Lets the supervisor ADOPT a running 527// daemon instead of blindly re-spawning it (which would EADDRINUSE on a live :8443). 528// seq1318 (RE-APPLIED 4th time): ONE /proc walk per poll, shared by every guard, instead of a full 529// /proc walk PER GUARD PER POLL (~25 x ~950 = ~23,775 cmdline reads/poll). Measured 180 -> 11 530// jiffies/60s. ps_alive_or_direct is ASYMMETRIC: ALIVE is free from the snapshot; a NOT-ALIVE 531// verdict -- the one that makes this supervisor kill and spawn -- is re-verified against a 532// snapshot <=250ms old, so a stale negative can never double-spawn a just-started daemon. 533// The old inline scan lives in nx_proc_snapshot.nx:ps_alive_direct as the fallback AND the gate oracle. 534func proc_alive_by_name(needle: *u8) -> i64 { 535 return ps_alive_or_direct(needle) 536} 537 538// Respawn a daemon (sites.elf/redirect/vroom/translate) via SINGLE fork + nx_setsid (the daemon becomes a 539// SESSION LEADER, no controlling terminal) + execve -- the exact property the durable `setsid ./sites.elf` 540// has, which PROVABLY survives. The old plain fork+execve left the daemon in the supervisor's session with a 541// ctty and it DIED on the next poll ("restarted (was dead)" forever). The daemon stays this supervisor's CHILD 542// (ppid=supervisor) so the poll-loop reap (wait4 WNOHANG) still collects it if it dies -- we do NOT wait4 here 543// (the daemon runs forever). Returns the daemon pid for hc_write_pid. 544func hc_spawn_path(pth: *u8) -> i64 { 545 let pid: i64 = sys_fork() 546 if pid==0 { 547 nx_setsid() 548 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 549 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 550 let argv: *i64 = sys_mmap(16) as *i64; argv[0]=pth as i64; argv[1]=0 551 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 552 sys_execve_clean(pth, argv, envp) 553 sys_exit(127) 554 return 0 555 } 556 return pid 557} 558 559// CrashLoopBackOff guard params, passed POSITIONALLY to rg_should_restart(ws,cnt,now,cap_ms,base_ms) -- see 560// nx_restart_guard.nx (S-class containment: K8s CrashLoopBackOff + OTP intensity + capped exponential backoff). 561// HC_GUARD_INTERVAL_MS = backoff CAP (max wait between restarts of a hard loop); HC_GUARD_BURST = backoff BASE 562// (first step, doubling each repeat crash). With the ~15s poll: the first ~4 crashes restart at poll rate, then the 563// wait escalates 16s->32s->...->5min cap = a hard crash-loop is CONTAINED to ~1 restart/5min (was: 5 per 2min, 564// reset each window = a SLOW INFINITE loop that spammed logs + starved CPU). A daemon that ran healthy >= 565// RG_HEALTH_RESET_MS(60s) before dying resets -> immediate restart (auto-recovery). (Names kept to avoid touching 566// the 12 call sites; reinterpreted per this comment -- TODO rename to HC_BACKOFF_CAP_MS/HC_BACKOFF_BASE_MS.) 567const HC_GUARD_INTERVAL_MS: i64 = 300000 // cap_ms: 5 min (K8s CrashLoopBackOff ceiling) 568const HC_GUARD_BURST: i64 = 1000 // base_ms: 1s first backoff step (doubles each repeated crash) 569 570// Does this daemon need a restart? DEAD (PID gone) -> yes. ALIVE but its PLAINTEXT port probes HUNG (up but 571// not serving) -> yes. port<=0 = PID-liveness only (TLS / unprobeable daemon) so alive is sufficient. 572func hc_needs_restart(alive: i64, port: i64) -> i64 { 573 if alive == 0 { return 1 } 574 if port <= 0 { return 0 } 575 if hp_probe(port, 3) == HP_HUNG { return 1 } 576 return 0 577} 578 579// Guard one daemon: liveness + (plaintext) serving-probe -> crash-loop-guarded restart. ws/cnt = caller-owned 580// 2-word guard state. write_pid=1 records the new pid (sites.elf). Returns 1 if it restarted, else 0. 581func hc_guard_one(name: *u8, path: *u8, port: i64, ws: *i64, cnt: *i64, pidpath: *u8, write_pid: i64) -> i64 { 582 let alive: i64 = proc_alive_by_name(name) 583 if hc_needs_restart(alive, port) == 0 { return 0 } 584 let now: i64 = sys_now_realtime_ms() 585 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 586 hc_puts(" [guard] " as *u8); hc_puts(name); hc_puts(" crash-looping -> BACKING OFF (not restarting this window)\n" as *u8) 587 return 0 588 } 589 if alive == 1 { proc_kill_by_name(name, 9) } // wedged husk: kill it so the respawn binds the port cleanly 590 let p: i64 = hc_spawn_path(path) 591 if write_pid == 1 { hc_write_pid(pidpath, p) } 592 hc_puts(" [guard] restarted " as *u8); hc_puts(name) 593 if alive == 1 { hc_puts(" (was HUNG)\n" as *u8) } else { hc_puts(" (was dead)\n" as *u8) } 594 return 1 595} 596 597// Spawn the auth'd library reader WITH its args (keysfile storefile realm port) + the correct CWD (its 598// relative knowledge/_offc paths). hc_spawn_path can't -- it execs bare, no args, wrong CWD. 599func hc_spawn_reader() -> i64 { 600 let pid: i64 = sys_fork() 601 if pid==0 { 602 let argv: *i64 = sys_mmap(32) as *i64 603 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_READER_CMD as i64; argv[3]=0 604 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 605 sys_execve_clean(HC_SH, argv, envp) 606 hc_puts(" [spawn_reader] EXECVE /bin/sh FAILED\n" as *u8) 607 sys_exit(127) 608 } 609 return pid 610} 611// Spawn the gallery serve (:18090) / gateway (:18190) WITH cwd+args via sh -c exec (same robust pattern as the 612// reader; hc_spawn_path execs bare/no-args/wrong-cwd). The gateway needs its OPAQUE realm keys+store + backend. 613func hc_spawn_galx_serve() -> i64 { 614 let pid: i64 = sys_fork() 615 if pid==0 { 616 nx_setsid() // SESSION LEADER + detach ctty -> survives the poll loop (the property hc_spawn_path has) 617 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 618 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 619 let argv: *i64 = sys_mmap(32) as *i64 620 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_GALX_SERVE_CMD as i64; argv[3]=0 621 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 622 sys_execve_clean(HC_SH, argv, envp) 623 sys_exit(127) 624 } 625 return pid 626} 627func hc_spawn_galx_gw() -> i64 { 628 let pid: i64 = sys_fork() 629 if pid==0 { 630 nx_setsid() // SESSION LEADER + detach ctty -> survives the poll loop (the property hc_spawn_path has) 631 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 632 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 633 let argv: *i64 = sys_mmap(32) as *i64 634 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_GALX_GW_CMD as i64; argv[3]=0 635 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 636 sys_execve_clean(HC_SH, argv, envp) 637 sys_exit(127) 638 } 639 return pid 640} 641// spawn the wiki OPAQUE gateway (:18791) -- same sh -c (exec) detach pattern as the gallery gateway, so the 642// exec'd nx_wiki_gw.elf is THIS supervisor's direct child + proc_alive_by_name("nx_wiki_gw.elf") finds it. 643func hc_spawn_wiki_gw() -> i64 { 644 let pid: i64 = sys_fork() 645 if pid==0 { 646 nx_setsid() 647 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 648 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 649 let argv: *i64 = sys_mmap(32) as *i64 650 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_WIKI_GW_CMD as i64; argv[3]=0 651 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 652 sys_execve_clean(HC_SH, argv, envp) 653 sys_exit(127) 654 } 655 return pid 656} 657// spawn the hub OPAQUE gateway (:18792) -- mirrors hc_spawn_wiki_gw (sh -c exec detach; direct child of this supervisor). 658func hc_spawn_hub_gw() -> i64 { 659 let pid: i64 = sys_fork() 660 if pid==0 { 661 nx_setsid() 662 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 663 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 664 let argv: *i64 = sys_mmap(32) as *i64 665 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_HUB_GW_CMD as i64; argv[3]=0 666 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 667 sys_execve_clean(HC_SH, argv, envp) 668 sys_exit(127) 669 } 670 return pid 671} 672// spawn the gen orchestrator (:18795) + gateway (:18794) -- mirror hc_spawn_hub_gw (sh -c exec detach; 673// direct child of this supervisor so proc_alive_by_name finds them). 674func hc_spawn_gen_orch() -> i64 { 675 let pid: i64 = sys_fork() 676 if pid==0 { 677 nx_setsid() 678 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 679 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 680 let argv: *i64 = sys_mmap(32) as *i64 681 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_GEN_ORCH_CMD as i64; argv[3]=0 682 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 683 sys_execve_clean(HC_SH, argv, envp) 684 sys_exit(127) 685 } 686 return pid 687} 688func hc_spawn_gen_gw() -> i64 { 689 let pid: i64 = sys_fork() 690 if pid==0 { 691 nx_setsid() 692 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 693 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 694 let argv: *i64 = sys_mmap(32) as *i64 695 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_GEN_GW_CMD as i64; argv[3]=0 696 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 697 sys_execve_clean(HC_SH, argv, envp) 698 sys_exit(127) 699 } 700 return pid 701} 702// spawn the SYNTH STUDIO daemon (:18796) -- mirror hc_spawn_gen_orch (sh -c exec detach). 703func hc_spawn_synth() -> i64 { 704 let pid: i64 = sys_fork() 705 if pid==0 { 706 nx_setsid() 707 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 708 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 709 let argv: *i64 = sys_mmap(32) as *i64 710 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_SYNTH_CMD as i64; argv[3]=0 711 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 712 sys_execve_clean(HC_SH, argv, envp) 713 sys_exit(127) 714 } 715 return pid 716} 717// spawn the DEV/CI API daemon (:18797) -- mirror hc_spawn_synth (sh -c exec detach). 718func hc_spawn_devapi() -> i64 { 719 let pid: i64 = sys_fork() 720 if pid==0 { 721 nx_setsid() 722 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 723 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 724 let argv: *i64 = sys_mmap(32) as *i64 725 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_DEVAPI_CMD as i64; argv[3]=0 726 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 727 sys_execve_clean(HC_SH, argv, envp) 728 sys_exit(127) 729 } 730 return pid 731} 732// spawn the media/torrent daemon (:8097) via the portable launcher nx_media_env hub -- mirrors hc_spawn_hub_gw. 733func hc_spawn_torrent() -> i64 { 734 let pid: i64 = sys_fork() 735 if pid==0 { 736 nx_setsid() 737 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 738 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 739 let argv: *i64 = sys_mmap(32) as *i64 740 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_TORRENT_CMD as i64; argv[3]=0 741 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 742 sys_execve_clean(HC_SH, argv, envp) 743 sys_exit(127) 744 } 745 return pid 746} 747// spawn the torrent OPAQUE gateway (:18793) -- mirrors hc_spawn_torrent / hc_spawn_hub_gw. 748func hc_spawn_torrent_gw() -> i64 { 749 let pid: i64 = sys_fork() 750 if pid==0 { 751 nx_setsid() 752 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 753 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 754 let argv: *i64 = sys_mmap(32) as *i64 755 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_TORRENT_GW_CMD as i64; argv[3]=0 756 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 757 sys_execve_clean(HC_SH, argv, envp) 758 sys_exit(127) 759 } 760 return pid 761} 762// Guard the library reader: liveness + plaintext serving-probe (it serves the login shell on GET / => 200, 763// so hp_probe sees it serving) -> crash-loop-guarded restart-WITH-ARGS. hc_guard_one can't pass args. 764// PID-ONLY (port 0), NOT a serving-probe: ROOT-CAUSE (spawntest-proven) -- hp_probe connects, sends /healthz, 765// reads only 4096 bytes, then CLOSES; the reader writes the (larger) login-shell response into that closed 766// socket -> SIGPIPE -> the reader (no SIGPIPE handler) is TERMINATED. So the supervisor's own health-probe was 767// killing the reader every poll (a curl that reads fully never trips it -- which is why manual launch worked). 768// Liveness-only avoids touching the reader's socket. (The deeper fix = reader ignores SIGPIPE; tracked.) 769func hc_guard_reader(ws: *i64, cnt: *i64) -> i64 { 770 let alive: i64 = proc_alive_by_name("nx_media_server_auth.elf" as *u8) 771 if hc_needs_restart(alive, 0) == 0 { return 0 } 772 let now: i64 = sys_now_realtime_ms() 773 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 774 hc_puts(" [guard] nx_media_server_auth.elf crash-looping -> BACKING OFF (this window)\n" as *u8); return 0 775 } 776 if alive == 1 { proc_kill_by_name("nx_media_server_auth.elf" as *u8, 9) } 777 hc_spawn_reader() 778 hc_puts(" [guard] restarted nx_media_server_auth.elf (library reader, :8791)") 779 if alive == 1 { hc_puts(" (was HUNG)\n" as *u8) } else { hc_puts(" (was dead)\n" as *u8) } 780 return 1 781} 782// Guard the gallery serve/gateway: liveness-only (the listener is fork-per-request; a serving-probe could 783// SIGPIPE a request child, so we trust PID-liveness like sites.elf:8443) -> crash-loop-guarded respawn-with-args. 784func hc_guard_galx_serve(ws: *i64, cnt: *i64) -> i64 { 785 if proc_alive_by_name("nx_gallery_serve.elf" as *u8) == 1 { return 0 } 786 let now: i64 = sys_now_realtime_ms() 787 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 788 hc_puts(" [guard] nx_gallery_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 789 } 790 hc_spawn_galx_serve() 791 hc_puts(" [guard] restarted nx_gallery_serve.elf :18090 (was dead)\n" as *u8) 792 return 1 793} 794func hc_guard_galx_gw(ws: *i64, cnt: *i64) -> i64 { 795 if proc_alive_by_name("nx_gallery_gateway.elf" as *u8) == 1 { return 0 } 796 let now: i64 = sys_now_realtime_ms() 797 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 798 hc_puts(" [guard] nx_gallery_gateway.elf crash-looping -> BACKING OFF\n" as *u8); return 0 799 } 800 hc_spawn_galx_gw() 801 hc_puts(" [guard] restarted nx_gallery_gateway.elf :18190 (was dead)\n" as *u8) 802 return 1 803} 804// Guard the wiki OPAQUE gateway (:18791): liveness-only (fork-per-request listener; a serving-probe could 805// SIGPIPE a request child, like the gallery) -> crash-loop-guarded respawn. This + the poll loop = the wiki 806// gateway is now permanently supervised: it auto-restarts on crash/kill forever, no manual setsid ever again. 807func hc_guard_wiki_gw(ws: *i64, cnt: *i64) -> i64 { 808 if proc_alive_by_name("nx_wiki_gw.elf" as *u8) == 1 { return 0 } 809 let now: i64 = sys_now_realtime_ms() 810 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 811 hc_puts(" [guard] nx_wiki_gw.elf crash-looping -> BACKING OFF\n" as *u8); return 0 812 } 813 hc_spawn_wiki_gw() 814 hc_puts(" [guard] restarted nx_wiki_gw.elf :18791 (was dead)\n" as *u8) 815 return 1 816} 817// Guard the hub OPAQUE gateway (:18792): liveness-only, crash-loop-guarded respawn (mirrors hc_guard_wiki_gw). 818// Until the binary is deployed to HC_HUB_GW it simply backs off (exec fails -> child exits) -- fail-safe, no harm. 819func hc_guard_hub_gw(ws: *i64, cnt: *i64) -> i64 { 820 if proc_alive_by_name("nx_hub_gw.elf" as *u8) == 1 { return 0 } 821 let now: i64 = sys_now_realtime_ms() 822 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 823 hc_puts(" [guard] nx_hub_gw.elf crash-looping -> BACKING OFF\n" as *u8); return 0 824 } 825 hc_spawn_hub_gw() 826 hc_puts(" [guard] restarted nx_hub_gw.elf :18792 (was dead)\n" as *u8) 827 return 1 828} 829// spawn the Relationship OS daemon (:8027 loopback) -- mirrors hc_spawn_hub_gw. 830func hc_spawn_relate() -> i64 { 831 let pid: i64 = sys_fork() 832 if pid==0 { 833 nx_setsid() 834 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 835 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 836 let argv: *i64 = sys_mmap(32) as *i64 837 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_RELATE_CMD as i64; argv[3]=0 838 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 839 sys_execve_clean(HC_SH, argv, envp) 840 sys_exit(127) 841 } 842 return pid 843} 844// Guard the Relationship OS daemon (:8027). liveness-only, crash-loop-guarded (mirrors hc_guard_mgmt). 845// Backs off until nx_relate_daemon.elf is promoted (exec fails -> child exits) -- fail-safe, no harm pre-deploy. 846func hc_guard_relate(ws: *i64, cnt: *i64) -> i64 { 847 if proc_alive_by_name("nx_relate_daemon.elf" as *u8) == 1 { return 0 } 848 let now: i64 = sys_now_realtime_ms() 849 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 850 hc_puts(" [guard] nx_relate_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 851 } 852 hc_spawn_relate() 853 hc_puts(" [guard] restarted nx_relate_daemon.elf :8027 (was dead)\n" as *u8) 854 return 1 855} 856// spawn the ethical CLEAN-SERVE daemon (:8102 -- /clean) -- mirrors hc_spawn_relate. 857func hc_spawn_cleanserve() -> i64 { 858 let pid: i64 = sys_fork() 859 if pid==0 { 860 nx_setsid() 861 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 862 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 863 let argv: *i64 = sys_mmap(32) as *i64 864 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_CLEANSERVE_CMD as i64; argv[3]=0 865 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 866 sys_execve_clean(HC_SH, argv, envp) 867 sys_exit(127) 868 } 869 return pid 870} 871// Guard the CLEAN-SERVE daemon (:8102). liveness-only, crash-loop-guarded (mirrors hc_guard_relate). 872// Backs off until nx_clean_serve_daemon.elf is promoted (exec fails -> child exits) -- fail-safe, no harm pre-deploy. 873func hc_guard_cleanserve(ws: *i64, cnt: *i64) -> i64 { 874 if proc_alive_by_name("nx_clean_serve_daemon.elf" as *u8) == 1 { return 0 } 875 let now: i64 = sys_now_realtime_ms() 876 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 877 hc_puts(" [guard] nx_clean_serve_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 878 } 879 hc_spawn_cleanserve() 880 hc_puts(" [guard] restarted nx_clean_serve_daemon.elf :8102 (was dead)\n" as *u8) 881 return 1 882} 883// spawn the WORKER MESH serve backend (:8029 -- console + SSE gen + gated image) -- mirrors hc_spawn_relate. 884func hc_spawn_mesh_serve() -> i64 { 885 let pid: i64 = sys_fork() 886 if pid==0 { 887 nx_setsid() 888 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 889 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 890 let argv: *i64 = sys_mmap(32) as *i64 891 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_MESH_SERVE_CMD as i64; argv[3]=0 892 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 893 sys_execve_clean(HC_SH, argv, envp) 894 sys_exit(127) 895 } 896 return pid 897} 898// Guard the mesh serve (:8029). liveness-only, crash-loop-guarded (mirrors hc_guard_relate). Backs off until 899// nx_mesh_serve.elf is present -- fail-safe, no harm pre-deploy. ADOPTS the running setsid daemon (no double-spawn). 900func hc_guard_mesh_serve(ws: *i64, cnt: *i64) -> i64 { 901 if proc_alive_by_name("nx_mesh_serve.elf" as *u8) == 1 { return 0 } 902 let now: i64 = sys_now_realtime_ms() 903 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 904 hc_puts(" [guard] nx_mesh_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 905 } 906 hc_spawn_mesh_serve() 907 hc_puts(" [guard] restarted nx_mesh_serve.elf :8029 (was dead)\n" as *u8) 908 return 1 909} 910// spawn the WORKER MESH OPAQUE gateway (:8055 -- gated /mesh console) -- mirrors hc_spawn_relate. 911func hc_spawn_mesh_gw() -> i64 { 912 let pid: i64 = sys_fork() 913 if pid==0 { 914 nx_setsid() 915 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 916 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 917 let argv: *i64 = sys_mmap(32) as *i64 918 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_MESH_GW_CMD as i64; argv[3]=0 919 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 920 sys_execve_clean(HC_SH, argv, envp) 921 sys_exit(127) 922 } 923 return pid 924} 925// Guard the mesh gateway (:8055). liveness-only, crash-loop-guarded (mirrors hc_guard_relate). ADOPTS the running 926// setsid gateway; respawns it after crash/reboot -- this is the durability rung "run it like gallery" implies. 927func hc_guard_mesh_gw(ws: *i64, cnt: *i64) -> i64 { 928 if proc_alive_by_name("nx_mesh_gateway.elf" as *u8) == 1 { return 0 } 929 let now: i64 = sys_now_realtime_ms() 930 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 931 hc_puts(" [guard] nx_mesh_gateway.elf crash-looping -> BACKING OFF\n" as *u8); return 0 932 } 933 hc_spawn_mesh_gw() 934 hc_puts(" [guard] restarted nx_mesh_gateway.elf :8055 (was dead)\n" as *u8) 935 return 1 936} 937// spawn the /project OPAQUE gateway (:8028) -- mirrors hc_spawn_relate (sh -c exec detach). 938func hc_spawn_project() -> i64 { 939 let pid: i64 = sys_fork() 940 if pid==0 { 941 nx_setsid() 942 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 943 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 944 let argv: *i64 = sys_mmap(32) as *i64 945 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_PROJECT_CMD as i64; argv[3]=0 946 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 947 sys_execve_clean(HC_SH, argv, envp) 948 sys_exit(127) 949 } 950 return pid 951} 952// Guard the /project OPAQUE gateway (:8028; sites gated row /project). liveness-only, crash-loop-guarded 953// (mirrors hc_guard_relate). Backs off until nx_project_serve.elf is deployed -- fail-safe, no harm; ADOPTS 954// an already-running detached daemon (liveness by name) exactly like the synth/devapi guards. 955func hc_guard_project(ws: *i64, cnt: *i64) -> i64 { 956 if proc_alive_by_name("nx_project_serve.elf" as *u8) == 1 { return 0 } 957 let now: i64 = sys_now_realtime_ms() 958 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 959 hc_puts(" [guard] nx_project_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 960 } 961 hc_spawn_project() 962 hc_puts(" [guard] restarted nx_project_serve.elf :8028 (was dead)\n" as *u8) 963 return 1 964} 965// spawn the sovereign management API (:18099) -- mirrors hc_spawn_hub_gw. 966func hc_spawn_mgmt() -> i64 { 967 let pid: i64 = sys_fork() 968 if pid==0 { 969 nx_setsid() 970 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 971 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 972 let argv: *i64 = sys_mmap(32) as *i64 973 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_MGMT_CMD as i64; argv[3]=0 974 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 975 sys_execve_clean(HC_SH, argv, envp) 976 sys_exit(127) 977 } 978 return pid 979} 980// Guard the sovereign MANAGEMENT API (:18099 loopback HTTP -> /api). liveness-only, crash-loop-guarded 981// (mirrors hc_guard_hub_gw). Backs off until nx_mgmt_api.elf is deployed -- fail-safe, no harm. 982func hc_guard_mgmt(ws: *i64, cnt: *i64) -> i64 { 983 if proc_alive_by_name("nx_mgmt_api.elf" as *u8) == 1 { return 0 } 984 let now: i64 = sys_now_realtime_ms() 985 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 986 hc_puts(" [guard] nx_mgmt_api.elf crash-looping -> BACKING OFF\n" as *u8); return 0 987 } 988 hc_spawn_mgmt() 989 hc_puts(" [guard] restarted nx_mgmt_api.elf :18099 (was dead)\n" as *u8) 990 return 1 991} 992// spawn the doc-portal ADMIN daemon (:8456) -- mirrors hc_spawn_mgmt. 993func hc_spawn_admin() -> i64 { 994 let pid: i64 = sys_fork() 995 if pid==0 { 996 nx_setsid() 997 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 998 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 999 let argv: *i64 = sys_mmap(32) as *i64 1000 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_ADMIN_CMD as i64; argv[3]=0 1001 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1002 sys_execve_clean(HC_SH, argv, envp) 1003 sys_exit(127) 1004 } 1005 return pid 1006} 1007// Guard the doc-portal ADMIN daemon (:8456 loopback HTTP -> admin.<d>/). liveness-only, crash-loop-guarded 1008// (mirrors hc_guard_mgmt). Backs off until nx_docportal_admin_daemon.elf is deployed -- fail-safe, no harm. 1009func hc_guard_admin(ws: *i64, cnt: *i64) -> i64 { 1010 if proc_alive_by_name("nx_docportal_admin_daemon.elf" as *u8) == 1 { return 0 } 1011 let now: i64 = sys_now_realtime_ms() 1012 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1013 hc_puts(" [guard] nx_docportal_admin_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1014 } 1015 hc_spawn_admin() 1016 // ⚠PORT CORRECTED 8456 -> 18456 AND THE CHECK NAMED (2026-07-30). This line claimed :8456 while 1017 // HC_ADMIN_CMD launches the daemon on 18456 -- and the guard above does not probe a port AT ALL, 1018 // it tests proc_alive_by_name. A message that names a port it never checks invites exactly the 1019 // wrong diagnosis: I read this line, saw the live daemon on :18456, and formed a port-table 1020 // hypothesis (seq1314 class) that was simply false. The guard was right; the message lied. 1021 // ★LAW: a diagnostic must describe the check that was ACTUALLY performed. Naming a port beside a 1022 // name-based liveness test is not a harmless typo -- it is a false lead with a plausible story. 1023 hc_puts(" [guard] restarted nx_docportal_admin_daemon.elf (:18456, liveness by PROCESS NAME not port) (was dead)\n" as *u8) 1024 return 1 1025} 1026// spawn the mail.<d> EMAIL-PORTAL daemon (:18465) -- mirrors hc_spawn_admin. 1027func hc_spawn_mailportal() -> i64 { 1028 let pid: i64 = sys_fork() 1029 if pid==0 { 1030 nx_setsid() 1031 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1032 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1033 let argv: *i64 = sys_mmap(32) as *i64 1034 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_MAILPORTAL_CMD as i64; argv[3]=0 1035 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1036 sys_execve_clean(HC_SH, argv, envp) 1037 sys_exit(127) 1038 } 1039 return pid 1040} 1041// Guard the mail.<d> EMAIL-PORTAL daemon (:18465 loopback HTTP -> mail.<d>/). liveness-only, 1042// crash-loop-guarded; backs off until nx_email_portal_daemon.elf is deployed -- fail-safe, no harm. 1043func hc_guard_mailportal(ws: *i64, cnt: *i64) -> i64 { 1044 if proc_alive_by_name("nx_email_portal_daemon.elf" as *u8) == 1 { return 0 } 1045 let now: i64 = sys_now_realtime_ms() 1046 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1047 hc_puts(" [guard] nx_email_portal_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1048 } 1049 hc_spawn_mailportal() 1050 hc_puts(" [guard] restarted nx_email_portal_daemon.elf :18465 (was dead)\n" as *u8) 1051 return 1 1052} 1053// spawn the admin.<d>/site SITE-VISUALS editor daemon (:18466) -- mirrors hc_spawn_admin. 1054func hc_spawn_siteedit() -> i64 { 1055 let pid: i64 = sys_fork() 1056 if pid==0 { 1057 nx_setsid() 1058 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1059 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1060 let argv: *i64 = sys_mmap(32) as *i64 1061 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_SITEEDIT_CMD as i64; argv[3]=0 1062 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1063 sys_execve_clean(HC_SH, argv, envp) 1064 sys_exit(127) 1065 } 1066 return pid 1067} 1068// Guard the site-visuals editor (:18466 loopback HTTP -> admin.<d>/site). liveness-only, 1069// crash-loop-guarded; backs off until nx_siteedit_daemon.elf is deployed -- fail-safe, no harm. 1070func hc_guard_siteedit(ws: *i64, cnt: *i64) -> i64 { 1071 if proc_alive_by_name("nx_siteedit_daemon.elf" as *u8) == 1 { return 0 } 1072 let now: i64 = sys_now_realtime_ms() 1073 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1074 hc_puts(" [guard] nx_siteedit_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1075 } 1076 hc_spawn_siteedit() 1077 hc_puts(" [guard] restarted nx_siteedit_daemon.elf :18466 (was dead)\n" as *u8) 1078 return 1 1079} 1080// spawn the sovereign agent-facing TOOLS API (:18096) -- mirrors hc_spawn_mgmt. 1081func hc_spawn_tapi() -> i64 { 1082 let pid: i64 = sys_fork() 1083 if pid==0 { 1084 nx_setsid() 1085 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1086 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1087 let argv: *i64 = sys_mmap(32) as *i64 1088 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_TAPI_CMD as i64; argv[3]=0 1089 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1090 sys_execve_clean(HC_SH, argv, envp) 1091 sys_exit(127) 1092 } 1093 return pid 1094} 1095// Guard the sovereign TOOLS API (:18096 loopback HTTP -> /api/tools + /mcp). liveness-only, crash-loop-guarded 1096// (mirrors hc_guard_mgmt). Backs off until nx_tools_api_serve.elf is deployed -- fail-safe, no harm. 1097func hc_guard_tapi(ws: *i64, cnt: *i64) -> i64 { 1098 if proc_alive_by_name("nx_tools_api_serve.elf" as *u8) == 1 { return 0 } 1099 let now: i64 = sys_now_realtime_ms() 1100 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1101 hc_puts(" [guard] nx_tools_api_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1102 } 1103 hc_spawn_tapi() 1104 hc_puts(" [guard] restarted nx_tools_api_serve.elf :18096 (was dead)\n" as *u8) 1105 return 1 1106} 1107// spawn the finance dashboard server (:18097) -- mirrors hc_spawn_tapi exactly. 1108func hc_spawn_fin() -> i64 { 1109 let pid: i64 = sys_fork() 1110 if pid==0 { 1111 nx_setsid() 1112 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1113 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1114 let argv: *i64 = sys_mmap(32) as *i64 1115 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_FIN_CMD as i64; argv[3]=0 1116 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1117 sys_execve_clean(HC_SH, argv, envp) 1118 sys_exit(127) 1119 } 1120 return pid 1121} 1122// Guard the finance dashboard server (:18097 loopback HTTP -> /finance). liveness-only, crash-loop-guarded 1123// (mirrors hc_guard_tapi). Backs off until nx_fin_serve.elf is deployed -- fail-safe, no harm. 1124func hc_guard_fin(ws: *i64, cnt: *i64) -> i64 { 1125 if proc_alive_by_name("nx_fin_serve.elf" as *u8) == 1 { return 0 } 1126 let now: i64 = sys_now_realtime_ms() 1127 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1128 hc_puts(" [guard] nx_fin_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1129 } 1130 hc_spawn_fin() 1131 hc_puts(" [guard] restarted nx_fin_serve.elf :18097 (was dead)\n" as *u8) 1132 return 1 1133} 1134// spawn the CONTINUOUS M&E evaluator (nx_health_eval loop) -- mirrors hc_spawn_fin exactly (sh -c exec 1135// detach; direct child so proc_alive_by_name finds it). It loops forever; if it ever dies, respawned. 1136func hc_spawn_health() -> i64 { 1137 let pid: i64 = sys_fork() 1138 if pid==0 { 1139 nx_setsid() 1140 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1141 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1142 let argv: *i64 = sys_mmap(32) as *i64 1143 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_HEALTH_CMD as i64; argv[3]=0 1144 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1145 sys_execve_clean(HC_SH, argv, envp) 1146 sys_exit(127) 1147 } 1148 return pid 1149} 1150// Guard the M&E evaluator loop. PID-liveness only (no port -- outbound TLS, listens on nothing), crash-loop 1151// guarded (mirrors hc_guard_fin). Backs off until nx_health_eval.elf + data/mozilla_certdata.txt are 1152// deployed -- fail-safe, no harm to anything else (a missing binary just means no respawn, never a brick). 1153func hc_guard_health(ws: *i64, cnt: *i64) -> i64 { 1154 if proc_alive_by_name("nx_health_eval.elf" as *u8) == 1 { return 0 } 1155 let now: i64 = sys_now_realtime_ms() 1156 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1157 hc_puts(" [guard] nx_health_eval.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1158 } 1159 hc_spawn_health() 1160 hc_puts(" [guard] restarted nx_health_eval.elf (continuous M&E loop, was dead)\n" as *u8) 1161 return 1 1162} 1163// spawn the video signaling/media relay (:8445) -- mirrors hc_spawn_health exactly. 1164func hc_spawn_sig2() -> i64 { 1165 let pid: i64 = sys_fork() 1166 if pid==0 { 1167 nx_setsid() 1168 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1169 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1170 let argv: *i64 = sys_mmap(32) as *i64 1171 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_SIG2_CMD as i64; argv[3]=0 1172 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1173 sys_execve_clean(HC_SH, argv, envp) 1174 sys_exit(127) 1175 } 1176 return pid 1177} 1178// Guard the signaling relay (task #26). PID-liveness ("nx_signaling_v2" needle ADOPTS one already running 1179// from any path), crash-loop guarded. Backs off until the ELF is deployed to the canonical path -- fail-safe. 1180func hc_guard_sig2(ws: *i64, cnt: *i64) -> i64 { 1181 if proc_alive_by_name("nx_signaling_v2" as *u8) == 1 { return 0 } 1182 let now: i64 = sys_now_realtime_ms() 1183 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1184 hc_puts(" [guard] nx_signaling_v2 crash-looping -> BACKING OFF\n" as *u8); return 0 1185 } 1186 hc_spawn_sig2() 1187 hc_puts(" [guard] restarted nx_signaling_v2 :8445 (video signaling relay, was dead)\n" as *u8) 1188 return 1 1189} 1190// spawn the /dist download server (:18120) -- mirrors hc_spawn_fin exactly. 1191func hc_spawn_dist() -> i64 { 1192 let pid: i64 = sys_fork() 1193 if pid==0 { 1194 nx_setsid() 1195 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1196 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1197 let argv: *i64 = sys_mmap(32) as *i64 1198 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_DIST_CMD as i64; argv[3]=0 1199 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1200 sys_execve_clean(HC_SH, argv, envp) 1201 sys_exit(127) 1202 } 1203 return pid 1204} 1205// Guard the /dist download server (:18120 -> /dist). liveness-only, crash-loop-guarded (mirrors hc_guard_fin). 1206// Backs off until nx_dist_serve.elf is deployed -- fail-safe. proc_alive_by_name matches the full cmdline (long name safe). 1207func hc_guard_dist(ws: *i64, cnt: *i64) -> i64 { 1208 if proc_alive_by_name("nx_dist_serve.elf" as *u8) == 1 { return 0 } 1209 let now: i64 = sys_now_realtime_ms() 1210 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1211 hc_puts(" [guard] nx_dist_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1212 } 1213 hc_spawn_dist() 1214 hc_puts(" [guard] restarted nx_dist_serve.elf :18120 (was dead)\n" as *u8) 1215 return 1 1216} 1217// spawn the BitTorrent SEEDER (:6881) -- mirrors hc_spawn_dist exactly. 1218func hc_spawn_seed() -> i64 { 1219 let pid: i64 = sys_fork() 1220 if pid==0 { 1221 nx_setsid() 1222 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1223 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1224 let argv: *i64 = sys_mmap(32) as *i64 1225 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_SEED_CMD as i64; argv[3]=0 1226 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1227 sys_execve_clean(HC_SH, argv, envp) 1228 sys_exit(127) 1229 } 1230 return pid 1231} 1232// Guard the BitTorrent seeder (:6881, inbound peers). liveness-only, crash-loop-guarded (mirrors hc_guard_dist). 1233// Backs off until nx_torrent_seed.elf is deployed -- fail-safe. proc_alive_by_name matches the full cmdline. 1234func hc_guard_seed(ws: *i64, cnt: *i64) -> i64 { 1235 if proc_alive_by_name("nx_torrent_seed.elf" as *u8) == 1 { return 0 } 1236 let now: i64 = sys_now_realtime_ms() 1237 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1238 hc_puts(" [guard] nx_torrent_seed.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1239 } 1240 hc_spawn_seed() 1241 hc_puts(" [guard] restarted nx_torrent_seed.elf :6881 (was dead)\n" as *u8) 1242 return 1 1243} 1244// spawn the DHT announcer -- mirrors hc_spawn_seed exactly. 1245func hc_spawn_seedann() -> i64 { 1246 let pid: i64 = sys_fork() 1247 if pid==0 { 1248 nx_setsid() 1249 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1250 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1251 let argv: *i64 = sys_mmap(32) as *i64 1252 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_SEEDANN_CMD as i64; argv[3]=0 1253 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1254 sys_execve_clean(HC_SH, argv, envp) 1255 sys_exit(127) 1256 } 1257 return pid 1258} 1259// Guard the DHT announcer (makes the seeder discoverable). liveness-only, crash-loop-guarded (mirrors hc_guard_seed). 1260func hc_guard_seedann(ws: *i64, cnt: *i64) -> i64 { 1261 if proc_alive_by_name("nx_seed_announce_all.elf" as *u8) == 1 { return 0 } 1262 let now: i64 = sys_now_realtime_ms() 1263 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1264 hc_puts(" [guard] nx_seed_announce_all.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1265 } 1266 hc_spawn_seedann() 1267 hc_puts(" [guard] restarted nx_seed_announce_all.elf (DHT announcer, was dead)\n" as *u8) 1268 return 1 1269} 1270// spawn the multiplayer game relay (:7702) -- mirrors hc_spawn_fin exactly. 1271func hc_spawn_mp() -> i64 { 1272 let pid: i64 = sys_fork() 1273 if pid==0 { 1274 nx_setsid() 1275 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 1276 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 1277 let argv: *i64 = sys_mmap(32) as *i64 1278 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_MP_CMD as i64; argv[3]=0 1279 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 1280 sys_execve_clean(HC_SH, argv, envp) 1281 sys_exit(127) 1282 } 1283 return pid 1284} 1285// Guard the multiplayer game relay (:7702 loopback HTTP -> /mineworld-mp + /pstate + /roster). liveness-only, 1286// crash-loop-guarded (mirrors hc_guard_fin). Backs off until nx_mp_serve.elf is deployed -- fail-safe, no harm. 1287func hc_guard_mp(ws: *i64, cnt: *i64) -> i64 { 1288 if proc_alive_by_name("nx_mp_serve.elf" as *u8) == 1 { return 0 } 1289 let now: i64 = sys_now_realtime_ms() 1290 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1291 hc_puts(" [guard] nx_mp_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1292 } 1293 hc_spawn_mp() 1294 hc_puts(" [guard] restarted nx_mp_serve.elf :7702 (was dead)\n" as *u8) 1295 return 1 1296} 1297// Guard the gen orchestrator (:18795) + gateway (:18794): liveness-only, crash-loop-guarded respawn 1298// (mirrors hc_guard_hub_gw). Fail-safe: backs off until the elfs land in /volume1/ai/gen (gendeploy). 1299func hc_guard_gen_orch(ws: *i64, cnt: *i64) -> i64 { 1300 if proc_alive_by_name("nx_gen_orchestrator_daemon.elf" as *u8) == 1 { return 0 } 1301 let now: i64 = sys_now_realtime_ms() 1302 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1303 hc_puts(" [guard] nx_gen_orchestrator_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1304 } 1305 hc_spawn_gen_orch() 1306 hc_puts(" [guard] restarted nx_gen_orchestrator_daemon.elf :18795 (was dead)\n" as *u8) 1307 return 1 1308} 1309func hc_guard_gen_gw(ws: *i64, cnt: *i64) -> i64 { 1310 if proc_alive_by_name("nx_gen_gateway_daemon.elf" as *u8) == 1 { return 0 } 1311 let now: i64 = sys_now_realtime_ms() 1312 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1313 hc_puts(" [guard] nx_gen_gateway_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1314 } 1315 hc_spawn_gen_gw() 1316 hc_puts(" [guard] restarted nx_gen_gateway_daemon.elf :18794 (was dead)\n" as *u8) 1317 return 1 1318} 1319// Guard the SYNTH STUDIO daemon (:18796): liveness-only, crash-loop-guarded respawn (mirrors hc_guard_gen_orch). 1320// Fail-safe: backs off until nx_synth_serve_daemon.elf is in /volume1/ai/synth. ADOPTS the already-running 1321// detached daemon (proc_alive -> no double-spawn); respawns it after a crash or NAS reboot = durability. 1322func hc_guard_synth(ws: *i64, cnt: *i64) -> i64 { 1323 if proc_alive_by_name("nx_synth_serve_daemon.elf" as *u8) == 1 { return 0 } 1324 let now: i64 = sys_now_realtime_ms() 1325 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1326 hc_puts(" [guard] nx_synth_serve_daemon.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1327 } 1328 hc_spawn_synth() 1329 hc_puts(" [guard] restarted nx_synth_serve_daemon.elf :18796 (was dead)\n" as *u8) 1330 return 1 1331} 1332// Guard the DEV/CI API daemon (:18797): liveness-only, crash-loop-guarded respawn (mirrors hc_guard_synth). 1333// Fail-safe: backs off until nx_dev_api_serve.elf is in /volume1/ai/devapi. ADOPTS the already-running daemon. 1334func hc_guard_devapi(ws: *i64, cnt: *i64) -> i64 { 1335 if proc_alive_by_name("nx_dev_api_serve.elf" as *u8) == 1 { return 0 } 1336 let now: i64 = sys_now_realtime_ms() 1337 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1338 hc_puts(" [guard] nx_dev_api_serve.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1339 } 1340 hc_spawn_devapi() 1341 hc_puts(" [guard] restarted nx_dev_api_serve.elf :18797 (was dead)\n" as *u8) 1342 return 1 1343} 1344// kickdevapi: kill the running dev-API daemon so the guard respawns the freshly-shipped binary (the update path). 1345func cmd_kickdevapi() -> i64 { 1346 let pk: i64 = proc_kill_by_name("nx_dev_api_serve.elf" as *u8, 9) 1347 hc_puts(" kickdevapi: killed nx_dev_api_serve.elf procs=" as *u8); hc_putn(pk) 1348 hc_puts(" -> guard respawns the NEW binary\n" as *u8) 1349 return 0 1350} 1351// devapitoolchain: extract the shipped nx_cc toolchain tarball in the dev-API dir so /api/dev/build+/gate compile ON 1352// the hub (the hub becomes a self-sufficient build host). Idempotent; chmods the binaries. Prints the tar exit code. 1353func cmd_devapitoolchain() -> i64 { 1354 let pid: i64 = sys_fork() 1355 if pid == 0 { 1356 let argv: *i64 = sys_mmap(32) as *i64 1357 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64 1358 argv[2]="cd /volume1/ai/devapi && tar xzf nx_toolchain.tar.gz && chmod +x _offc/*.elf && echo TOOLCHAIN-EXTRACTED-OK && ./_offc/nx_sov_build_run.elf nx_bytes_eq 2>&1 | tail -1" as *u8 as i64; argv[3]=0 1359 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin:/usr/local/bin:/opt/bin" as *u8 as i64; envp[1]=0 1360 sys_execve_clean(HC_SH, argv, envp) 1361 sys_exit(127) 1362 } 1363 let st: *i64 = sys_mmap(16) as *i64 1364 sys_wait4(pid, st, 0) 1365 hc_puts(" devapitoolchain: tar xzf nx_toolchain.tar.gz -> /volume1/ai/devapi/ (exit=" as *u8); hc_putn((st[0]>>8)&255); hc_puts(")\n" as *u8) 1366 return 0 1367} 1368// Guard the media/torrent daemon (:8097): liveness-only (PID by name), crash-loop-guarded respawn via the 1369// portable launcher. Until the bundle is deployed to /volume1/ai/torrent the exec fails -> child exits -> 1370// it simply backs off (fail-safe, no harm to the rest of the fleet -- each guard is independent). 1371func hc_guard_torrent(ws: *i64, cnt: *i64) -> i64 { 1372 if proc_alive_by_name("nx_torrent_daemon" as *u8) == 1 { return 0 } 1373 let now: i64 = sys_now_realtime_ms() 1374 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1375 hc_puts(" [guard] nx_torrent_daemon crash-looping -> BACKING OFF\n" as *u8); return 0 1376 } 1377 hc_spawn_torrent() 1378 hc_puts(" [guard] restarted nx_torrent_daemon :8097 (was dead)\n" as *u8) 1379 return 1 1380} 1381// Guard the torrent OPAQUE gateway (:18793): liveness-only, crash-loop-guarded respawn. Fail-safe: backs off 1382// until the gateway elf is deployed to /volume1/ai/torrent. 1383func hc_guard_torrent_gw(ws: *i64, cnt: *i64) -> i64 { 1384 if proc_alive_by_name("nx_torrent_gw.elf" as *u8) == 1 { return 0 } 1385 let now: i64 = sys_now_realtime_ms() 1386 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1387 hc_puts(" [guard] nx_torrent_gw.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1388 } 1389 hc_spawn_torrent_gw() 1390 hc_puts(" [guard] restarted nx_torrent_gw.elf :18793 (was dead)\n" as *u8) 1391 return 1 1392} 1393// Guard the login OPAQUE daemon (:9091, the token minter): liveness-only respawn. Was UNSUPERVISED (a real 1394// fragility -- a crash = site-wide login outage); now auto-restarts forever. Adopts the running one (proc_alive). 1395func hc_guard_login(ws: *i64, cnt: *i64) -> i64 { 1396 if proc_alive_by_name("nx_opaque_login.elf" as *u8) == 1 { return 0 } 1397 let now: i64 = sys_now_realtime_ms() 1398 if rg_should_restart(ws, cnt, now, HC_GUARD_INTERVAL_MS, HC_GUARD_BURST) == 0 { 1399 hc_puts(" [guard] nx_opaque_login.elf crash-looping -> BACKING OFF\n" as *u8); return 0 1400 } 1401 hc_spawn_login() 1402 hc_puts(" [guard] restarted nx_opaque_login.elf :9091 (was dead)\n" as *u8) 1403 return 1 1404} 1405 1406// DEDICATED READER-KEEPER: fork+exec the reader and BLOCKING-wait4 it, respawn on death, forever. This is 1407// EXACTLY the spawntest pattern that PROVABLY keeps the reader alive -- whereas spawning it from the poll loop 1408// (which then nanosleeps + rescans /proc) reliably kills it ~15s later (cause still unidentified, but the 1409// blocking-wait parent keeps it up; spawntest-proven). The reader is THIS keeper's child, so the main poll 1410// loop never touches it. The keeper logs each death's sig/code. Run as a forked child of cmd_supervise. 1411// PURE respawn-delay policy, extracted so it can be reasoned about and gated independently. 1412// Returns ms to wait before the next respawn attempt. 1413// WHY IT EXISTS (defect found 2026-07-20 while diagnosing a wedged NAS): the original breaker 1414// counted ONLY fast (<5s) code=1 bind-fails -- but this very function's own header documents the 1415// reader's real failure mode as "reliably kills it ~15s later". 15s > 5s, so isfast was never set, 1416// fastfails never reached 8, and THE CIRCUIT-BREAKER WAS DEAD CODE FOR THE ONLY FAILURE THAT 1417// ACTUALLY HAPPENS: an unbounded ~1-per-16s respawn that never escalates and never gives up. 1418// Now ANY repeatedly short-lived death escalates. Still never stops retrying and never deletes 1419// anything, so it self-heals the instant the underlying cause clears (never-brick preserved). 1420func hc_keep_delay(fastfails: i64, shortfails: i64) -> i64 { 1421 if fastfails >= 8 { return HC_MAGIC_60000 } 1422 if shortfails >= 20 { return HC_MAGIC_300000 } 1423 if shortfails >= 8 { return HC_MAGIC_60000 } 1424 return 1000 1425} 1426 1427// where the keeper publishes its monotonic spawn count so `status` can read it in ONE call (seq1623). 1428// Deliberately in the daemon cwd, not /tmp: /tmp is tmpfs and this must survive across samples; a 1429// counter that vanishes is exactly the kind of instrument this session kept catching. 1430const HC_RK_COUNT_PATH: *u8 = "reader_keep.count" as *u8 1431func cmd_reader_keep() -> i64 { 1432 hc_puts("[reader-keep] dedicated blocking-wait keeper for the library reader (:8791)\n" as *u8) 1433 let st: *i64 = sys_mmap(16) as *i64 1434 var run: i64 = 1 1435 var rk_spawns: i64 = 0 // published to HC_RK_COUNT_PATH each spawn; 0 on a fresh supervisor 1436 var shortfails: i64 = 0 // consecutive deaths inside 5 MINUTES (any sig/code) -> the real-mode breaker 1437 var fastfails: i64 = 0 // consecutive FAST code=1 exits (bind-fail => :HC_MAGIC_8791 already held) -> throttle the hopeless hot-spin 1438 while run == 1 { 1439 // ROOT FIX (was the CPU sink): the poll-loop guard hc_guard_reader ALSO keeps :8791 alive, so if a reader is 1440 // already up, spawning another just fork/exec/bind-fails (code=1) forever. Skip when one is already alive; 1441 // re-check every 5s. Self-heals the instant :8791 frees; never deletes anything (never-brick). 1442 if proc_alive_by_name("nx_media_server_auth.elf" as *u8) == 1 { 1443 sys_sleep_ms(HC_MAGIC_5000) 1444 } else { 1445 let t0: i64 = sys_now_us() 1446 rk_spawns = rk_spawns + 1 1447 // hc_write_pid is a GENERIC decimal writer that happens to be pid-named -- reused, not cloned. 1448 hc_write_pid(HC_RK_COUNT_PATH, rk_spawns) 1449 let pid: i64 = hc_spawn_reader() 1450 hc_puts(" [reader-keep] spawned reader pid=" as *u8); hc_putn(pid); hc_puts("\n" as *u8) 1451 st[0]=0 1452 sys_wait4(pid, st, 0) // BLOCKING -- the survival pattern; returns only when the reader actually dies 1453 let sig: i64 = st[0]&0x7f 1454 let code: i64 = (st[0]>>8)&0xff 1455 let life_us: i64 = sys_now_us() - t0 1456 hc_puts(" [reader-keep] reader pid=" as *u8); hc_putn(pid); hc_puts(" died sig=" as *u8); hc_putn(sig); hc_puts(" code=" as *u8); hc_putn(code); hc_puts(" -> respawn\n" as *u8) 1457 // CIRCUIT-BREAKER (never-brick PREVENT): a fast code=1 exit means the reader could not bind :8791 (a live 1458 // duplicate already holds it) -- retrying every 1s just churns fork/exec/bind-fail forever (the measured CPU 1459 // sink). After 8 consecutive fast bind-fails, back off to once/min: 60x less churn, and it STILL self-heals 1460 // the instant :8791 frees (we never stop retrying, never delete anything). 1461 var isfast: i64 = 0 1462 if sig == 0 { if code == 1 { if life_us < HC_MAGIC_5000000 { isfast = 1 } } } 1463 if isfast == 1 { fastfails = fastfails + 1 } else { fastfails = 0 } 1464 // REAL-MODE BREAKER: a reader that dies inside 5 MINUTES is failing, not completing a lifecycle -- 1465 // regardless of sig/code and regardless of whether it was "fast". This is the counter that 1466 // actually moves for the observed ~15s death mode; fastfails alone never did. 1467 var isshort: i64 = 0 1468 if life_us < HC_MAGIC_300000000 { isshort = 1 } 1469 if isshort == 1 { shortfails = shortfails + 1 } else { shortfails = 0 } 1470 if fastfails == 8 { hc_puts(" [reader-keep] 8 consecutive fast bind-fails (:8791 held elsewhere) -> backoff 60s (self-heals when the port frees)\n" as *u8) } 1471 if shortfails == 8 { hc_puts(" [reader-keep] 8 consecutive short-lived deaths -> backoff 60s (breaker ENGAGED; was dead code before 07-20)\n" as *u8) } 1472 if shortfails == 20 { hc_puts(" [reader-keep] 20 consecutive short-lived deaths -> backoff 300s (still retrying, still self-healing)\n" as *u8) } 1473 let delay: i64 = hc_keep_delay(fastfails, shortfails) 1474 // OBSERVABILITY FIX 2026-07-30: the two backoff notices above fire at EXACTLY ==8 and ==20, 1475 // once each, and these lines carry NO TIMESTAMP -- so a tail read past that point shows an 1476 // unbroken wall of spawn/die and looks IDENTICAL whether the breaker is engaged (1 retry per 1477 // minute) or dead (1 per second). I misread exactly that today and called it a fork storm. 1478 // Emit the CHOSEN DELAY on every respawn: the delay IS the rate, so the log states its own 1479 // throttle instead of making a reader infer it from wall-clock it does not have. 1480 hc_puts(" [reader-keep] shortfails=" as *u8); hc_putn(shortfails) 1481 hc_puts(" fastfails=" as *u8); hc_putn(fastfails) 1482 hc_puts(" next-retry-ms=" as *u8); hc_putn(delay); hc_puts("\n" as *u8) 1483 sys_sleep_ms(delay) 1484 } 1485 } 1486 return 0 1487} 1488 1489// ---- LIVE STATUS PAGE: the supervisor already knows every daemon's state each poll, so it emits a fresh 1490// status.html into the nishifamily doc-root every cycle. Browser auto-refreshes (meta refresh) -> a truly 1491// live nishifamily.com/status with zero extra moving parts. The /status monitoring foundation. 1492func hc_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off+i]=s[i];i=i+1} return off+i } 1493func hc_catn(dst: *u8, off: i64, v: i64) -> i64 { 1494 var m: i64=v; if m<0 {m=0-m} 1495 let t: *u8=sys_mmap(28); var k: i64=0; if m==0 {t[0]=48 as u8;k=1} 1496 while m>0 {t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} 1497 var i: i64=0; while i<k {dst[off+i]=t[k-1-i];i=i+1} 1498 return off+k 1499} 1500func hc_status_row(dst: *u8, off: i64, name: *u8, label: *u8) -> i64 { 1501 var o: i64=off 1502 o=hc_cat(dst,o,"<div class=row><span class=svc>" as *u8); o=hc_cat(dst,o,label); o=hc_cat(dst,o,"</span>" as *u8) 1503 if proc_alive_by_name(name)==1 { o=hc_cat(dst,o,"<span class='pill up'>UP</span>" as *u8) } else { o=hc_cat(dst,o,"<span class='pill down'>DOWN</span>" as *u8) } 1504 o=hc_cat(dst,o,"</div>" as *u8) 1505 return o 1506} 1507// hc_snap_svc: one "SVC <name> <port> <UP|DOWN> <procs> <rwin> <rtot>" line for the mgmt-API health snapshot 1508// (read by /api/health + /api/services -> rendered at nishifamily.com/health). procs = BOOLEAN liveness (1=UP / 1509// 0=DOWN) ON PURPOSE: a raw proc count would false-positive mc_is_dup "duplicate-instance" on sites.elf's 1510// per-request fork children -- the exact false +/- to avoid. state=="DOWN" drives the honest down reason. 1511// hc_rwin: the TRUE windowed crash-loop signal for the snapshot -- the guard's own restart counter (cnt) with the 1512// SAME 60s health-reset rg_should_restart applies (nx_restart_guard.nx:44): a daemon that ran healthy >= 1513// RG_HEALTH_RESET_MS since its last restart reads 0, so a service that crash-looped then RECOVERED is NOT falsely 1514// flagged (no false +/-). A genuinely crash-looping daemon (dying repeatedly within the window) shows cnt>0, and 1515// mc_is_loop trips at >=5. nx_clock_tickless is passed 0 by the caller (its by-design tickless respawn is not a 1516// crash-loop) -- belt-and-suspenders against a false flag on the one daemon that respawns on purpose. 1517func hc_rwin(cnt: *i64, ws: *i64, now: i64) -> i64 { 1518 if now - ws[0] >= RG_HEALTH_RESET_MS { return 0 } 1519 return cnt[0] 1520} 1521func hc_snap_svc(buf: *u8, o: i64, name: *u8, port: i64, rwin: i64) -> i64 { 1522 var p: i64 = o 1523 p = hc_cat(buf, p, "SVC " as *u8); p = hc_cat(buf, p, name); p = hc_cat(buf, p, " " as *u8); p = hc_catn(buf, p, port); p = hc_cat(buf, p, " " as *u8) 1524 // Fields: <state> <procs> <rwin> <rtot> <holder>. procs = BOOLEAN liveness (1/0) ON PURPOSE (a raw count false- 1525 // positives mc_is_dup on fork children). rwin = the TRUE windowed restart count (crash-loop signal) from the 1526 // guard state -- the gap this closes. 8th token (holder) = port-holder PID, computed ONLY on the DOWN path. 1527 if proc_alive_by_name(name) == 1 { 1528 p = hc_cat(buf, p, "UP 1 " as *u8); p = hc_catn(buf, p, rwin); p = hc_cat(buf, p, " " as *u8); p = hc_catn(buf, p, rwin); p = hc_cat(buf, p, " 0\n" as *u8) 1529 } else { 1530 var holder: i64 = 0 1531 if port > 0 { holder = pon_port_owner_pid(port) } 1532 p = hc_cat(buf, p, "DOWN 0 " as *u8); p = hc_catn(buf, p, rwin); p = hc_cat(buf, p, " " as *u8); p = hc_catn(buf, p, rwin); p = hc_cat(buf, p, " " as *u8); p = hc_catn(buf, p, holder); p = hc_cat(buf, p, "\n" as *u8) 1533 } 1534 return p 1535} 1536// hc_emit_snapshot: write the mgmt-API health snapshot. SUP 1 = single-supervisor BY CONSTRUCTION (this proc holds 1537// the heartbeat lease; a 2nd supervisor DEFERS, see cmd_supervise) -> mc_is_duel(1)=false, NO false "dueling- 1538// supervisors" (the operator's exact past misdiagnosis). Atomic write to the path nx_mgmt_api reads (HC_MGMT_CMD). 1539func hc_emit_snapshot(rw_sites: i64, rw_wiki: i64, rw_ggw: i64, rw_gserve: i64, rw_reader: i64, rw_redirect: i64, rw_tgw: i64, rw_mgmt: i64, rw_adm: i64, rw_vroom: i64, rw_tapi: i64, rw_mail: i64) -> i64 { 1540 let buf: *u8 = sys_mmap(HC_MAGIC_4096); var o: i64 = 0 1541 o = hc_cat(buf, o, "SUP 1\n" as *u8) 1542 o = hc_snap_svc(buf, o, "sites.elf" as *u8, HC_MAGIC_8443, rw_sites) 1543 o = hc_snap_svc(buf, o, "nx_wiki_gw.elf" as *u8, HC_MAGIC_18791, rw_wiki) 1544 o = hc_snap_svc(buf, o, "nx_gallery_gateway.elf" as *u8, HC_MAGIC_18190, rw_ggw) 1545 o = hc_snap_svc(buf, o, "nx_gallery_serve.elf" as *u8, HC_MAGIC_18090, rw_gserve) 1546 o = hc_snap_svc(buf, o, "nx_media_server_auth.elf" as *u8, HC_MAGIC_8791, rw_reader) 1547 o = hc_snap_svc(buf, o, "redirect.elf" as *u8, HC_MAGIC_8080, rw_redirect) 1548 o = hc_snap_svc(buf, o, "nx_torrent_gw.elf" as *u8, HC_MAGIC_18793, rw_tgw) 1549 o = hc_snap_svc(buf, o, "nx_clock_tickless.elf" as *u8, 0, 0) // rwin=0 BY CONSTRUCTION: tickless respawn is by-design, NOT a crash-loop (no false +/-) 1550 o = hc_snap_svc(buf, o, "nx_mgmt_api.elf" as *u8, HC_MAGIC_18098, rw_mgmt) 1551 o = hc_snap_svc(buf, o, "nx_docportal_admin_daemon.elf" as *u8, HC_MAGIC_18456, rw_adm) // admin.<d>/ (relocated :HC_MAGIC_8456 -> :HC_MAGIC_18456 to dodge PID HC_MAGIC_15274's squat; matches HC_ADMIN_CMD + proxy_routes) -- 8th token = its real :HC_MAGIC_18456 holder 1552 o = hc_snap_svc(buf, o, "nx_email_portal_daemon.elf" as *u8, HC_MAGIC_18465, rw_mail) // mail.<d>/ email portal (matches HC_MAILPORTAL_CMD + proxy_routes) 1553 o = hc_snap_svc(buf, o, "nx_siteedit_daemon.elf" as *u8, HC_MAGIC_18466, 0) // admin.<d>/site visuals editor (rw window not tracked yet) 1554 o = hc_snap_svc(buf, o, "nx_vroom_daemon.elf" as *u8, HC_MAGIC_8446, rw_vroom) // video room backend (nginx-fronted :HC_MAGIC_8446) -- ADDITIVE: surfaces the guarded (poll @890) daemon in /api/health so a dead room is VISIBLE, not silent 1555 o = hc_snap_svc(buf, o, "nx_tools_api_serve.elf" as *u8, HC_MAGIC_18096, rw_tapi) // agent-facing tools API (/api/tools + /mcp) -- R0 MCP-compatible + superset; surfaces in /api/services + /health 1556 // THE TOKEN MINTER (:9091), 2026-07-30 -- same ADDITIVE fix the vroom row above records, for the one 1557 // service it matters most on. hc_guard_login has supervised it for a while, but it appeared in NONE of 1558 // /api/health, /api/services or mgmt_snap.json, so overall:"OK" did not cover the surface that mints 1559 // EVERY session: if login died, health still read OK and an operator watching it learned nothing. 1560 // The guard protects AVAILABILITY; this snapshot protects ATTENTION. They must not disagree about which 1561 // services exist. rwin=0 like the siteedit row -- its restart window is not threaded into this emitter yet 1562 // (an honest 0, not a fabricated number). 1563 o = hc_snap_svc(buf, o, "nx_opaque_login.elf" as *u8, HC_MAGIC_9091, 0) 1564 let tmp: *u8 = "/volume1/homes/elderwesto/nishihost/mgmt_snap.json.tmp" as *u8 1565 let dst: *u8 = "/volume1/homes/elderwesto/nishihost/mgmt_snap.json" as *u8 1566 let fd: i64 = sys_openat_wr(tmp, 0x1a4) 1567 if fd >= 0 { sys_write(fd, buf, o); sys_close(fd); sys_renameat(tmp, dst) } 1568 sys_munmap(buf, HC_MAGIC_4096) 1569 return 0 1570} 1571 1572func hc_emit_status_html(now: i64) -> i64 { 1573 let buf: *u8 = sys_mmap(HC_MAGIC_16384); var o: i64=0 1574 o=hc_cat(buf,o,"<!doctype html><html lang=en><head><meta charset=utf-8><meta http-equiv=refresh content=15><meta name=viewport content='width=device-width,initial-scale=1'><title>Nishi Systems Status</title><style>body{margin:0;background:#0b1020;color:#e6ecf5;font:15px/1.6 -apple-system,Segoe UI,Roboto,sans-serif}.wrap{max-width:680px;margin:0 auto;padding:32px 20px}h1{font-size:1.5rem;margin:0 0 2px}.sub{color:#8794ad;font-size:.9rem;margin:0 0 24px}.row{display:flex;justify-content:space-between;align-items:center;padding:13px 16px;margin:7px 0;background:#141b30;border:1px solid #1f2942;border-radius:9px}.svc{font-weight:500}.pill{font-size:.8rem;font-weight:700;padding:3px 11px;border-radius:20px;letter-spacing:.04em}.pill.up{background:#0f3d24;color:#3ad77f}.pill.down{background:#451620;color:#ff6b81}.ts{color:#5f6d88;font-size:.8rem;margin-top:22px;text-align:center}</style></head><body><div class=wrap>" as *u8) 1575 o=hc_cat(buf,o,"<h1>Nishi Systems Status</h1><p class=sub>live &middot; self-refreshing every 15s &middot; emitted by the sovereign nx_hostctl supervisor</p>" as *u8) 1576 o=hc_status_row(buf,o,"sites.elf" as *u8,"Web &mdash; nishifamily.com (:8443 TLS)" as *u8) 1577 o=hc_status_row(buf,o,"nx_wiki_gw.elf" as *u8,"Wiki OPAQUE gateway (:18791)" as *u8) 1578 o=hc_status_row(buf,o,"nx_hub_gw.elf" as *u8,"Hub OPAQUE gateway (:18792)" as *u8) 1579 o=hc_status_row(buf,o,"nx_gen_gateway_daemon.elf" as *u8,"Gen OPAQUE gateway (:18794)" as *u8) 1580 o=hc_status_row(buf,o,"nx_gen_orchestrator_daemon.elf" as *u8,"Gen orchestrator (:18795)" as *u8) 1581 o=hc_status_row(buf,o,"nx_opaque_login.elf" as *u8,"Login OPAQUE daemon (:9091)" as *u8) 1582 o=hc_status_row(buf,o,"nx_gallery_gateway.elf" as *u8,"Gallery OPAQUE gateway (:18190)" as *u8) 1583 o=hc_status_row(buf,o,"nx_gallery_serve.elf" as *u8,"Gallery media serve (:18090)" as *u8) 1584 o=hc_status_row(buf,o,"nx_media_server_auth.elf" as *u8,"Library reader (:8791)" as *u8) 1585 o=hc_status_row(buf,o,"redirect.elf" as *u8,"HTTP&rarr;HTTPS redirect (:8080)" as *u8) 1586 o=hc_status_row(buf,o,"nx_vroom_daemon.elf" as *u8,"Video room backend (:8446, nginx-fronted)" as *u8) 1587 o=hc_cat(buf,o,"<p class=ts>supervisor poll @ epoch " as *u8); o=hc_catn(buf,o,now); o=hc_cat(buf,o,"s</p></div></body></html>" as *u8) 1588 let tmp: *u8 = "/volume1/homes/elderwesto/nishihost/sites/nishifamily/status.html.tmp" as *u8 1589 let dst: *u8 = "/volume1/homes/elderwesto/nishihost/sites/nishifamily/status.html" as *u8 1590 let fd: i64 = sys_openat_wr(tmp, 0x1a4) 1591 if fd >= 0 { sys_write(fd, buf, o); sys_close(fd); sys_renameat(tmp, dst) } 1592 sys_munmap(buf, HC_MAGIC_16384) 1593 return 0 1594} 1595 1596// POLL supervisor: every 15s, ensure each daemon is alive AND (for plaintext daemons) actually SERVING; 1597// restart any that died or wedged, crash-loop-guarded. ADOPTS already-running daemons (no double-spawn). 1598// Sovereign replacement for nishi_supervisor.sh -- now HUNG-aware, the gap systemd/proc-scan both miss. 1599// assert vm.overcommit_memory=1 (the 2026-07-03 commit-leak outage fix: fork-per-connection daemons with 1600// large anon maps get fork() refused under the mode-0 heuristic once Committed_AS piles up -- the sovereign 1601// spine re-asserts the knob on EVERY supervise start, so a NAS reboot can never silently re-arm the wedge). 1602// RAM-only kernel knob: reversible, never-brick by construction; a write failure logs and continues. 1603func hc_assert_overcommit() -> i64 { 1604 let fd: i64 = sys_openat_wr("/proc/sys/vm/overcommit_memory" as *u8, 0x1a4) 1605 if fd < 0 { hc_puts("[hostctl] WARN: cannot open vm.overcommit_memory (fork-wedge guard NOT asserted)\n" as *u8); return 0 } 1606 sys_write(fd, "1" as *u8, 1) 1607 sys_close(fd) 1608 hc_puts("[hostctl] vm.overcommit_memory=1 asserted (commit-leak fork-wedge guard)\n" as *u8) 1609 return 0 1610} 1611 1612func cmd_supervise() -> i64 { 1613 // FD-LEAK CURE (2026-07-17): drop any INHERITED fd >= 3 before doing anything else. A dirty launch chain 1614 // (a selfswap/boot that still held mgmt's :18098 listen socket) would otherwise propagate that socket to 1615 // EVERY daemon this supervisor forks -- children inherit the supervisor's fd table -- so a redeployed mgmt 1616 // hits EADDRINUSE and cannot rebind :18098 (the ~10min control-plane outage of 2026-07-17). One scrub here 1617 // cleans the supervisor AND, by inheritance, every child it spawns. [[nx_fd_hygiene]] 1618 nx_fd_scrub_inherited() 1619 hc_puts("[hostctl] fd-scrub at supervisor entry (inherited fds dropped -> no listen-socket leak into children)\n" as *u8) 1620 hc_puts("[hostctl] sovereign supervisor: PID-liveness + SERVING health-probe (HUNG-aware) + crash-loop guard\n" as *u8) 1621 hc_assert_overcommit() 1622 // SINGLE-SUPERVISOR by construction: if another supervisor is already heartbeating, DEFER (don't start a 1623 // competing poll loop -- that race was the nishifamily/andelinwest outage). Never zero: the deferring 1624 // instance just exits, the live one keeps running. reconcile/selfswap clear the lease so they always claim. 1625 if hc_sup_fresh() == 1 { 1626 hc_puts("[hostctl] another supervisor is heartbeating (lease fresh <90s) -- DEFERRING, not competing\n" as *u8) 1627 return 0 1628 } 1629 hc_sup_touch() // claim the lease NOW so any later/racing launch sees the fresh heartbeat and defers 1630 let kpid: i64 = sys_fork() // dedicated reader-keeper (blocking-wait pattern); poll loop below handles the rest 1631 if kpid == 0 { cmd_reader_keep(); sys_exit(0); return 0 } 1632 hc_puts("[hostctl] forked reader-keeper pid=" as *u8); hc_putn(kpid); hc_puts("\n" as *u8) 1633 // retire the bash gallery while-loop supervisor (unique: its cmdline has BOTH "while" and the gateway elf; 1634 // the real daemons + the sites supervisor do not) so this becomes the ONE supervisor for the gallery too. 1635 let bk: i64 = proc_kill_by_two("while" as *u8, "nx_gallery_gateway.elf" as *u8, 9) 1636 hc_puts("[hostctl] retired bash gallery supervisor procs=" as *u8); hc_putn(bk); hc_puts("\n" as *u8) 1637 let s_ws: *i64 = sys_mmap(16) as *i64; let s_cnt: *i64 = sys_mmap(16) as *i64 // sites.elf 1638 let r_ws: *i64 = sys_mmap(16) as *i64; let r_cnt: *i64 = sys_mmap(16) as *i64 // redirect.elf 1639 let v_ws: *i64 = sys_mmap(16) as *i64; let v_cnt: *i64 = sys_mmap(16) as *i64 // vroom 1640 let sr_ws: *i64 = sys_mmap(16) as *i64; let sr_cnt: *i64 = sys_mmap(16) as *i64 // sni_router.elf :HC_MAGIC_7443 (sovereign public edge) 1641 let x_ws: *i64 = sys_mmap(16) as *i64; let x_cnt: *i64 = sys_mmap(16) as *i64 // translate 1642 let lib_ws: *i64 = sys_mmap(16) as *i64; let lib_cnt: *i64 = sys_mmap(16) as *i64 // library reader :HC_MAGIC_8791 1643 let reap_st: *i64 = sys_mmap(16) as *i64 // reaped-child status (alloc ONCE, reused -- no per-poll leak) 1644 let gs_ws: *i64 = sys_mmap(16) as *i64; let gs_cnt: *i64 = sys_mmap(16) as *i64 // gallery serve :HC_MAGIC_18090 1645 let gw_ws: *i64 = sys_mmap(16) as *i64; let gw_cnt: *i64 = sys_mmap(16) as *i64 // gallery gateway :HC_MAGIC_18190 1646 let wgw_ws: *i64 = sys_mmap(16) as *i64; let wgw_cnt: *i64 = sys_mmap(16) as *i64 // wiki gateway :HC_MAGIC_18791 1647 let hgw_ws: *i64 = sys_mmap(16) as *i64; let hgw_cnt: *i64 = sys_mmap(16) as *i64 // hub gateway :HC_MAGIC_18792 1648 let lgn_ws: *i64 = sys_mmap(16) as *i64; let lgn_cnt: *i64 = sys_mmap(16) as *i64 // login daemon :HC_MAGIC_9091 1649 let tor_ws: *i64 = sys_mmap(16) as *i64; let tor_cnt: *i64 = sys_mmap(16) as *i64 // media/torrent daemon :HC_MAGIC_8097 1650 let tgw_ws: *i64 = sys_mmap(16) as *i64; let tgw_cnt: *i64 = sys_mmap(16) as *i64 // torrent OPAQUE gateway :HC_MAGIC_18793 1651 let gor_ws: *i64 = sys_mmap(16) as *i64; let gor_cnt: *i64 = sys_mmap(16) as *i64 // gen orchestrator :HC_MAGIC_18795 1652 let ggw_ws: *i64 = sys_mmap(16) as *i64; let ggw_cnt: *i64 = sys_mmap(16) as *i64 // gen OPAQUE gateway :HC_MAGIC_18794 1653 let syn_ws: *i64 = sys_mmap(16) as *i64; let syn_cnt: *i64 = sys_mmap(16) as *i64 // SYNTH STUDIO daemon :HC_MAGIC_18796 (sites /synth/api -> here; renders motion/creatures on demand) 1654 let devapi_ws: *i64 = sys_mmap(16) as *i64; let devapi_cnt: *i64 = sys_mmap(16) as *i64 // DEV/CI API daemon :HC_MAGIC_18797 (sites /api/dev -> here; build/gate/run organs over HTTP, cap-gated) 1655 let clk_ws: *i64 = sys_mmap(16) as *i64; let clk_cnt: *i64 = sys_mmap(16) as *i64 // nx_clock_tickless: the ONE consolidated clock (runs a bounded tickless window then exits -> respawned = continuous) 1656 let mgmt_ws: *i64 = sys_mmap(16) as *i64; let mgmt_cnt: *i64 = sys_mmap(16) as *i64 // mgmt API :HC_MAGIC_18099 (HTTP /api) 1657 let adm_ws: *i64 = sys_mmap(16) as *i64; let adm_cnt: *i64 = sys_mmap(16) as *i64 // doc-portal admin daemon :HC_MAGIC_8456 (admin.<d>/ owner self-service) 1658 let mail_ws: *i64 = sys_mmap(16) as *i64; let mail_cnt: *i64 = sys_mmap(16) as *i64 // email-portal daemon :HC_MAGIC_18465 (mail.<d>/ webmail) 1659 let sed_ws: *i64 = sys_mmap(16) as *i64; let sed_cnt: *i64 = sys_mmap(16) as *i64 // site-visuals editor :HC_MAGIC_18466 (admin.<d>/site) 1660 let tapi_ws: *i64 = sys_mmap(16) as *i64; let tapi_cnt: *i64 = sys_mmap(16) as *i64 // tools API :HC_MAGIC_18096 (HTTP /api/tools + /mcp -- R0 agent-facing API) 1661 let fin_ws: *i64 = sys_mmap(16) as *i64; let fin_cnt: *i64 = sys_mmap(16) as *i64 // finance dashboard :HC_MAGIC_18097 (HTTP /finance) 1662 let dist_ws: *i64 = sys_mmap(16) as *i64; let dist_cnt: *i64 = sys_mmap(16) as *i64 // /dist download server :HC_MAGIC_18120 (HTTP /dist -- content-addressed) 1663 let mp_ws: *i64 = sys_mmap(16) as *i64; let mp_cnt: *i64 = sys_mmap(16) as *i64 // multiplayer game relay :HC_MAGIC_7702 (HTTP /mineworld-mp + /pstate + /roster) 1664 let seed_ws: *i64 = sys_mmap(16) as *i64; let seed_cnt: *i64 = sys_mmap(16) as *i64 // BitTorrent seeder :HC_MAGIC_6881 (inbound peers -- the UPLOAD/SHARE half) 1665 let sann_ws: *i64 = sys_mmap(16) as *i64; let sann_cnt: *i64 = sys_mmap(16) as *i64 // DHT announcer (makes the seeder discoverable via BEP-5) 1666 let he_ws: *i64 = sys_mmap(16) as *i64; let he_cnt: *i64 = sys_mmap(16) as *i64 // continuous M&E evaluator (nx_health_eval loop -- functional/quality health of the whole surface, every 300s) 1667 let sg_ws: *i64 = sys_mmap(16) as *i64; let sg_cnt: *i64 = sys_mmap(16) as *i64 // video signaling/media relay :HC_MAGIC_8445 (task #26: was UNSUPERVISED -- the fire-and-pray near-miss) 1668 let rel_ws: *i64 = sys_mmap(16) as *i64; let rel_cnt: *i64 = sys_mmap(16) as *i64 // Relationship OS daemon :HC_MAGIC_8027 (the /relate app; backs off until relatedeploy'd) 1669 let pjw_ws: *i64 = sys_mmap(16) as *i64; let pjw_cnt: *i64 = sys_mmap(16) as *i64 // /project OPAQUE gateway :HC_MAGIC_8028 (contractor portal; adopts the running daemon) 1670 let cs_ws: *i64 = sys_mmap(16) as *i64; let cs_cnt: *i64 = sys_mmap(16) as *i64 // ethical CLEAN-SERVE daemon :HC_MAGIC_8102 (/clean; backs off until cleanservedeploy'd) 1671 let meshsrv_ws: *i64 = sys_mmap(16) as *i64; let meshsrv_cnt: *i64 = sys_mmap(16) as *i64 // WORKER MESH serve :HC_MAGIC_8029 (console + SSE gen + gated image backend) 1672 let meshgw_ws: *i64 = sys_mmap(16) as *i64; let meshgw_cnt: *i64 = sys_mmap(16) as *i64 // WORKER MESH OPAQUE gateway :HC_MAGIC_8055 (gated /mesh console) 1673 var run: i64 = 1 1674 while run == 1 { 1675 // seq1318: ONE /proc walk feeds EVERY liveness question below; all guards read the SAME instant. 1676 ps_refresh() 1677 hc_guard_one("sites.elf" as *u8, HC_ELF, 0, s_ws, s_cnt, HC_PID, 1) // TLS:HC_MAGIC_8443 -> PID-only 1678 hc_guard_one("redirect.elf" as *u8, HC_REDIRECT, 0, r_ws, r_cnt, HC_PID, 0) // PID-only: the :HC_MAGIC_8080 serving-probe (hp_probe sends /healthz, reads HC_MAGIC_4096, closes) SIGPIPE'd/HUNG-killed this working daemon every poll -> crash-loop (SAME root cause as the reader, see hostctl:~409). PID-liveness like sites.elf. (verify post-deploy: external probe :80 -> 301 https) 1679 hc_guard_one("nx_vroom_daemon.elf" as *u8, HC_VROOM, HC_MAGIC_8446, v_ws, v_cnt, HC_PID, 0) // plaintext -> probe 1680 hc_guard_one("/sni_router.elf" as *u8, HC_SNIROUTER, 0, sr_ws, sr_cnt, HC_PID, 0) // PID-only (TLS passthrough, unprobeable); "/sni_router.elf" needle excludes nx_sni_router.elf 1681 hc_guard_one("nx_translate_daemon.elf" as *u8, HC_XLATE, HC_MAGIC_8447, x_ws, x_cnt, HC_PID, 0) // plaintext -> probe 1682 hc_guard_galx_serve(gs_ws, gs_cnt) // gallery serve :HC_MAGIC_18090 (NSFW gallery backend) 1683 hc_guard_galx_gw(gw_ws, gw_cnt) // gallery OPAQUE gateway :HC_MAGIC_18190 (login + media proxy) 1684 hc_guard_wiki_gw(wgw_ws, wgw_cnt) // wiki OPAQUE gateway :HC_MAGIC_18791 (login + the gated HC_MAGIC_1195-page wiki) 1685 hc_guard_hub_gw(hgw_ws, hgw_cnt) // hub OPAQUE gateway :HC_MAGIC_18792 (leveled /hub via the maturity registry) 1686 hc_guard_login(lgn_ws, lgn_cnt) // login OPAQUE daemon :HC_MAGIC_9091 (the token minter -- was unsupervised) 1687 hc_guard_torrent(tor_ws, tor_cnt) // media/torrent daemon :HC_MAGIC_8097 (the acquirer, migrated dev -> NAS hub; backs off until deployed) 1688 hc_guard_torrent_gw(tgw_ws, tgw_cnt) // torrent OPAQUE gateway :HC_MAGIC_18793 (sites /torrent -> here -> daemon; the live nishifamily front) 1689 hc_guard_gen_orch(gor_ws, gor_cnt) // gen orchestrator :HC_MAGIC_18795 (UI+batch->HC_MAGIC_5080+gallery; backs off until gendeploy'd) 1690 hc_guard_gen_gw(ggw_ws, ggw_cnt) // gen OPAQUE gateway :HC_MAGIC_18794 (sites /gen -> here -> orchestrator; backs off until gendeploy'd) 1691 hc_guard_synth(syn_ws, syn_cnt) // SYNTH STUDIO daemon :HC_MAGIC_18796 (sites /synth/api -> here; adopts the running detached daemon, respawns after crash/reboot) 1692 hc_guard_devapi(devapi_ws, devapi_cnt) // DEV/CI API daemon :HC_MAGIC_18797 (sites /api/dev -> here; adopts running daemon, respawns after crash/reboot) 1693 hc_guard_one("nx_clock_tickless.elf" as *u8, HC_CLOCK, 0, clk_ws, clk_cnt, HC_PID, 0) // the ONE consolidated scheduler: PID-only (alive during its tickless window, dead after -> respawn = continuous, no per-job daemon). ADDITIVE: nothing above changes. 1694 hc_guard_mgmt(mgmt_ws, mgmt_cnt) // sovereign management API :HC_MAGIC_18099 (HTTP /api -- operate the ecosystem over the wire, not the shell dance) 1695 hc_guard_relate(rel_ws, rel_cnt) // Relationship OS daemon :HC_MAGIC_8027 (/relate; fail-safe backoff until relatedeploy promotes the binary) 1696 hc_guard_project(pjw_ws, pjw_cnt) // /project OPAQUE gateway :HC_MAGIC_8028 (contractor portal; fail-safe backoff until the elf is deployed) 1697 hc_guard_cleanserve(cs_ws, cs_cnt) // ethical CLEAN-SERVE daemon :HC_MAGIC_8102 (/clean; fail-safe backoff until cleanservedeploy promotes the binary) 1698 hc_guard_mesh_serve(meshsrv_ws, meshsrv_cnt) // WORKER MESH serve :HC_MAGIC_8029 (sovereign job console backend; adopts the running setsid daemon, respawns after crash/reboot) 1699 hc_guard_mesh_gw(meshgw_ws, meshgw_cnt) // WORKER MESH OPAQUE gateway :HC_MAGIC_8055 (sites /mesh -> here; gated console, respawns after crash/reboot) 1700 hc_guard_admin(adm_ws, adm_cnt) // doc-portal ADMIN daemon :HC_MAGIC_8456 (admin.<d>/ -- owner self-service: invite-gated self-register + upload) 1701 hc_guard_mailportal(mail_ws, mail_cnt) // EMAIL-PORTAL daemon :HC_MAGIC_18465 (mail.<d>/ -- Modern-Auth webmail: invite register + inbox + local delivery) 1702 hc_guard_siteedit(sed_ws, sed_cnt) // SITE-VISUALS editor :HC_MAGIC_18466 (admin.<d>/site -- blueprint save/preview/publish, same realm as the doc portal) 1703 hc_guard_tapi(tapi_ws, tapi_cnt) // agent-facing tools API :HC_MAGIC_18096 (HTTP /api/tools + /mcp -- R0 MCP-compatible + superset) 1704 hc_guard_fin(fin_ws, fin_cnt) // finance dashboard :HC_MAGIC_18097 (HTTP /finance -- gated login surface) 1705 hc_guard_dist(dist_ws, dist_cnt) // /dist content-addressed download server :HC_MAGIC_18120 (durable -- auto-restarts) 1706 hc_guard_seed(seed_ws, seed_cnt) // BitTorrent seeder :HC_MAGIC_6881 (inbound peers; serves seed_index.conf -- the UPLOAD/SHARE half, durable) 1707 hc_guard_seedann(sann_ws, sann_cnt) // DHT announcer: periodically announce_peer every registered info_hash -> discoverable (durable) 1708 hc_guard_mp(mp_ws, mp_cnt) // multiplayer game relay :HC_MAGIC_7702 (/mineworld-mp + /pstate + /roster -- shared-world games) 1709 hc_guard_health(he_ws, he_cnt) // CONTINUOUS M&E: nx_health_eval loop -- functional/quality health of the whole surface every 300s (kills fire-and-pray). ADDITIVE: nothing above changes. 1710 hc_guard_sig2(sg_ws, sg_cnt) // video signaling/media relay :HC_MAGIC_8445 (task #26 closed: supervised + reconcile-cycled). ADDITIVE. 1711 hc_emit_status_html(sys_now_realtime_sec()) // LIVE nishifamily.com/status.html -- refreshed every poll 1712 let snap_now: i64 = sys_now_realtime_ms() // real windowed rwin per service (guard cnt + 60s health-reset) 1713 hc_emit_snapshot(hc_rwin(s_cnt,s_ws,snap_now), hc_rwin(wgw_cnt,wgw_ws,snap_now), hc_rwin(gw_cnt,gw_ws,snap_now), hc_rwin(gs_cnt,gs_ws,snap_now), hc_rwin(lib_cnt,lib_ws,snap_now), hc_rwin(r_cnt,r_ws,snap_now), hc_rwin(tgw_cnt,tgw_ws,snap_now), hc_rwin(mgmt_cnt,mgmt_ws,snap_now), hc_rwin(adm_cnt,adm_ws,snap_now), hc_rwin(v_cnt,v_ws,snap_now), hc_rwin(tapi_cnt,tapi_ws,snap_now), hc_rwin(mail_cnt,mail_ws,snap_now)) // LIVE mgmt_snap.json -> /api/health + /api/services -> nishifamily.com/health dashboard (TRUE state, no false +/-) 1714 // reader handled by the forked cmd_reader_keep() (blocking-wait), NOT polled here -- polling kills it 1715 var rr: i64 = sys_wait4(0-1, reap_st, WNOHANG) // reap dead children so zombies don't pile toward RLIMIT_NPROC 1716 while rr > 0 { 1717 hc_puts(" [reap] pid=" as *u8); hc_putn(rr); hc_puts(" sig=" as *u8); hc_putn(reap_st[0]&0x7f); hc_puts(" code=" as *u8); hc_putn((reap_st[0]>>8)&0xff); hc_puts("\n" as *u8) // INSTRUMENT: why does the supervised reader exit? 1718 rr = sys_wait4(0-1, reap_st, WNOHANG) 1719 } 1720 hc_sup_touch() // heartbeat the single-supervisor lease every poll (a competing launch sees this and defers) 1721 sys_sleep_ms(HC_MAGIC_15000) 1722 } 1723 return 0 1724} 1725 1726func cmd_deploy() -> i64 { 1727 hc_puts("[hostctl] deploy: atomic-publish staged artifacts + restart (pure syscalls, no shell)\n" as *u8) 1728 sys_mkdir(HC_DOCROOT, 0x1ed) 1729 let r1: i64 = sys_renameat(HC_INDEX_NEW, HC_INDEX) 1730 let r2: i64 = sys_renameat(HC_CONF_NEW, HC_CONF) 1731 // seq1097 (2026-07-27 outage): REFUSE the elf swap unless a REAL artifact is staged. The old 1732 // unconditional live->prev rename meant a deploy with nothing staged (e.g. a retry after an 1733 // aborted-response /api/deploy that DID execute server-side) renamed live away and installed 1734 // NOTHING -- guard spins exit-127 on a missing file. stat-gate: exists AND st_size>0 (st_size @ 1735 // +48, same struct-stat offsets hc_file_age_s relies on). Fail-safe: refuse loudly, touch nothing. 1736 let stb: *u8 = sys_mmap(160) 1737 var stok: i64 = 0 1738 if sys_fstatat(HC_ELF_NEW, stb) == 0 { 1739 let szp: *i64 = ((stb as i64) + 48) as *i64 1740 if szp[0] > 0 { stok = 1 } 1741 } 1742 if stok == 0 { 1743 hc_puts(" REFUSED elf swap: sites.elf.new absent or 0 bytes -- live binary left untouched\n" as *u8) 1744 return 1 1745 } 1746 sys_renameat(HC_ELF, HC_ELF_PREV) // never-brick: keep the current live binary as .prev for rollback 1747 let r3: i64 = sys_renameat(HC_ELF_NEW, HC_ELF) 1748 nx_chmod(HC_ELF, 0x1ed) 1749 // REAL return codes, NOT (rX & 1). rename returns 0 on success and -errno on failure, and ENOENT is -2 1750 // whose low bit is 0 -- so the masked form printed EVERY EVEN ERRNO AS SUCCESS. That is exactly how a 1751 // promote that installed NOTHING reported `rc=0` in cmd_mgmtdeploy on 2026-07-30, and it was caught only 1752 // by comparing artifact bytes. Print what the kernel actually said. 1753 hc_puts(" rename rc index=" as *u8); hc_putn(r1) 1754 hc_puts(" conf=" as *u8); hc_putn(r2) 1755 hc_puts(" elf=" as *u8); hc_putn(r3) 1756 hc_puts(" (0=ok, negative=-errno, NOT bit-masked)\n" as *u8) 1757 // ROBUST name-kill (matches cmd_wikideploy/galxdeploy): the pidfile is only written when the supervisor 1758 // ITSELF restarts sites.elf (hc_guard_one write_pid=1). An ADOPTED sites.elf (running before the supervisor 1759 // came up) leaves the pidfile stale -> the old pidfile-kill could MISS the restart (new binary never loads) 1760 // or SIGKILL a reused PID. Kill by name so the supervisor's name-guard respawns the NEW binary deterministically. 1761 let pk: i64 = proc_kill_by_name("sites.elf" as *u8, 9) 1762 hc_puts(" killed sites.elf procs=" as *u8); hc_putn(pk); hc_puts(" -> supervisor respawns the NEW binary (name-guarded restart, <=15s)\n" as *u8) 1763 return 0 1764} 1765 1766// mgmtdeploy: promote nx_mgmt_api.elf.new -> live (.prev kept, never-brick) + kill by name -> hc_guard_mgmt 1767// respawns the NEW binary (<=15s). Closes the publish-from-anywhere bootstrap: /api/upload stages the mgmt 1768// binary itself, /api/deploy (conf row `mgmtapi` -> this sub) promotes it -- after which ALL future mgmt 1769// updates AND static-content publishes flow over the authenticated public edge, no SSH/LAN. 1770func cmd_mgmtdeploy() -> i64 { 1771 hc_puts("[hostctl] mgmtdeploy: promote the staged mgmt binary -> live (.prev kept) + kick -> guard respawns\n" as *u8) 1772 // ---- REFUSE BEFORE DESTROY (2026-07-30). THREE COMPOUNDING DEFECTS LIVED IN THESE FIVE LINES: 1773 // (1) the live -> .prev rename ran UNCONDITIONALLY, BEFORE anything checked a staged binary existed. With 1774 // no .new present that MOVES THE LIVE BINARY AWAY and the install rename then fails, leaving NO live 1775 // mgmt at all. Only the never-brick auto-rollback put it back -- MEASURED TODAY: live and .prev both 1776 // ended at 578789 while the real 578865 build sat on disk unused. 1777 // (2) the rc was printed as (rr & 1). rename returns 0 on success and -errno on failure, and ENOENT is 1778 // -2, whose low bit is 0 -- so THE FAILURE PRINTED rc=0, IDENTICAL TO SUCCESS. Every even errno 1779 // displayed as success, so the operator reads a green promote that never happened. 1780 // (3) /api/build stages `<t>.sov.elf.new` but this promoter only ever read `<t>.elf.new` -- the seq1525 1781 // class, staged where the promoter cannot see it. Two names for one artifact is two sources of truth. 1782 // FIX: accept EITHER staged name, and REFUSE WITHOUT TOUCHING LIVE when neither is present. 1783 let mlive: *u8 = "/volume1/homes/elderwesto/nishihost/nx_mgmt_api.elf" as *u8 1784 let mprev: *u8 = "/volume1/homes/elderwesto/nishihost/nx_mgmt_api.elf.prev" as *u8 1785 let mnew1: *u8 = "/volume1/homes/elderwesto/nishihost/nx_mgmt_api.elf.new" as *u8 1786 let mnew2: *u8 = "/volume1/homes/elderwesto/nishihost/nx_mgmt_api.sov.elf.new" as *u8 1787 var stg: *u8 = 0 as *u8 1788 // ★PRECEDENCE BUG FIXED 2026-07-30 -- THIS IS HOW A "SUCCESSFUL" DEPLOY SHIPS A STALE BINARY. 1789 // This preferred mnew1 (.elf.new, the legacy UPLOAD name) whenever it existed, so ONE stale leftover 1790 // PERMANENTLY SHADOWED every fresh /api/build artifact. MEASURED: both names sat on disk; /api/deploy 1791 // answered PROMOTED and hc_promote_to genuinely promoted -- the STALE one -- while nx_mgmt_api.sov.elf.new 1792 // (581942B, built minutes earlier) was left untouched. Nothing lied; the wrong artifact was chosen. The 1793 // leftover persists BY DESIGN because hc_promote_to COPIES rather than renames (EXDEV safety), so nothing 1794 // ever consumed it. Prefer the sanctioned build artifact, and consume BOTH on success (below) so a 1795 // leftover can never shadow a build again. Rollback is unaffected -- it lives in .prev, not in .new. 1796 if hc_exists(mnew2) == 1 { stg = mnew2 } 1797 if stg == (0 as *u8) { if hc_exists(mnew1) == 1 { stg = mnew1 } } 1798 if stg == (0 as *u8) { 1799 hc_puts(" REFUSED: no staged binary -- looked for BOTH nx_mgmt_api.elf.new and nx_mgmt_api.sov.elf.new.\n" as *u8) 1800 hc_puts(" LIVE IS UNTOUCHED. The old code would have moved it to .prev and left the host with nothing.\n" as *u8) 1801 return 1 1802 } 1803 // USE THE SHARED PROMOTER, DO NOT HAND-ROLL ANOTHER ONE. This exact defect has now been found and fixed 1804 // THREE TIMES in three functions -- hc_promote_to first, then cmd_selfswap at seq992 after its own outage, 1805 // then here. hc_promote_to is also STRICTLY SAFER than a local renameat: it COPIES, because sys_renameat 1806 // across btrfs subvolumes returns EXDEV, which once moved a live binary to .prev and installed NOTHING. 1807 // A fourth site that hand-rolls this will rediscover the same outage; call this instead. 1808 let ok: i64 = hc_promote_to(stg, mlive, mprev) 1809 if ok == 0 { 1810 hc_puts(" INSTALL FAILED -- hc_promote_to reported no-op/short-write; live left as it was.\n" as *u8) 1811 return 1 1812 } 1813 hc_puts(" installed via hc_promote_to (EXDEV-safe copy, .prev banked, refuses when nothing is staged)\n" as *u8) 1814 // CONSUME BOTH STAGED NAMES. hc_promote_to COPIES, so without this the source survives and the next 1815 // deploy re-promotes an artifact that has already shipped -- which is exactly the shadowing bug above. 1816 // Safe: these are SPENT staging files, the live binary is installed and .prev holds the rollback copy. 1817 // Unlinking both (not just the one chosen) is the point -- leaving the loser is what created the trap. 1818 sys_unlinkat(mnew2) 1819 sys_unlinkat(mnew1) 1820 hc_puts(" consumed staged artifacts (both names) -- a leftover can no longer shadow the next build\n" as *u8) 1821 // ---- R5 SEQUENCING: LET MGMT FINISH ITS REPLY BEFORE WE KILL IT ------------------------------------ 1822 // THE DEFECT THIS REMOVES: this sub is invoked BY the mgmt daemon (POST /api/deploy target=mgmtapi -> 1823 // md_exec_hostctl -> here), so SIGKILLing nx_mgmt_api.elf right now destroys the process that is still 1824 // writing the caller's HTTP response. That is the FETCH-FAIL every /api/deploy has ever returned (~14x in 1825 // one session) -- and we built DOCTRINE around it ("503 = EXPECTED, do NOT retry-hammer") instead of 1826 // fixing it. It is also why the seq1563 deploy lease strands: mgmt dies before reaching its own release, 1827 // so the lane stays closed until the TTL and the NEXT deploy is refused as lease-busy. 1828 // ⚠ THE KILLER IS A DIFFERENT PROCESS: hostctl is forked by mgmt, so mgmt is not "self" from here and the 1829 // md_kill_by_name self-guard cannot fire. The delay has to live at the site that actually kills. 1830 // FIX: detach a child that waits, THEN signals. hostctl returns immediately, mgmt finishes and flushes its 1831 // response, and only then does it go -- guard respawns the already-promoted binary. SIGTERM not SIGKILL so 1832 // a daemon that later grows a drain handler gets to use it; the guard still respawns either way. 1833 let dpid: i64 = sys_fork() 1834 if dpid == 0 { 1835 nx_setsid() 1836 sys_sleep_ms(1500) 1837 proc_kill_by_name("nx_mgmt_api.elf" as *u8, 15) 1838 sys_exit(0) 1839 } 1840 hc_puts(" scheduled SIGTERM of nx_mgmt_api.elf in 1500ms (reply-flush window) -> hc_guard_mgmt respawns the NEW binary (<=15s)\n" as *u8) 1841 return 0 1842} 1843// never-brick mgmt ROLLBACK: restore nx_mgmt_api.elf.prev -> live + kick (reverse a bad mgmtdeploy). 1844func cmd_mgmtrollback() -> i64 { 1845 hc_puts("[hostctl] mgmtrollback: restore nx_mgmt_api.elf.prev -> live + kick\n" as *u8) 1846 let rr: i64 = sys_renameat("/volume1/homes/elderwesto/nishihost/nx_mgmt_api.elf.prev" as *u8, "/volume1/homes/elderwesto/nishihost/nx_mgmt_api.elf" as *u8) 1847 nx_chmod("/volume1/homes/elderwesto/nishihost/nx_mgmt_api.elf" as *u8, 0x1ed) 1848 hc_put_rc(" restore rename rc=" as *u8, rr) 1849 let pk: i64 = proc_kill_by_name("nx_mgmt_api.elf" as *u8, 9) 1850 hc_puts(" killed nx_mgmt_api.elf procs=" as *u8); hc_putn(pk); hc_puts(" -> guard respawns the RESTORED binary\n" as *u8) 1851 return 0 1852} 1853 1854const HC_COPYBUF: i64 = 262144 // chunked-copy buffer for cross-subvolume promote (hc_copy_file) 1855// ---- P1 off-LAN parity: promote/rollback the TORRENT stack (lives in /volume1/ai/torrent/, a DIFFERENT dir 1856// than the nishihost staging area). /api/upload stages <name>.new in nishihost cwd; this cross-dir promotes it 1857// into the torrent dir (SAME volume1 => sys_renameat works). GUARDED per-binary: only promote if the staged 1858// .new exists, so deploying one torrent binary never destroys the other's live copy. .prev kept (never-brick). 1859func hc_exists(p: *u8) -> i64 { let fd: i64 = sys_openat_rd(p); if fd < 0 { return 0 } sys_close(fd); return 1 } 1860// ---- HONEST RETURN CODES (2026-07-30) ------------------------------------------------------------------ 1861// THE IDIOM THIS REPLACES, WHICH WAS AT 14 SITES IN THIS FILE: `b[0] = (48 + (rc & 1))` printed only the LOW 1862// BIT of a syscall result. rename/unlink return 0 on success and -errno on failure, and ENOENT is -2 -- low 1863// bit 0 -- so EVERY EVEN ERRNO RENDERED AS `0`, INDISTINGUISHABLE FROM SUCCESS. That is precisely how 1864// cmd_mgmtdeploy reported `install rename rc=0` while installing NOTHING and briefly leaving the host with no 1865// mgmt binary; it was caught only by comparing artifact BYTES, never by the message. 1866// ★★★★★LAW: A SUCCESS MESSAGE DERIVED FROM A MASKED ERROR CODE IS WORSE THAN NO MESSAGE -- it does not 1867// merely fail to inform, it actively asserts the opposite of what happened. One helper, so no site can get 1868// this wrong again (the same discipline as binding SIGPIPE and FD_CLOEXEC to the listen primitive). 1869func hc_put_rc(label: *u8, rc: i64) -> i64 { 1870 hc_puts(label) 1871 hc_putn(rc) 1872 hc_puts(" (0=ok, negative=-errno, NOT bit-masked)\n" as *u8) 1873 return 0 1874} 1875// chunked copy src -> dst (0777); 0 = clean full copy, -1 = short write. REQUIRED for the torrent promote: 1876// /volume1/ai/torrent is a DIFFERENT btrfs subvolume than /volume1/homes (nishihost), so sys_renameat ACROSS 1877// them returns EXDEV. The old hc_promote_to swallowed that (rename live->prev same-dir OK, then rename 1878// new->live cross-subvol EXDEV FAILED) -> it moved the live binary to .prev and installed NOTHING -> the 1879// daemon could not respawn (missing binary) = a self-inflicted outage. Copy is cross-subvolume-safe. 1880func hc_copy_file(src: *u8, dst: *u8) -> i64 { 1881 let sf: i64 = sys_openat_rd(src); if sf < 0 { return 0 - 1 } 1882 let df: i64 = sys_openat_wr(dst, 0x1ff); if df < 0 { sys_close(sf); return 0 - 1 } 1883 let buf: *u8 = sys_mmap(HC_COPYBUF); var bad: i64 = 0; var go: i64 = 1 1884 while go == 1 { 1885 let r: i64 = sys_read(sf, buf, HC_COPYBUF) 1886 if r <= 0 { go = 0 } else { let w: i64 = sys_write(df, buf, r); if w != r { bad = 1; go = 0 } } 1887 } 1888 sys_close(sf); sys_close(df) 1889 return 0 - bad 1890} 1891// Byte length of a path, or -1 if unreadable. Streamed, so an arbitrarily large elf is never held whole. 1892func hc_file_size(p: *u8) -> i64 { 1893 let fd: i64 = sys_openat_rd(p) 1894 if fd < 0 { return 0 - 1 } 1895 let sb: *u8 = sys_mmap(HC_COPYBUF) 1896 var tot: i64 = 0 1897 var go: i64 = 1 1898 while go == 1 { 1899 let r: i64 = sys_read(fd, sb, HC_COPYBUF) 1900 if r <= 0 { go = 0 } 1901 if r > 0 { tot = tot + r } 1902 } 1903 sys_close(fd) 1904 return tot 1905} 1906// ---- EXPECTED-IDENTITY PROMOTE (2026-07-30) -- deploy a DIGEST, not a TAG ------------------------------- 1907// THE RACE THIS CLOSES, MEASURED THE SAME DAY: nx_sov_build_run writes _build/<t>.sov.elf and a LATER step 1908// copies that PATH and promotes it. Any sibling rebuilding the same target in between OVERWRITES the file. 1909// I built nx_mgmt_api at 578865, verified it, and by promote time _build held a sibling's 579263 -- WHICH IS 1910// WHAT WENT LIVE. I verified one artifact and shipped another and nothing detected it. mgmt then walked 1911// 578789 -> 578865 -> 579263 -> 580657 inside one hour, so this is the normal condition, not a freak. 1912// A PATH IS A PROMISE ABOUT A LOCATION, NOT ABOUT CONTENT. The `build-<target>` lease does not help: it is 1913// released when the COMPILE ends, so it locks the wrong interval -- the hazard lives AFTER it. 1914// expect_size > 0 makes the caller state WHICH artifact it verified; a mismatch REFUSES with both numbers 1915// instead of silently shipping bytes nobody tested. 0 keeps the old behaviour so every existing caller is 1916// unchanged and this is purely additive. 1917// ⚠SIZE IS A WEAKER CHECK THAN A CONTENT HASH -- a same-size rewrite passes (the seq1477 law). It is used 1918// here because it needs NO new import in a file 52 organs depend on, and it catches the measured failure. 1919// The strong check is `nx_artifact_id verify <path> <size> <fnv>` run by the caller before invoking this. 1920func hc_promote_to_expect(nhnew: *u8, live: *u8, prev: *u8, expect_size: i64) -> i64 { 1921 if hc_exists(nhnew) == 0 { return 0 } 1922 let asz: i64 = hc_file_size(nhnew) 1923 if expect_size > 0 { 1924 if asz != expect_size { 1925 hc_puts(" REFUSED: the staged artifact is NOT the one you built -- expected bytes=" as *u8) 1926 hc_putn(expect_size) 1927 hc_puts(" actual bytes=" as *u8); hc_putn(asz); hc_puts("\n" as *u8) 1928 hc_puts(" A sibling almost certainly rebuilt this target between your build and this promote.\n" as *u8) 1929 hc_puts(" LIVE IS UNTOUCHED. Rebuild, re-read the size, and promote the artifact you verified.\n" as *u8) 1930 return 0 1931 } 1932 } 1933 sys_renameat(live, prev) // keep current live as .prev (same-dir rename; a running proc keeps its old inode) 1934 let rr: i64 = hc_copy_file(nhnew, live) // promote staged .new -> live via COPY (cross-subvolume EXDEV-safe; sys_renameat here WAS the outage) 1935 nx_chmod(live, 0x1ed) 1936 if rr == 0 { 1937 // A RECEIPT ON EVERY PROMOTE, not only when asked: a deploy verb that cannot name what it deployed 1938 // is not a receipt, and a lost response then reads as a stale binary. 1939 hc_puts(" promoted bytes=" as *u8); hc_putn(asz); hc_puts(" (verify with: nx_artifact_id.elf id <live-path>)\n" as *u8) 1940 return 1 1941 } 1942 return 0 1943} 1944func hc_promote_to(nhnew: *u8, live: *u8, prev: *u8) -> i64 { 1945 return hc_promote_to_expect(nhnew, live, prev, 0) 1946} 1947// ---- GENERIC DEPLOY (R2 of the SOTA rung plan) --------------------------------------------------- 1948// THE DEFECT THIS RETIRES: cmd_deploy() takes NO PARAMETERS. It is hardcoded to the HC_ELF constants 1949// and to proc_kill_by_name("sites.elf") -- a GENERIC NAME on a SINGLE-SERVICE deployer. That is why 1950// every new deployable needed its own function, and the cost was counted 2026-07-31: 1951// 15 cmd_*deploy functions + 12 cmd_*rollback twins = 27 near-duplicate copies of ONE pattern. 1952// 1953// THE REAL COST IS NOT DUPLICATION, IT IS DIVERGENCE. Four hard-won lessons live in cmd_mgmtdeploy -- 1954// refuse-before-destroy, print the REAL rc, accept EITHER staged name, consume BOTH on success -- and 1955// cmd_deploy has only the first. Each lesson had to be re-applied 27 times and demonstrably was not. 1956// ★★★★★★ EVERY COPY OF A PATTERN IS A PLACE A FIX CAN FAIL TO ARRIVE. 1957// 1958// With this, adding a deployable is a ROW in deploy_targets.conf naming sub=gdeploy plus the live path 1959// and process name -- a DATA edit, not new code. That is the whole of R2. 1960 1961// Build <live><suffix> into dst. Returns dst for call-site brevity. 1962func hc_pathsuf(dst: *u8, live: *u8, suf: *u8) -> *u8 { 1963 var o: i64 = hc_cat(dst, 0, live) 1964 o = hc_cat(dst, o, suf) 1965 dst[o] = 0 as u8 1966 return dst 1967} 1968 1969// Derive the SECOND staged name: /path/nx_foo.elf -> /path/nx_foo.sov.elf.new 1970// /api/build stages <target>.sov.elf.new while /api/upload stages <artifact>.new. TWO NAMES FOR ONE 1971// ARTIFACT IS TWO SOURCES OF TRUTH, and a promoter that reads only one silently ignores the other -- 1972// the seq1525 class, staged where the promoter cannot see it. 1973func hc_sovname(dst: *u8, live: *u8) -> *u8 { 1974 var n: i64 = 0 1975 while live[n] != (0 as u8) { n = n + 1 } 1976 // strip a trailing ".elf" if present, then append ".sov.elf.new" 1977 var cut: i64 = n 1978 if n > 4 { 1979 if live[n-4] == (46 as u8) { if live[n-3] == (101 as u8) { if live[n-2] == (108 as u8) { if live[n-1] == (102 as u8) { cut = n - 4 } } } } 1980 } 1981 var i: i64 = 0 1982 while i < cut { dst[i] = live[i]; i = i + 1 } 1983 var o: i64 = hc_cat(dst, cut, ".sov.elf.new" as *u8) 1984 dst[o] = 0 as u8 1985 return dst 1986} 1987 1988// Promote a staged artifact over <live> and restart <killname>. ALL FOUR LESSONS IN ONE PLACE. 1989func hc_deploy_one(live: *u8, killname: *u8) -> i64 { 1990 let bufA: *u8 = sys_mmap(512) 1991 let bufB: *u8 = sys_mmap(512) 1992 let bufP: *u8 = sys_mmap(512) 1993 let new1: *u8 = hc_pathsuf(bufA, live, ".new" as *u8) 1994 let new2: *u8 = hc_sovname(bufB, live) 1995 let prev: *u8 = hc_pathsuf(bufP, live, ".prev" as *u8) 1996 1997 // LESSON 3 + PRECEDENCE: prefer the SANCTIONED BUILD artifact (.sov.elf.new). Preferring the legacy 1998 // upload name let ONE stale leftover permanently shadow every fresh build -- a promote that reported 1999 // success and shipped the wrong binary. Nothing lied; the wrong artifact was chosen. 2000 var stg: *u8 = 0 as *u8 2001 if hc_exists(new2) == 1 { stg = new2 } 2002 if stg == (0 as *u8) { if hc_exists(new1) == 1 { stg = new1 } } 2003 2004 // LESSON 1 -- REFUSE BEFORE DESTROY. The original unconditional live->prev rename moved the live 2005 // binary away BEFORE checking anything was staged, leaving the host with NOTHING when the install 2006 // then failed. Refuse loudly and touch nothing. 2007 if stg == (0 as *u8) { 2008 hc_puts(" REFUSED: nothing staged for " as *u8); hc_puts(live) 2009 hc_puts(" (looked for BOTH .new and .sov.elf.new) -- LIVE UNTOUCHED\n" as *u8) 2010 return 1 2011 } 2012 2013 // hc_promote_to banks .prev, COPIES (EXDEV-safe across subvolumes), and chmods 0755. 2014 // ⚠⚠ IT RETURNS 1 ON SUCCESS AND 0 ON FAILURE -- the INVERSE of the syscall convention used three 2015 // lines below it. The first version of this function assumed 0=ok and therefore printed 2016 // "PROMOTE FAILED" over a promote that had DEMONSTRABLY SUCCEEDED (live=NEW, .prev=OLD on disk), 2017 // then skipped the consume and the restart. Caught by a sandbox positive-path test, not by review. 2018 // ★★★★★★ A RETURN CONVENTION IS PART OF AN API -- READ IT, NEVER INFER IT FROM THE NEIGHBOURS. 2019 // This file mixes both conventions, which is exactly why the mistake is available to make. 2020 let ok: i64 = hc_promote_to(stg, live, prev) 2021 hc_puts(" promote ok=" as *u8); hc_putn(ok); hc_puts(" (hc_promote_to: 1=SUCCESS, 0=failure)\n" as *u8) 2022 if ok != 1 { 2023 hc_puts(" PROMOTE FAILED -- live left as-is, .prev intact\n" as *u8) 2024 return 1 2025 } 2026 2027 // LESSON 4 -- consume BOTH staged names. hc_promote_to copies rather than renames, so nothing 2028 // consumes the source; a leftover would shadow the next build forever. 2029 sys_unlinkat(new1) 2030 sys_unlinkat(new2) 2031 2032 let pk: i64 = proc_kill_by_name(killname, 9) 2033 hc_puts(" killed " as *u8); hc_puts(killname) 2034 hc_puts(" procs=" as *u8); hc_putn(pk) 2035 hc_puts(" -> guard respawns the NEW binary (name-guarded restart, <=15s)\n" as *u8) 2036 return 0 2037} 2038 2039// Does s start with p? Needed for the gdeploy@<row> form; hc_streq is exact-match only. 2040func hc_prefix(s: *u8, p: *u8) -> i64 { 2041 var i: i64 = 0 2042 while p[i] != (0 as u8) { 2043 if s[i] == (0 as u8) { return 0 } 2044 if s[i] != p[i] { return 0 } 2045 i = i + 1 2046 } 2047 return 1 2048} 2049 2050// Read field `want` (0-based) of the deploy_targets.conf row whose FIRST field == rowname, into out. 2051// Returns 1 if found. ONE SOURCE OF TRUTH ON PURPOSE: the paths live in the SAME row /api/deploy already 2052// reads, not a second file. A second mapping file would be a second thing to forget to update, which is 2053// the declared-vs-real divergence this whole arc is about. 2054func hc_row_field(rowname: *u8, want: i64, out: *u8) -> i64 { 2055 let lp: *i64 = sys_mmap(16) as *i64 2056 lp[0] = 0 2057 let b: *u8 = sys_read_file("deploy_targets.conf" as *u8, lp) 2058 let n: i64 = lp[0] 2059 if n <= 0 { return 0 } 2060 var line: i64 = 0 2061 while line < n { 2062 var eol: i64 = line 2063 var es: i64 = 0 2064 while es == 0 { if eol >= n { es = 1 } else { if b[eol] == (10 as u8) { es = 1 } else { eol = eol + 1 } } } 2065 var skip: i64 = 0 2066 if line < eol { if b[line] == (35 as u8) { skip = 1 } } 2067 if skip == 0 { 2068 // field 0 must equal rowname 2069 var p: i64 = line 2070 var fe: i64 = 0 2071 while fe == 0 { if p < eol { if b[p] == (32 as u8) { fe = 1 } else { p = p + 1 } } else { fe = 1 } } 2072 var hit: i64 = 1 2073 var k: i64 = 0 2074 while rowname[k] != (0 as u8) { 2075 if line + k >= p { hit = 0; k = k + 1 } else { 2076 if b[line + k] != rowname[k] { hit = 0 } 2077 k = k + 1 2078 } 2079 } 2080 if p - line != k { hit = 0 } 2081 if hit == 1 { 2082 var f: i64 = 1 2083 var q: i64 = p 2084 var done: i64 = 0 2085 while done == 0 { 2086 var ws: i64 = 0 2087 while ws == 0 { if q < eol { if b[q] == (32 as u8) { q = q + 1 } else { ws = 1 } } else { ws = 1 } } 2088 if q >= eol { done = 1 } else { 2089 let fs: i64 = q 2090 var f2: i64 = 0 2091 while f2 == 0 { if q < eol { if b[q] == (32 as u8) { f2 = 1 } else { q = q + 1 } } else { f2 = 1 } } 2092 if f == want { 2093 var z: i64 = 0 2094 while fs + z < q { out[z] = b[fs + z]; z = z + 1 } 2095 out[z] = 0 as u8 2096 return 1 2097 } 2098 f = f + 1 2099 } 2100 } 2101 return 0 2102 } 2103 } 2104 line = eol + 1 2105 } 2106 return 0 2107} 2108 2109// gdeploy <live-path> <kill-name> -- the data-driven deploy verb. 2110func cmd_gdeploy(live: *u8, killname: *u8) -> i64 { 2111 hc_puts("[hostctl] gdeploy: generic atomic promote + name-guarded restart\n" as *u8) 2112 hc_puts(" live=" as *u8); hc_puts(live); hc_puts(" kill=" as *u8); hc_puts(killname); hc_puts("\n" as *u8) 2113 return hc_deploy_one(live, killname) 2114} 2115 2116// ---- TOOLCHAIN DEPLOY (seq891) ------------------------------------------------------------------- 2117// THE GAP THIS CLOSES: the ecosystem could build and deploy every SERVICE over its own API but could 2118// NOT update the COMPILER that builds them. /api/upload gates on a service-basename allowlist and 2119// /api/build stages into nishihost/, with no allowlisted route into buildroot/_offc -- so a PROVEN 2120// compiler fix could not be landed API-first (rule 27) and needed on-LAN/ssh. 2121// 2122// NEVER-BRICK BY CONSTRUCTION: this reuses hc_promote_to, the same primitive the torrent and gallery 2123// cross-dir promotes already use. It no-ops when nothing is staged, banks the current binary as .prev, 2124// COPIES rather than renames (cross-subvolume EXDEV is what caused a real outage here once), and 2125// chmods 0755 so the promoted compiler stays EXECUTABLE -- the detail that makes the tempting 2126// /api/unpack shortcut unsafe (nx_treepack writes 0644 and unlinks; see debt seq903). 2127// 2128// Each binary is INDEPENDENT: a missing stage is a no-op, not a failure, so a partial ship can never 2129// half-install a toolchain. The toolchain is NOT a daemon -- there is nothing to kill or respawn, and 2130// the next /api/build simply picks up the new binary. Verify a landing by BEHAVIOUR (build a probe), 2131// never by the promote returning 1. 2132const HC_TC_CC: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_cc_sovereign.elf" as *u8 2133const HC_TC_CC_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/runtime/nx_cc_sovereign.elf.new" as *u8 2134const HC_TC_CC_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_cc_sovereign.elf.prev" as *u8 2135const HC_TC_CC_BAD: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_cc_sovereign.elf.bad" as *u8 2136const HC_TC_ASM: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nxasm_x86_main.elf" as *u8 2137const HC_TC_ASM_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/runtime/nxasm_x86_main.elf.new" as *u8 2138const HC_TC_ASM_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nxasm_x86_main.elf.prev" as *u8 2139const HC_TC_ASM_BAD: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nxasm_x86_main.elf.bad" as *u8 2140const HC_TC_SBR: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_sov_build_run.elf" as *u8 2141const HC_TC_SBR_NEW: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/runtime/nx_sov_build_run.elf.new" as *u8 2142const HC_TC_SBR_PREV: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_sov_build_run.elf.prev" as *u8 2143const HC_TC_SBR_BAD: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_sov_build_run.elf.bad" as *u8 2144func cmd_toolchaindeploy() -> i64 { 2145 hc_puts("[hostctl] toolchaindeploy: promote staged toolchain -> buildroot/_offc/ (.prev kept, chmod 0755)\n" as *u8) 2146 let c: i64 = hc_promote_to(HC_TC_CC_NEW, HC_TC_CC, HC_TC_CC_PREV) 2147 let a: i64 = hc_promote_to(HC_TC_ASM_NEW, HC_TC_ASM, HC_TC_ASM_PREV) 2148 let b: i64 = hc_promote_to(HC_TC_SBR_NEW, HC_TC_SBR, HC_TC_SBR_PREV) 2149 hc_puts("[hostctl] toolchaindeploy: cc=" as *u8); hc_putn(c) 2150 hc_puts(" nxasm=" as *u8); hc_putn(a) 2151 hc_puts(" sovbuildrun=" as *u8); hc_putn(b) 2152 hc_puts(" (1=promoted, 0=nothing staged) -- VERIFY BY BUILDING A PROBE, not by these counts\n" as *u8) 2153 return 0 2154} 2155func cmd_toolchainrollback() -> i64 { 2156 hc_puts("[hostctl] toolchainrollback: restore the .prev toolchain binaries\n" as *u8) 2157 let c: i64 = hc_promote_to(HC_TC_CC_PREV, HC_TC_CC, HC_TC_CC_BAD) 2158 let a: i64 = hc_promote_to(HC_TC_ASM_PREV, HC_TC_ASM, HC_TC_ASM_BAD) 2159 let b: i64 = hc_promote_to(HC_TC_SBR_PREV, HC_TC_SBR, HC_TC_SBR_BAD) 2160 hc_puts("[hostctl] toolchainrollback: cc=" as *u8); hc_putn(c) 2161 hc_puts(" nxasm=" as *u8); hc_putn(a) 2162 hc_puts(" sovbuildrun=" as *u8); hc_putn(b) 2163 hc_puts("\n" as *u8) 2164 return 0 2165} 2166func cmd_torrentdeploy() -> i64 { 2167 hc_puts("[hostctl] torrentdeploy: promote staged torrent binaries -> /volume1/ai/torrent/ (.prev kept) + kicktorrent\n" as *u8) 2168 let d: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_torrent_daemon.sov.elf.new" as *u8, "/volume1/ai/torrent/nx_torrent_daemon.sov.elf" as *u8, "/volume1/ai/torrent/nx_torrent_daemon.sov.elf.prev" as *u8) 2169 let s: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_torrent_seedeval.elf.new" as *u8, "/volume1/ai/torrent/nx_torrent_seedeval.elf" as *u8, "/volume1/ai/torrent/nx_torrent_seedeval.elf.prev" as *u8) 2170 // ★THE SEEDER ITSELF (2026-07-30) -- the one torrent binary in this directory with NO promotion line. 2171 // torrentdeploy promoted daemon, seedeval, worker, mp4faststart, extract and vidindex, but never 2172 // nx_torrent_seed, so a seeder fix could not ship by ANY sanctioned route: /api/promote is refused now 2173 // that it is correctly declared a daemon, and /api/deploy had nothing to call. This ONE LINE is the 2174 // whole adoption gap -- the primitives (hc_promote_to), the guard (restarts it when dead) and the 2175 // restart verb (kickseed) all already existed and were simply unreachable for this artefact. 2176 // ⚠NAME TRAP: the siblings promote to `<name>.sov.elf`, but HC_SEED_CMD execs `./nx_torrent_seed.elf` 2177 // and the guard matches that exact name -- so the LIVE target is `.elf`, NOT `.sov.elf`. Promoting to 2178 // the sibling-style name would place a binary nothing ever runs and read as a successful deploy. 2179 // hc_promote_to is guarded on .new existence, so this is a NO-OP whenever no seeder is staged. 2180 let sd: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_torrent_seed.sov.elf.new" as *u8, "/volume1/ai/torrent/nx_torrent_seed.elf" as *u8, "/volume1/ai/torrent/nx_torrent_seed.elf.prev" as *u8) 2181 // WORKER (nx_torrent_get.sov.elf) parity: it lives in /volume1/ai/torrent/ as a daemon SIBLING (d_self_dir 2182 // execs it there) but had NO API deploy path -- so a worker fix (e.g. the "IGNORING: seeders unchoke us, 2183 // have=0" data-timeout fix) could not ship without shell. Built by /api/build -> nishihost/<name>.sov.elf.new, 2184 // cross-dir promoted here (same volume1 => rename), .prev kept (never-brick). Guarded on .new existence, so a 2185 // daemon-only or seedeval-only deploy never touches the live worker. Running workers double-fork (reparent to 2186 // init) so they survive; NEW spawns use the new elf -> pause/resume a stuck torrent to force it onto the fix. 2187 let w: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_torrent_get.sov.elf.new" as *u8, "/volume1/ai/torrent/nx_torrent_get.sov.elf" as *u8, "/volume1/ai/torrent/nx_torrent_get.sov.elf.prev" as *u8) 2188 // nx_mp4_faststart: daemon sibling (self-dir) the /stream handler execve's to relocate moov to the front of a 2189 // non-faststart mp4 so a browser <video> can play it. Same cross-subvolume copy-promote as the worker. 2190 let mf: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_mp4_faststart.sov.elf.new" as *u8, "/volume1/ai/torrent/nx_mp4_faststart.sov.elf" as *u8, "/volume1/ai/torrent/nx_mp4_faststart.sov.elf.prev" as *u8) 2191 // GALLERY BRIDGE (2026-07-24): EXTRACTOR (download.part -> real .mp4 on complete) + fixed NAS-path INDEXER 2192 // (scans /volume1/ai/torrent -> gallery catalog). Worker-spawned siblings; absent-.new promotes = no-ops (:1759). 2193 let ex: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_torrent_extract.sov.elf.new" as *u8, "/volume1/ai/torrent/nx_torrent_extract.sov.elf" as *u8, "/volume1/ai/torrent/nx_torrent_extract.sov.elf.prev" as *u8) 2194 let gv: i64 = hc_promote_to("/volume1/homes/elderwesto/nishihost/nx_galx_vidindex.sov.elf.new" as *u8, "/volume1/ai/torrent/nx_galx_vidindex.sov.elf" as *u8, "/volume1/ai/torrent/nx_galx_vidindex.sov.elf.prev" as *u8) 2195 hc_puts(" promoted daemon=" as *u8); hc_putn(d); hc_puts(" seedeval=" as *u8); hc_putn(s); hc_puts(" SEEDER=" as *u8); hc_putn(sd); hc_puts(" worker=" as *u8); hc_putn(w); hc_puts(" mp4faststart=" as *u8); hc_putn(mf); hc_puts(" extract=" as *u8); hc_putn(ex); hc_puts(" vidindex=" as *u8); hc_putn(gv); hc_puts("\n" as *u8) 2196 // 2026-07-24: daemon auto-kill REMOVED here -- torrentdeploy now only PLACES binaries (incl. the new 2197 // gallery-bridge extractor + indexer siblings); restart the daemon EXPLICITLY via kicktorrent only when 2198 // the daemon binary itself changed, so placing sibling tools never forces a needless daemon restart. 2199 hc_puts(" (daemon not auto-restarted; run kicktorrent explicitly if the daemon binary changed)\n" as *u8) 2200 return 0 2201} 2202func cmd_torrentrollback() -> i64 { 2203 hc_puts("[hostctl] torrentrollback: restore torrent binaries .prev -> live + kicktorrent (reverse a bad torrentdeploy)\n" as *u8) 2204 let d: i64 = hc_promote_to("/volume1/ai/torrent/nx_torrent_daemon.sov.elf.prev" as *u8, "/volume1/ai/torrent/nx_torrent_daemon.sov.elf" as *u8, "/volume1/ai/torrent/nx_torrent_daemon.sov.elf.bad" as *u8) 2205 let s: i64 = hc_promote_to("/volume1/ai/torrent/nx_torrent_seedeval.elf.prev" as *u8, "/volume1/ai/torrent/nx_torrent_seedeval.elf" as *u8, "/volume1/ai/torrent/nx_torrent_seedeval.elf.bad" as *u8) 2206 // ★ROLLBACK MUST COVER EXACTLY WHAT DEPLOY PLACES. A deploy line without a matching rollback line is a 2207 // one-way door: the .prev is banked and unreachable, so a bad seeder could be shipped but not reversed. 2208 let sd: i64 = hc_promote_to("/volume1/ai/torrent/nx_torrent_seed.elf.prev" as *u8, "/volume1/ai/torrent/nx_torrent_seed.elf" as *u8, "/volume1/ai/torrent/nx_torrent_seed.elf.bad" as *u8) 2209 hc_puts(" restored daemon=" as *u8); hc_putn(d); hc_puts(" seedeval=" as *u8); hc_putn(s); hc_puts(" SEEDER=" as *u8); hc_putn(sd); hc_puts("\n" as *u8) 2210 if d == 1 { proc_kill_by_name("nx_torrent_daemon" as *u8, 9) } 2211 // Restart the seeder only if its binary actually changed, mirroring the daemon rule above. 2212 if sd == 1 { proc_kill_by_name("nx_torrent_seed.elf" as *u8, 9) } 2213 return 0 2214} 2215 2216// promote/rollback the Relationship OS daemon (:8027). Same-dir promote in nishihost cwd (hc_promote_to = keep 2217// live as .prev, mv .new -> live, chmod; guarded on .new existence -> deploying without a staged .new is a no-op). 2218func cmd_relatedeploy() -> i64 { 2219 hc_puts("[hostctl] relatedeploy: promote nx_relate_daemon.elf.new -> live (.prev kept) + restart :8027\n" as *u8) 2220 let d: i64 = hc_promote_to(HC_RELATE_NEW, HC_RELATE, HC_RELATE_PREV) 2221 hc_puts(" promoted=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2222 if d == 1 { let pk: i64 = proc_kill_by_name("nx_relate_daemon.elf" as *u8, 9); hc_puts(" killed relate procs=" as *u8); hc_putn(pk); hc_puts(" -> guard respawns the NEW :8027 daemon\n" as *u8) } 2223 return 0 2224} 2225func cmd_relaterollback() -> i64 { 2226 hc_puts("[hostctl] relaterollback: restore nx_relate_daemon.elf.prev -> live + restart (reverse a bad relatedeploy)\n" as *u8) 2227 let d: i64 = hc_promote_to(HC_RELATE_PREV, HC_RELATE, "/volume1/homes/elderwesto/nishihost/nx_relate_daemon.elf.bad" as *u8) 2228 hc_puts(" restored=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2229 if d == 1 { proc_kill_by_name("nx_relate_daemon.elf" as *u8, 9) } 2230 return 0 2231} 2232// promote/rollback the SOVEREIGN RU<->EN translate daemon (:8447, /translate). Same-dir promote in nishihost cwd 2233// (mirrors cmd_docportaldeploy). Staged binary is nx_translate_daemon.sov.elf.new (nx_ship/build-over-API output). 2234func cmd_xlatedeploy() -> i64 { 2235 hc_puts("[hostctl] xlatedeploy: promote nx_translate_daemon.sov.elf.new -> live (.prev kept) + restart :8447\n" as *u8) 2236 let d: i64 = hc_promote_to(HC_XLATE_NEW, HC_XLATE, HC_XLATE_PREV) 2237 hc_puts(" promoted=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2238 if d == 1 { let pk: i64 = proc_kill_by_name("nx_translate_daemon.elf" as *u8, HC_SIGKILL); hc_puts(" killed xlate procs=" as *u8); hc_putn(pk); hc_puts(" -> guard respawns the NEW :8447 daemon\n" as *u8) } 2239 return 0 2240} 2241func cmd_xlaterollback() -> i64 { 2242 hc_puts("[hostctl] xlaterollback: restore nx_translate_daemon.elf.prev -> live + restart (reverse a bad xlatedeploy)\n" as *u8) 2243 let d: i64 = hc_promote_to(HC_XLATE_PREV, HC_XLATE, "/volume1/homes/elderwesto/nishihost/nx_translate_daemon.elf.bad" as *u8) 2244 hc_puts(" restored=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2245 if d == 1 { proc_kill_by_name("nx_translate_daemon.elf" as *u8, HC_SIGKILL) } 2246 return 0 2247} 2248 2249// promote/rollback the ethical CLEAN-SERVE daemon (:8102). Same-dir promote in nishihost cwd (mirrors cmd_relatedeploy). 2250func cmd_cleanservedeploy() -> i64 { 2251 hc_puts("[hostctl] cleanservedeploy: promote nx_clean_serve_daemon.elf.new -> live (.prev kept) + restart :8102\n" as *u8) 2252 let d: i64 = hc_promote_to(HC_CLEANSERVE_NEW, HC_CLEANSERVE, HC_CLEANSERVE_PREV) 2253 hc_puts(" promoted=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2254 if d == 1 { let pk: i64 = proc_kill_by_name("nx_clean_serve_daemon.elf" as *u8, 9); hc_puts(" killed cleanserve procs=" as *u8); hc_putn(pk); hc_puts(" -> guard respawns the NEW :8102 daemon\n" as *u8) } 2255 return 0 2256} 2257// promote/rollback the DOCPORTAL admin daemon (:HC_MAGIC_18456, /search + /doc + /api). Same-dir promote in nishihost cwd 2258// (mirrors cmd_cleanservedeploy). The staged binary is <name>.SOV.elf.new (nx_ship/build-over-API output) -> the 2259// deploy sub consumes it directly, so /api/deploy target=docportal needs NO manual .sov rename. .prev = rollback. 2260func cmd_docportaldeploy() -> i64 { 2261 hc_puts("[hostctl] docportaldeploy: promote nx_docportal_admin_daemon.sov.elf.new -> live (.prev kept) + kick :18456\n" as *u8) 2262 let d: i64 = hc_promote_to(HC_DOCPORTAL_NEW, HC_DOCPORTAL, HC_DOCPORTAL_PREV) 2263 hc_puts(" promoted=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2264 if d == 1 { let pk: i64 = proc_kill_by_name("nx_docportal_admin_daemon.elf" as *u8, 9); hc_puts(" killed docportal procs=" as *u8); hc_putn(pk); hc_puts(" -> guard respawns the NEW :18456 daemon\n" as *u8) } 2265 return 0 2266} 2267func cmd_docportalrollback() -> i64 { 2268 hc_puts("[hostctl] docportalrollback: restore nx_docportal_admin_daemon.elf.prev -> live + kick (reverse a bad docportaldeploy)\n" as *u8) 2269 let d: i64 = hc_promote_to(HC_DOCPORTAL_PREV, HC_DOCPORTAL, "/volume1/homes/elderwesto/nishihost/nx_docportal_admin_daemon.elf.bad" as *u8) 2270 hc_puts(" restored=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2271 if d == 1 { proc_kill_by_name("nx_docportal_admin_daemon.elf" as *u8, 9) } 2272 return 0 2273} 2274func cmd_cleanserverollback() -> i64 { 2275 hc_puts("[hostctl] cleanserverollback: restore nx_clean_serve_daemon.elf.prev -> live + restart (reverse a bad cleanservedeploy)\n" as *u8) 2276 let d: i64 = hc_promote_to(HC_CLEANSERVE_PREV, HC_CLEANSERVE, "/volume1/homes/elderwesto/nishihost/nx_clean_serve_daemon.elf.bad" as *u8) 2277 hc_puts(" restored=" as *u8); hc_putn(d); hc_puts("\n" as *u8) 2278 if d == 1 { proc_kill_by_name("nx_clean_serve_daemon.elf" as *u8, 9) } 2279 return 0 2280} 2281 2282// never-brick ROLLBACK: restore the previous live sites.elf (saved by cmd_deploy as .prev) + respawn. Reverses a bad deploy. 2283func cmd_rollback() -> i64 { 2284 hc_puts("[hostctl] rollback: restore sites.elf.prev -> live + respawn (reverse a bad deploy)\n" as *u8) 2285 let rr: i64 = sys_renameat(HC_ELF_PREV, HC_ELF) 2286 nx_chmod(HC_ELF, 0x1ed) 2287 hc_put_rc(" restore rename rc=" as *u8, rr) 2288 let pk: i64 = proc_kill_by_name("sites.elf" as *u8, 9) 2289 hc_puts(" killed sites.elf procs=" as *u8); hc_putn(pk); hc_puts(" -> supervisor respawns the RESTORED binary\n" as *u8) 2290 return 0 2291} 2292 2293// CERT-ONLY deploy: backup the live cert+key as .bak, atomically install the staged .new, then kill 2294// sites.elf so the supervisor respawns the LIVE binary (which reloads the cert at startup). NO binary 2295// swap -- safe even with a broken staged sites.elf. never-brick: .bak enables cmd_certrollback. 2296func cmd_certswap() -> i64 { 2297 hc_puts("[hostctl] certswap: backup live cert -> .bak, install .new -> live, restart sites.elf (no binary swap)\n" as *u8) 2298 sys_renameat(HC_CERT_LIVE, HC_CERT_BAK) 2299 sys_renameat(HC_KEY_LIVE, HC_KEY_BAK) 2300 let r1: i64 = sys_renameat(HC_CERT_NEW, HC_CERT_LIVE) 2301 let r2: i64 = sys_renameat(HC_KEY_NEW, HC_KEY_LIVE) 2302 hc_put_rc(" install rc cert=" as *u8, r1) 2303 hc_put_rc(" install rc key=" as *u8, r2) 2304 let pk: i64 = proc_kill_by_name("sites.elf" as *u8, 9) 2305 hc_puts(" killed sites.elf procs=" as *u8); hc_putn(pk); hc_puts(" -> supervisor respawns the LIVE binary (reloads new cert, <=15s)\n" as *u8) 2306 return 0 2307} 2308 2309// never-brick cert ROLLBACK: restore the previous cert+key (.bak) + respawn. Reverses a bad certswap. 2310func cmd_certrollback() -> i64 { 2311 hc_puts("[hostctl] certrollback: restore cert/key .bak -> live + restart sites.elf\n" as *u8) 2312 sys_renameat(HC_CERT_BAK, HC_CERT_LIVE) 2313 sys_renameat(HC_KEY_BAK, HC_KEY_LIVE) 2314 let pk: i64 = proc_kill_by_name("sites.elf" as *u8, 9) 2315 hc_puts(" killed sites.elf procs=" as *u8); hc_putn(pk); hc_puts(" -> supervisor respawns (restored cert)\n" as *u8) 2316 return 0 2317} 2318 2319// spawn the live login daemon detached (mirrors hc_spawn_wiki_gw). 2320func hc_spawn_login() -> i64 { 2321 let pid: i64 = sys_fork() 2322 if pid==0 { 2323 nx_setsid() 2324 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 2325 if dn >= 0 { sys_dup3(dn,0,0); sys_dup3(dn,1,0); sys_dup3(dn,2,0) } 2326 let argv: *i64 = sys_mmap(32) as *i64 2327 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_LOGIN_CMD as i64; argv[3]=0 2328 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2329 sys_execve_clean(HC_SH, argv, envp) 2330 sys_exit(127) 2331 } 2332 return pid 2333} 2334// PRE-FLIGHT: spawn the staged login .new on throwaway :9099 (NOT :9091); print its startup. OPAQUE-LOGIN-UP = 2335// safe to logindeploy; CTX-INIT-FAIL/BIND-FAIL = bad keys/args, do NOT deploy. Then kill the test (frees :9099). 2336func cmd_logintest() -> i64 { 2337 hc_puts("=== logintest: spawn nx_opaque_login.elf.new on :9099 (live :9091 untouched) ===\n" as *u8) 2338 let pid: i64 = sys_fork() 2339 if pid==0 { 2340 nx_setsid() 2341 let argv: *i64 = sys_mmap(32) as *i64 2342 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_LOGIN_TEST_CMD as i64; argv[3]=0 2343 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2344 sys_execve_clean(HC_SH, argv, envp) 2345 sys_exit(127) 2346 } 2347 sys_sleep_ms(HC_MAGIC_1500) 2348 let lb: *u8 = sys_mmap(HC_MAGIC_8192) 2349 let n: i64 = hc_read_cmdline("/tmp/logintest.out" as *u8, lb, HC_MAGIC_8192) 2350 hc_puts("--- /tmp/logintest.out ---\n" as *u8) 2351 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(empty -- did not start?)\n" as *u8) } 2352 let k: i64 = proc_kill_by_name("nx_opaque_login.elf.new" as *u8, 9) 2353 hc_puts("\n killed test procs=" as *u8); hc_putn(k); hc_puts(" (:9099 freed)\n" as *u8) 2354 return 0 2355} 2356 2357// runmtlsproxy: SHALLOW execve (REPLACE this fresh nx_hostctl image with the proxy) -- reached ONLY via 2358// hc_dfork_exec(HC_RUNMTLSPROXY), which already fork+setsid-detached. The SECOND execve from this NON-forked 2359// main-dispatch context dodges the deep-forked-child codegen edge -- FULLY SOVEREIGN, no /bin/sh (cf. cmd_runsite). 2360// stdout/stderr -> /tmp/mtlsproxy.out so mtlsproxytest can report the cert-load + bind result. main() already chdir'd. 2361func cmd_runmtlsproxy() -> i64 { 2362 let lf: i64 = sys_openat_wr("/tmp/mtlsproxy.out" as *u8, 0x1a4) 2363 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2364 let argv: *i64 = sys_mmap(32) as *i64 2365 argv[0]=HC_MTLS_PROXY_BIN as i64; argv[1]=HC_MP_PORT as i64; argv[2]=HC_MP_BACKEND as i64; argv[3]=0 2366 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2367 sys_execve_clean(HC_MTLS_PROXY_BIN, argv, envp) 2368 sys_exit(127) 2369 return 0 2370} 2371// mtlsproxytest: sovereign-launch the mTLS proxy on :9444, print its startup log (cert load + bind), and leave it 2372// RUNNING for a browser test. Touches NOTHING live -- new port, unsupervised, no DSM/:443 change. 2373func cmd_mtlsproxytest() -> i64 { 2374 hc_puts("=== mtlsproxytest: sovereign launch nx_aw_mtls_proxy.elf :9444 -> wiki_gw :18791 (no /bin/sh) ===\n" as *u8) 2375 hc_dfork_exec(HC_RUNMTLSPROXY) 2376 sys_sleep_ms(HC_MAGIC_1500) 2377 let lb: *u8 = sys_mmap(HC_MAGIC_8192) 2378 let n: i64 = hc_read_cmdline("/tmp/mtlsproxy.out" as *u8, lb, HC_MAGIC_8192) 2379 hc_puts("--- /tmp/mtlsproxy.out ---\n" as *u8) 2380 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(empty -- did not start? cert path / bind :9444)\n" as *u8) } 2381 if proc_alive_by_name("nx_aw_mtls_proxy.elf" as *u8) == 1 { hc_puts(" nx_aw_mtls_proxy ALIVE on :9444 (sovereign, unsupervised test)\n" as *u8) } else { hc_puts(" nx_aw_mtls_proxy NOT alive -- see log above\n" as *u8) } 2382 return 0 2383} 2384 2385// SHALLOW execve targets (reached via hc_dfork_exec -> fresh main-dispatch image, dodges the codegen edge, no sh). 2386// NAS-resident Apertus/model mirror: shallow-execve the download organ (absolute /volume1 paths -> CWD-independent), 2387// log to /tmp/apertus_mirror.out. Reached ONLY via hc_dfork_exec(HC_RUNMIRROR) (fork+setsid-detached), so it survives 2388// the SSH channel teardown. Mirrors cmd_runmtlsproxy/cmd_runandelin exactly. 2389func cmd_runmirror() -> i64 { 2390 let lf: i64 = sys_openat_wr("/tmp/apertus_mirror.out" as *u8, 0x1a4) 2391 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2392 let argv: *i64 = sys_mmap(32) as *i64 2393 argv[0]=HC_MIRROR_BIN as i64; argv[1]=0 2394 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2395 sys_execve_clean(HC_MIRROR_BIN, argv, envp) 2396 sys_exit(127) 2397 return 0 2398} 2399// mirror: fire-and-forget launch the NAS-resident mirror (detached), then show the first log bytes as confirmation. 2400func cmd_mirror() -> i64 { 2401 hc_puts("=== mirror: sovereign-launch nx_apertus_mirror_nas.elf -> /volume1/ai/apertus (NAS-local, detached) ===\n" as *u8) 2402 hc_dfork_exec(HC_RUNMIRROR) 2403 sys_sleep_ms(HC_MAGIC_1500) 2404 let lb: *u8 = sys_mmap(HC_MAGIC_8192) 2405 let n: i64 = hc_read_cmdline("/tmp/apertus_mirror.out" as *u8, lb, HC_MAGIC_8192) 2406 hc_puts("--- /tmp/apertus_mirror.out (first bytes) ---\n" as *u8) 2407 if n>0 { sys_write(1, lb, n) } else { hc_puts("(empty -- starting)\n" as *u8) } 2408 return 0 2409} 2410// PARALLEL: shallow-execve the concurrent-fork downloader (proves what crashed WSL runs fine on the NAS). 2411func cmd_runmirrorpar() -> i64 { 2412 let lf: i64 = sys_openat_wr("/tmp/apertus_par.out" as *u8, 0x1a4) 2413 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2414 let argv: *i64 = sys_mmap(32) as *i64 2415 argv[0]=HC_MIRRORPAR_BIN as i64; argv[1]=0 2416 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2417 sys_execve_clean(HC_MIRRORPAR_BIN, argv, envp) 2418 sys_exit(127) 2419 return 0 2420} 2421func cmd_mirrorpar() -> i64 { 2422 hc_puts("=== mirrorpar: sovereign-launch nx_par_pull_nas.elf (PARALLEL concurrent, NAS-native, detached) ===\n" as *u8) 2423 hc_dfork_exec(HC_RUNMIRRORPAR) 2424 sys_sleep_ms(HC_MAGIC_2500) 2425 let lb: *u8 = sys_mmap(HC_MAGIC_8192) 2426 let n: i64 = hc_read_cmdline("/tmp/apertus_par.out" as *u8, lb, HC_MAGIC_8192) 2427 hc_puts("--- /tmp/apertus_par.out (first bytes) ---\n" as *u8) 2428 if n>0 { sys_write(1, lb, n) } else { hc_puts("(empty -- starting)\n" as *u8) } 2429 return 0 2430} 2431// stop the NAS mirror/parallel pulls -- FULL-cmdline match (hc_kill_by_cmdline), NOT the 15-char-truncated comm. 2432func cmd_mirrorstop() -> i64 { 2433 hc_puts("=== mirrorstop: kill NAS mirror + parallel pulls (full-cmdline match) ===\n" as *u8) 2434 let k1: i64 = hc_kill_by_cmdline("nx_apertus_mirror_nas.elf" as *u8) 2435 let k2: i64 = hc_kill_by_cmdline("nx_par_pull_nas.elf" as *u8) 2436 hc_puts(" killed mirror=" as *u8); hc_putn(k1); hc_puts(" par=" as *u8); hc_putn(k2); hc_puts("\n" as *u8) 2437 return 0 2438} 2439// rundistpub: SHALLOW execve of the publisher -- reached ONLY via hc_dfork_exec, dodging the deep-forked-child 2440// codegen edge (a DIRECT fork+execve here silently no-ops, exactly what the mirror sidesteps). stdout -> the log. 2441func cmd_rundistpub() -> i64 { 2442 let lf: i64 = sys_openat_wr("/tmp/distpub.out" as *u8, 0x1a4) 2443 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2444 let argv: *i64 = sys_mmap(32) as *i64; argv[0]=HC_DISTPUB_BIN as i64; argv[1]=0 2445 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2446 sys_execve_clean(HC_DISTPUB_BIN, argv, envp) 2447 sys_exit(127) 2448 return 0 2449} 2450// distpub: launch the publisher detached (survives SSH teardown), give it time to hash, then emit its output. 2451// Idempotent + additive, so re-running is always safe if the first emit is early. 2452func cmd_distpub() -> i64 { 2453 hc_puts("=== distpub: publish a real NAS file to the hub distribution index ===\n" as *u8) 2454 hc_dfork_exec(HC_RUNDISTPUB) 2455 sys_sleep_ms(HC_MAGIC_4000) 2456 let lb: *u8 = sys_mmap(HC_MAGIC_8192); let n: i64 = hc_read_cmdline("/tmp/distpub.out" as *u8, lb, HC_MAGIC_8192) 2457 hc_puts("--- /tmp/distpub.out ---\n" as *u8) 2458 if n>0 { sys_write(1, lb, n) } else { hc_puts("(empty -- still hashing? re-run distpub; it is idempotent)\n" as *u8) } 2459 return 0 2460} 2461// rundistserve: SHALLOW execve of the /dist download server (via hc_dfork_exec). argv = [bin, "serve", "18099"]. 2462func cmd_rundistserve() -> i64 { 2463 let lf: i64 = sys_openat_wr("/tmp/distserve.out" as *u8, 0x1a4) 2464 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2465 let argv: *i64 = sys_mmap(64) as *i64; argv[0]=HC_DISTSERVE_BIN as i64; argv[1]=HC_A_SERVE as i64; argv[2]=HC_A_PORT as i64; argv[3]=0 2466 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2467 sys_execve_clean(HC_DISTSERVE_BIN, argv, envp) 2468 sys_exit(127) 2469 return 0 2470} 2471// rundistprobe: SHALLOW execve of the loopback probe. argv = [bin, "probe", "18099", "/dist/<cid>/<name>"]. 2472func cmd_rundistprobe() -> i64 { 2473 let lf: i64 = sys_openat_wr("/tmp/distprobe.out" as *u8, 0x1a4) 2474 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2475 let argv: *i64 = sys_mmap(64) as *i64; argv[0]=HC_DISTSERVE_BIN as i64; argv[1]=HC_A_PROBE as i64; argv[2]=HC_A_PORT as i64; argv[3]=HC_DIST_PROBE_PATH as i64; argv[4]=0 2476 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2477 sys_execve_clean(HC_DISTSERVE_BIN, argv, envp) 2478 sys_exit(127) 2479 return 0 2480} 2481// distserve: launch the /dist download server detached, show its LIVE banner. 2482func cmd_distserve() -> i64 { 2483 hc_puts("=== distserve: start the /dist download server on 127.0.0.1:18099 ===\n" as *u8) 2484 hc_dfork_exec(HC_RUNDISTSERVE) 2485 sys_sleep_ms(HC_MAGIC_1200) 2486 let lb: *u8 = sys_mmap(HC_MAGIC_4096); let n: i64 = hc_read_cmdline("/tmp/distserve.out" as *u8, lb, HC_MAGIC_4096) 2487 if n>0 { sys_write(1, lb, n) } else { hc_puts("(no banner yet)\n" as *u8) } 2488 return 0 2489} 2490// distprobe: loopback GET a published cid -> proves the download transport returns the real bytes. 2491func cmd_distprobe() -> i64 { 2492 hc_puts("=== distprobe: loopback GET a published cid from :18099 ===\n" as *u8) 2493 hc_dfork_exec(HC_RUNDISTPROBE) 2494 sys_sleep_ms(HC_MAGIC_2000) 2495 let lb: *u8 = sys_mmap(HC_MAGIC_8192); let n: i64 = hc_read_cmdline("/tmp/distprobe.out" as *u8, lb, HC_MAGIC_8192) 2496 if n>0 { sys_write(1, lb, n) } else { hc_puts("(no probe output -- server not up?)\n" as *u8) } 2497 return 0 2498} 2499func cmd_runandelin() -> i64 { 2500 let lf: i64 = sys_openat_wr("/tmp/andelin.out" as *u8, 0x1a4) 2501 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2502 let argv: *i64 = sys_mmap(32) as *i64 2503 argv[0]=HC_ANDELIN_BIN as i64; argv[1]=0 2504 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2505 sys_execve_clean(HC_ANDELIN_BIN, argv, envp) 2506 sys_exit(127) 2507 return 0 2508} 2509func cmd_runsnir() -> i64 { 2510 let lf: i64 = sys_openat_wr("/tmp/snir.out" as *u8, 0x1a4) 2511 if lf >= 0 { sys_dup3(lf,1,0); sys_dup3(lf,2,0) } 2512 let argv: *i64 = sys_mmap(32) as *i64 2513 argv[0]=HC_SNIR_BIN as i64; argv[1]=HC_SNIR_PORT as i64; argv[2]=HC_SNIR_BACKEND as i64; argv[3]=0 2514 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2515 sys_execve_clean(HC_SNIR_BIN, argv, envp) 2516 sys_exit(127) 2517 return 0 2518} 2519// nftread: SOVEREIGN NETFILTER R0 grounding. Direct (non-forked main-dispatch) shallow execve of nx_nft_read -> 2520// READS the kernel nat-table info via getsockopt(IPT_SO_GET_INFO). Runs as root (this nx_hostctl was sudo'd). 2521// READ-ONLY: cannot disturb DSM. stdout (the table info, or the rc telling us legacy-vs-nft) flows back up the SSH channel. 2522func cmd_nftread() -> i64 { 2523 let argv: *i64 = sys_mmap(16) as *i64; argv[0]=HC_NFTREAD_BIN as i64; argv[1]=0 2524 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2525 sys_execve_clean(HC_NFTREAD_BIN, argv, envp) 2526 sys_exit(127) 2527 return 0 2528} 2529// nettap: the sovereign WIRESHARK-in-the-API (operator 2026-07-04 "build that capability into the apis"). 2530// Shallow-execve of nx_netscope_capture (AF_PACKET raw socket -> nx_packet_dissect; root here = CAP_NET_RAW OK). 2531// READ-ONLY on the wire + HARD-BOUNDED (dport filter, 60s wall-clock, 200-row cap) -- observes, never disturbs. 2532// Optional argv[2] = dest port (default 443). Output (SYN src -> dst rows) flows back up the SSH channel. 2533func cmd_nettap(port: *u8) -> i64 { 2534 let argv: *i64 = sys_mmap(48) as *i64 2535 argv[0] = HC_NETTAP_BIN as i64 2536 if (port as i64) != 0 { argv[1] = port as i64 } else { argv[1] = "443" as *u8 as i64 } 2537 argv[2] = "60" as *u8 as i64 2538 argv[3] = "200" as *u8 as i64 2539 argv[4] = 0 2540 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2541 sys_execve_clean(HC_NETTAP_BIN, argv, envp) 2542 sys_exit(127) 2543 return 0 2544} 2545// platformtest: launch the andelinwest CLIENT daemon (:8453) + the SNI router (:8444) on INTERNAL ports, print 2546// each startup, leave both RUNNING for verification. Touches NOTHING live (no :443, no DSM). 2547func cmd_platformtest() -> i64 { 2548 hc_puts("=== platformtest: sovereign-launch andelinwest :8453 (LE ECDSA) + SNI router :8444 (no /bin/sh) ===\n" as *u8) 2549 let lb: *u8 = sys_mmap(HC_MAGIC_8192) 2550 hc_dfork_exec(HC_RUNANDELIN) 2551 sys_sleep_ms(HC_MAGIC_1200) 2552 let n1: i64 = hc_read_cmdline("/tmp/andelin.out" as *u8, lb, HC_MAGIC_8192) 2553 hc_puts("--- /tmp/andelin.out ---\n" as *u8); if n1>0 { sys_write(1, lb, n1) } else { hc_puts("(empty)\n" as *u8) } 2554 if proc_alive_by_name("nx_andelinwest_daemon.elf" as *u8) == 1 { hc_puts(" andelinwest ALIVE :8453\n" as *u8) } else { hc_puts(" andelinwest NOT alive\n" as *u8) } 2555 hc_dfork_exec(HC_RUNSNIR) 2556 sys_sleep_ms(HC_MAGIC_1200) 2557 let n2: i64 = hc_read_cmdline("/tmp/snir.out" as *u8, lb, HC_MAGIC_8192) 2558 hc_puts("--- /tmp/snir.out ---\n" as *u8); if n2>0 { sys_write(1, lb, n2) } else { hc_puts("(empty)\n" as *u8) } 2559 if proc_alive_by_name("nx_sni_router.elf" as *u8) == 1 { hc_puts(" sni_router ALIVE :8444\n" as *u8) } else { hc_puts(" sni_router NOT alive\n" as *u8) } 2560 return 0 2561} 2562// DEPLOY the new login daemon: save .prev, atomic-swap .new->live, kill+respawn :9091 (24h sessions). Existing 2563// tokens keep working (stateless); only /login is briefly down during respawn. Reversible: loginrollback. 2564func cmd_logindeploy() -> i64 { 2565 hc_puts("[hostctl] logindeploy: .prev save + atomic-swap nx_opaque_login.elf + restart :9091 (24h sessions)\n" as *u8) 2566 sys_renameat(HC_LOGIN, HC_LOGIN_PREV) 2567 let r: i64 = sys_renameat(HC_LOGIN_NEW, HC_LOGIN) 2568 nx_chmod(HC_LOGIN, 0x1ed) 2569 hc_put_rc(" swap rc=" as *u8, r) 2570 let pk: i64 = proc_kill_by_name("nx_opaque_login.elf" as *u8, 9) 2571 sys_sleep_ms(400) 2572 let np: i64 = hc_spawn_login() 2573 hc_puts(" killed old login procs=" as *u8); hc_putn(pk); hc_puts(" respawned pid=" as *u8); hc_putn(np); hc_puts(" :9091\n" as *u8) 2574 return 0 2575} 2576// ROLLBACK the login daemon: restore .prev -> live + restart :9091. Reverses a bad logindeploy. 2577func cmd_loginrollback() -> i64 { 2578 hc_puts("[hostctl] loginrollback: restore nx_opaque_login.elf.prev -> live + restart :9091\n" as *u8) 2579 let r: i64 = sys_renameat(HC_LOGIN_PREV, HC_LOGIN) 2580 nx_chmod(HC_LOGIN, 0x1ed) 2581 hc_put_rc(" restore rc=" as *u8, r) 2582 proc_kill_by_name("nx_opaque_login.elf" as *u8, 9) 2583 sys_sleep_ms(400) 2584 hc_spawn_login() 2585 hc_puts(" restored + restarted :9091\n" as *u8) 2586 return 0 2587} 2588 2589// HUB deploy prep: create the hub content dirs with pure syscalls (no shell mkdir) + chmod the gateway 2590// binary executable if it has already been recv'd. The supervisor's HC_HUB_GW guard then auto-launches it. 2591// Idempotent (sys_mkdir on an existing dir is harmless; nx_chmod on an absent file is harmless). 2592func cmd_hubdeploy() -> i64 { 2593 hc_puts("[hostctl] hubdeploy: mkdir /volume1/ai/hub{,/pages} + chmod nx_hub_gw.elf +x (sovereign sys_mkdir)\n" as *u8) 2594 sys_mkdir("/volume1/ai/hub" as *u8, 0x1ff) // 0777 so the elderwesto recv can write content here 2595 sys_mkdir("/volume1/ai/hub/pages" as *u8, 0x1ff) 2596 nx_chmod("/volume1/ai/hub/nx_hub_gw.elf" as *u8, 0x1ed) // 0755 if already landed (harmless if absent) 2597 let hk: i64 = proc_kill_by_name("nx_hub_gw.elf" as *u8, 9) // restart so a re-deployed binary takes effect (HC_HUB_GW guard respawns it) 2598 hc_puts(" hub dirs ready + killed nx_hub_gw procs=" as *u8); hc_putn(hk); hc_puts(" -> HC_HUB_GW guard respawns the new :18792 gateway\n" as *u8) 2599 return 0 2600} 2601 2602// GEN deploy prep: create /volume1/ai/gen (0777 so the elderwesto recv can write the elfs + the daemons can 2603// write the gallery store/blob/sidecar) + chmod the gen daemons +x. The HC_GEN_* guards then auto-launch them. 2604// Idempotent (sys_mkdir on an existing dir is harmless; nx_chmod on an absent file is harmless). 2605func cmd_gendeploy() -> i64 { 2606 hc_puts("[hostctl] gendeploy: mkdir /volume1/ai/gen + chmod gen daemons +x -> guards launch :18794/:18795\n" as *u8) 2607 sys_mkdir("/volume1/ai/gen" as *u8, 0x1ff) 2608 nx_chmod("/volume1/ai/gen/nx_gen_gateway_daemon.elf" as *u8, 0x1ed) 2609 nx_chmod("/volume1/ai/gen/nx_gen_orchestrator_daemon.elf" as *u8, 0x1ed) 2610 let g1: i64 = proc_kill_by_name("nx_gen_gateway_daemon.elf" as *u8, 9) 2611 let g2: i64 = proc_kill_by_name("nx_gen_orchestrator_daemon.elf" as *u8, 9) 2612 hc_puts(" gen dir ready + killed gw=" as *u8); hc_putn(g1); hc_puts(" orch=" as *u8); hc_putn(g2); hc_puts(" -> HC_GEN guards respawn :18794/:18795\n" as *u8) 2613 return 0 2614} 2615// genprobe: one-shot LIVE proof. Run nx_gen_live_probe.elf (sh -c exec, like cmd_logintest) -> it registers a temp 2616// owner in the live store, enrolls it OWNER, logs in, and drives the deployed gateway @127.0.0.1:18794 asserting 2617// owner->200 + no-cred->401, then demotes the temp owner. Read its output back and print it (relayed to operator). 2618func cmd_genprobe() -> i64 { 2619 // The live probe now drives a REAL generation (~15s GPU) which outlasts the nx_aw_hostctl relay's read window. 2620 // So: PRINT the previous run's COMPLETE result (from the file), then FIRE a new run DETACHED (stdout->file + 2621 // setsid so it survives the SSH close and finishes the generation). Re-run genprobe in ~25s to read this run. 2622 hc_puts("[hostctl] genprobe: last result below, then firing a fresh live-probe run (detached)\n" as *u8) 2623 let lb: *u8 = sys_mmap(HC_MAGIC_131072) 2624 let n: i64 = hc_read_cmdline("/volume1/ai/gen/genprobe.out" as *u8, lb, HC_MAGIC_131072) 2625 hc_puts("--- last nx_gen_live_probe result ---\n" as *u8) 2626 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(no previous result yet)\n" as *u8) } 2627 nx_chmod("/volume1/ai/gen/nx_gen_live_probe.elf" as *u8, 0x1ed) 2628 let pid: i64 = sys_fork() 2629 if pid==0 { 2630 nx_setsid() 2631 let argv: *i64 = sys_mmap(32) as *i64 2632 argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_GENPROBE_CMD as i64; argv[3]=0 2633 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2634 sys_execve_clean(HC_SH, argv, envp) 2635 sys_exit(127) 2636 } 2637 hc_puts("--- fired a new run (detached); re-run `genprobe` in ~25s to read its result ---\n" as *u8) 2638 return 0 2639} 2640// First-swap TAKEOVER: stop the old .sh supervisor + old daemon (sovereign /proc kill), atomic-publish 2641// the staged artifacts, then BECOME the sovereign supervisor (spawn + monitor + respawn the new daemon). 2642// Run detached (setsid) on the NAS -- retires nishi_supervisor.sh. 2643func cmd_takeover() -> i64 { 2644 hc_puts("[hostctl] TAKEOVER: stop old .sh supervisor + daemon, atomic-publish, become sovereign supervisor\n" as *u8) 2645 proc_kill_by_name("nishi_supervisor" as *u8, 9) 2646 proc_kill_by_name("sites.elf" as *u8, 9) 2647 sys_sleep_ms(600) 2648 sys_mkdir(HC_DOCROOT, 0x1ed) 2649 sys_renameat(HC_INDEX_NEW, HC_INDEX) 2650 sys_renameat(HC_CONF_NEW, HC_CONF) 2651 // seq1097 twin: never install a 0-byte staged elf (a failed upload can stage one; it sat for days 2652 // in 2026-07). Absent .new already fails the rename harmlessly; this closes the 0-byte case. 2653 let tkstb: *u8 = sys_mmap(160) 2654 var tkok: i64 = 0 2655 if sys_fstatat(HC_ELF_NEW, tkstb) == 0 { 2656 let tkszp: *i64 = ((tkstb as i64) + 48) as *i64 2657 if tkszp[0] > 0 { tkok = 1 } 2658 } 2659 if tkok == 1 { 2660 sys_renameat(HC_ELF_NEW, HC_ELF) 2661 nx_chmod(HC_ELF, 0x1ed) 2662 } else { 2663 hc_puts("[hostctl] takeover: no real staged sites.elf.new (absent or 0B) -- keeping current live\n" as *u8) 2664 } 2665 hc_puts("[hostctl] artifacts published; daemonizing sovereign supervisor (setsid + detach stdio)\n" as *u8) 2666 let pid: i64 = sys_fork() 2667 if pid == 0 { 2668 nx_setsid() // new session -> survives SSH/parent close 2669 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 2670 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } // detach stdout/stderr from the SSH channel 2671 cmd_supervise() // become the detached sovereign supervisor 2672 sys_exit(0) 2673 return 0 2674 } 2675 hc_puts("[hostctl] supervisor daemonized; takeover returns (ssh_exec gets channel EOF)\n" as *u8) 2676 return 0 2677} 2678 2679// LOCAL Engineer gate: prove atomic-rename publish + pidfile + kill + /proc-scan-kill on /tmp (no NAS). 2680func cmd_selftest() -> i64 { 2681 var pass: i64=0; var total: i64=0 2682 // 1. atomic publish 2683 let fda: i64 = sys_openat_wr("/tmp/hc_live" as *u8, 0x1a4); sys_write(fda, "OLD" as *u8, 3); sys_close(fda) 2684 let fdb: i64 = sys_openat_wr("/tmp/hc_live.new" as *u8, 0x1a4); sys_write(fdb, "NEWCONTENT" as *u8, 10); sys_close(fdb) 2685 sys_renameat("/tmp/hc_live.new" as *u8, "/tmp/hc_live" as *u8) 2686 let box: *i64 = sys_mmap(16) as *i64; box[0]=0 2687 let got: *u8 = sys_read_file("/tmp/hc_live" as *u8, box) 2688 total=total+1 2689 if (got as i64)!=0 { if box[0]==10 { hc_puts(" PASS atomic-publish: live flipped to NEWCONTENT\n" as *u8); pass=pass+1 } else { hc_puts(" FAIL publish len\n" as *u8) } } else { hc_puts(" FAIL publish read\n" as *u8) } 2690 // 2. pidfile round-trip + kill 2691 let kid: i64 = sys_fork() 2692 if kid==0 { sys_sleep_ms(HC_MAGIC_4000); sys_exit(0); return 0 } 2693 hc_write_pid("/tmp/hc_test.pid" as *u8, kid) 2694 let rp: i64 = hc_read_pid("/tmp/hc_test.pid" as *u8) 2695 sys_sleep_ms(300) 2696 let kr: i64 = nx_kill(rp, 9) 2697 let st: *i64 = sys_mmap(16) as *i64; st[0]=0 2698 let reaped: i64 = sys_wait4(kid, st, 0) 2699 total=total+1 2700 if rp==kid { if kr==0 { if (st[0]&0x7f)==9 { hc_puts(" PASS pidfile+kill: round-tripped pid, SIGKILLed daemon\n" as *u8); pass=pass+1 } else { hc_puts(" FAIL not signal-killed\n" as *u8) } } else { hc_puts(" FAIL kill rc\n" as *u8) } } else { hc_puts(" FAIL pidfile round-trip\n" as *u8) } 2701 // 3. proc_kill_by_name -- fork a child that execs /bin/sleep with a unique marker, /proc-kill it 2702 let kid2: i64 = sys_fork() 2703 if kid2 == 0 { 2704 let av: *i64 = sys_mmap(32) as *i64 2705 av[0] = "/bin/sleep" as *u8 as i64; av[1] = "31337" as *u8 as i64; av[2] = 0 2706 let ev: *i64 = sys_mmap(16) as *i64; ev[0] = 0 2707 sys_execve_clean("/bin/sleep" as *u8, av, ev) 2708 sys_exit(0) 2709 return 0 2710 } 2711 sys_sleep_ms(400) 2712 let nk: i64 = proc_kill_by_name("31337" as *u8, 9) 2713 let st2: *i64 = sys_mmap(16) as *i64; st2[0]=0 2714 let reaped2: i64 = sys_wait4(kid2, st2, 0) 2715 total=total+1 2716 if nk >= 1 { if reaped2 == kid2 { if (st2[0]&0x7f)==9 { hc_puts(" PASS proc_kill_by_name: found by /proc cmdline + SIGKILLed\n" as *u8); pass=pass+1 } else { hc_puts(" FAIL proc_kill: not signal-killed\n" as *u8) } } else { hc_puts(" FAIL proc_kill: wait4\n" as *u8) } } else { hc_puts(" FAIL proc_kill: found 0\n" as *u8) } 2717 2718 hc_puts("---- hostctl selftest passed " as *u8); let nb: *u8=sys_mmap(4); nb[0]=(48+pass) as u8; sys_write(1,nb,1); hc_puts(" / " as *u8); let tb: *u8=sys_mmap(4); tb[0]=(48+total) as u8; sys_write(1,tb,1); hc_puts(" ----\n" as *u8) 2719 if pass==total { return 0 } 2720 return 1 2721} 2722 2723// SOVEREIGN supervisor self-update (operator: "no sh"). nx_aw_push streams the new binary to nishihost as 2724// nx_hostctl.new, then execs THIS (run as nx_hostctl.new selfswap). We: atomic-rename .new over the live 2725// supervisor path; kill ONLY the old supervisor (matched by its "supervise" cmdline -- this "selfswap" 2726// process and sites.elf/redirect/vroom do NOT contain that token, so they're untouched); then re-exec the 2727// LIVE (now-new) binary as a detached `supervise`. The new supervisor ADOPTS every running daemon (proc_alive, 2728// no double-spawn) and adds the library reader -> the live site is never interrupted, only the supervisor 2729// process is swapped. Re-exec (not a bare cmd_supervise call) gives it the canonical "nx_hostctl supervise" 2730// cmdline so the NEXT selfswap can find it. 2731func cmd_selfswap() -> i64 { 2732 hc_puts("[hostctl] selfswap: atomic-install new supervisor + restart supervisor only (sites.elf untouched)\n" as *u8) 2733 // *** FAIL-SAFE GUARD (seq992, 2026-07-25) -- RESOLVE THE STAGED BINARY BEFORE TOUCHING THE LIVE ONE. 2734 // This function used to rename the live supervisor to .prev and only THEN rename the staged binary 2735 // over it, with NO existence check. If nothing was staged under HC_SELF_NEW, the live supervisor was 2736 // moved away, NOTHING was installed, the running supervisor was SIGKILLed, and the re-exec targeted a 2737 // path that no longer existed => no supervisor, unable to respawn itself. That is the EXACT outage 2738 // hc_promote_to was rewritten to avoid ("it moved the live binary to .prev and installed NOTHING"), 2739 // and hc_promote_to got the hc_exists guard while this path did not. 2740 // It was reachable by the DOCUMENTED sequence: /api/build stages <t>.sov.elf.new, but the deploy row 2741 // names nx_hostctl.new, and no API-reachable rename bridges them (seq983) -- so build-then-deploy ran 2742 // selfswap with HC_SELF_NEW absent. Accepting the .sov.elf.new name here closes the naming trap too. 2743 var srcnew: *u8 = HC_SELF_NEW 2744 if hc_exists(HC_SELF_NEW) == 0 { 2745 if hc_exists(HC_SELF_NEW_SOV) == 1 { 2746 srcnew = HC_SELF_NEW_SOV 2747 hc_puts(" staged binary found under the /api/build name (nx_hostctl.sov.elf.new)\n" as *u8) 2748 } else { 2749 hc_puts("[hostctl] selfswap: REFUSED -- no staged supervisor at nx_hostctl.new or nx_hostctl.sov.elf.new; live binary UNTOUCHED\n" as *u8) 2750 return 1 2751 } 2752 } 2753 sys_renameat(HC_SELF, HC_SELF_PREV) // never-brick (#26): save the OLD supervisor binary for superrollback (selfswap had NO rollback) 2754 let rr: i64 = sys_renameat(srcnew, HC_SELF) 2755 nx_chmod(HC_SELF, 0x1ed) 2756 hc_put_rc(" install rename rc=" as *u8, rr) 2757 let k: i64 = proc_kill_by_two("nx_hostctl" as *u8, "supervise" as *u8, 9) 2758 hc_puts(" killed old supervisor procs=" as *u8); let kb: *u8=sys_mmap(4); kb[0]=(48+(k%10)) as u8; sys_write(1,kb,1); hc_puts(" (matched nx_hostctl+supervise only -- s6-supervise untouched)\n" as *u8) 2759 sys_sleep_ms(400) 2760 hc_sup_clear() // clear the lease so the re-exec'd new supervisor claims (the killed old one's heartbeat is stale) 2761 let pid: i64 = sys_fork() 2762 if pid == 0 { 2763 nx_setsid() 2764 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 2765 if dn >= 0 { sys_dup3(dn, 0, 0); sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } // ALL of stdin/out/err -> /dev/null (stdin too: the inherited SSH channel closing was killing the re-exec'd child) 2766 let argv: *i64 = sys_mmap(16) as *i64; argv[0]=HC_SELF as i64; argv[1]=HC_SUPERVISE as i64; argv[2]=0 2767 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2768 sys_execve_clean(HC_SELF, argv, envp) 2769 sys_exit(127) 2770 return 0 2771 } 2772 hc_puts(" new supervisor daemonized (re-exec'd live binary as `supervise`)\n" as *u8) 2773 return 0 2774} 2775 2776// superrollback: restore the PREVIOUS supervisor binary (saved by selfswap as .prev) + reconcile to it. The 2777// never-brick (#26) rollback path selfswap lacked -- if a freshly selfswap'd supervisor misbehaves, this puts the 2778// proven binary back and relaunches it (kill duplicates, clear the lease, daemonize the one). The recovery net. 2779func cmd_superrollback() -> i64 { 2780 hc_puts("[hostctl] superrollback: restore nx_hostctl.prev -> live supervisor binary, then reconcile to it\n" as *u8) 2781 let rr: i64 = sys_renameat(HC_SELF_PREV, HC_SELF) 2782 nx_chmod(HC_SELF, 0x1ed) 2783 hc_puts(" restore rename rc=" as *u8); hc_putn(rr); hc_puts("\n" as *u8) 2784 let k: i64 = proc_kill_by_two("nx_hostctl" as *u8, "supervise" as *u8, 9) 2785 hc_puts(" killed supervisors=" as *u8); hc_putn(k); hc_puts("\n" as *u8) 2786 sys_sleep_ms(800) 2787 hc_sup_clear() 2788 hc_daemonize_supervise() 2789 hc_puts(" restored (prev) supervisor daemonized\n" as *u8) 2790 return 0 2791} 2792 2793// supervisetest: READ-ONLY mechanical proof of the single-supervisor guarantee (#26: prove, don't assert). Reads 2794// the live lease + reports its age and what a 2nd `supervise` launch WOULD do -- WITHOUT starting a competing 2795// loop. fresh(<90s) => a duplicate launch defers (guaranteed one supervisor); stale => the heartbeat stopped. 2796func cmd_supervisetest() -> i64 { 2797 hc_puts("[hostctl] supervisetest: READ-ONLY single-supervisor lease check (no competing loop started)\n" as *u8) 2798 let e: i64 = hc_read_pid(HC_SUP_LOCK) 2799 let now: i64 = sys_now_realtime_sec() 2800 hc_puts(" lease_epoch=" as *u8); hc_putn(e); hc_puts(" now=" as *u8); hc_putn(now); hc_puts(" age_s=" as *u8); hc_putn(now - e); hc_puts("\n" as *u8) 2801 let fresh: i64 = hc_sup_fresh() 2802 hc_puts(" hc_sup_fresh=" as *u8); hc_putn(fresh) 2803 if fresh == 1 { hc_puts(" -> a 2nd `supervise` launch WOULD DEFER = single-supervisor GUARANTEED (mechanically proven)\n" as *u8) } 2804 if fresh == 0 { hc_puts(" -> lease NOT fresh (no live heartbeat) -> a launch would CLAIM (no live supervisor to defer to)\n" as *u8) } 2805 return 0 2806} 2807 2808// DIAGNOSTIC: spawn the reader exactly as the supervisor does (fork + sh -c exec, direct child), then BLOCK in 2809// wait4 on it and report lifetime + sig/code. Run standalone (nx_hostctl spawntest) -> isolates the spawn from 2810// the poll loop. If it never returns, the reader stays up when nx_hostctl spawns it (so the loop/probe is the 2811// real culprit); if it returns, we finally see WHY the reader dies (signal vs exit code, and how long it lived). 2812func cmd_spawntest() -> i64 { 2813 hc_puts("[spawntest] fork+exec reader (direct child), blocking wait4 for its fate\n" as *u8) 2814 let t0: i64 = sys_now_realtime_sec() 2815 let pid: i64 = hc_spawn_reader() 2816 hc_puts(" spawned pid=" as *u8); hc_putn(pid); hc_puts("\n" as *u8) 2817 let st: *i64 = sys_mmap(16) as *i64; st[0]=0 2818 let reaped: i64 = sys_wait4(pid, st, 0) 2819 let t1: i64 = sys_now_realtime_sec() 2820 hc_puts(" REAPED=" as *u8); hc_putn(reaped); hc_puts(" lived_s=" as *u8); hc_putn(t1-t0) 2821 hc_puts(" sig=" as *u8); hc_putn(st[0]&0x7f); hc_puts(" code=" as *u8); hc_putn((st[0]>>8)&0xff); hc_puts("\n" as *u8) 2822 return 0 2823} 2824 2825// hc_dfork_exec: the SOVEREIGN daemonizer -- pure .nx, NO OS setsid/sh. SINGLE fork -> the CHILD does nx_setsid 2826// (the setsid SYSCALL -> SESSION LEADER, no controlling terminal) -> execve's a fresh `nx_hostctl <arg>` image, 2827// which runs forever and reparents to init when this (parent) process exits. The parent does NOT wait4 (the 2828// child never returns). This is EXACTLY the structure of `setsid nx_hostctl supervise &` (live PID 16331, which 2829// SURVIVES) and the durable `setsid ./sites.elf` -- both SESSION LEADERS. The earlier DOUBLE fork made the 2830// daemon a non-leader grandchild, which cmd_supervise did NOT survive (the trivial sleeper did) -- session- 2831// leadership is the property that matters. stdio -> supervisor.log + /dev/null. arg = "supervise"|"sleeper". 2832func hc_dfork_exec(arg: *u8) -> i64 { 2833 let pid: i64 = sys_fork() 2834 if pid == 0 { 2835 nx_setsid() 2836 let lf: i64 = sys_openat_append("/volume1/homes/elderwesto/nishihost/supervisor.log" as *u8, 420) 2837 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 2838 if dn >= 0 { sys_dup3(dn, 0, 0) } 2839 if lf >= 0 { sys_dup3(lf, 1, 0); sys_dup3(lf, 2, 0) } 2840 let argv: *i64 = sys_mmap(32) as *i64 2841 argv[0]=HC_SELF as i64; argv[1]=arg as i64; argv[2]=0 2842 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2843 sys_execve_clean(HC_SELF, argv, envp) // child (session leader) becomes nx_hostctl <arg>, runs forever 2844 sys_exit(127) 2845 return 0 2846 } 2847 return 0 // parent returns immediately; child reparents to init on exit 2848} 2849 2850func hc_daemonize_supervise() -> i64 { return hc_dfork_exec(HC_SUPERVISE) } 2851 2852// A sovereign idle daemon (NO OS /bin/sleep) -- the daemontest payload only: loops sleeping forever so I can 2853// daemonize it and confirm it survived the channel teardown without restarting the real supervisor. 2854func cmd_sleeper() -> i64 { 2855 var run: i64 = 1 2856 while run == 1 { sys_sleep_ms(HC_MAGIC_3600000) } 2857 return 0 2858} 2859 2860// SAFE proof that the SOVEREIGN hc_dfork_exec survives the teardown, WITHOUT restarting the real supervisor: 2861// daemonize `nx_hostctl sleeper` (pure .nx, no OS tools), then check it is still running after the channel closed. 2862func cmd_daemontest() -> i64 { 2863 hc_puts("[daemontest] SOVEREIGN hc_dfork_exec -> nx_hostctl sleeper (no OS tools); check it survives\n" as *u8) 2864 hc_dfork_exec(HC_SLEEPER) 2865 return 0 2866} 2867 2868// S-CLASS OPS RECONCILE (operator: "fix this bullshit rooted owned stuff -- s-class exceed at ops/mgmt"). 2869// THE ROOT-CAUSE of the recurring pain: a SPLIT ownership model -- the boot rc.d launches nx_hostctl as ROOT 2870// (so sites.elf/vroom/translate/redirect are root), while ad-hoc SSH-launched supervisors + the reader ran as 2871// elderwesto. Neither could manage the other's daemons (EPERM), deploys couldn't restart root daemons, and 2872// duplicate supervisors competed. FIX = ONE supervisor, run as root (consistent with the boot context), 2873// owning EVERYTHING. Run via `sudo nx_hostctl reconcile` (nx_aw_sudo): kill every supervisor/keeper (matched 2874// nx_hostctl+supervise, NOT this "reconcile" proc), the reader, and sites.elf (so the freshly-deployed binary 2875// loads); then fork+setsid+detach and BECOME the one root supervisor (cmd_supervise forks the keeper -> reader 2876// and adopts/respawns the rest). Direct cmd_supervise() call (NOT execve) -- the keeper-proven survival path. 2877func cmd_reconcile() -> i64 { 2878 hc_puts("[hostctl] reconcile: ONE sovereign session-leader supervisor; ADOPT running sites.elf (zero website downtime)\n" as *u8) 2879 let k1: i64 = proc_kill_by_two("nx_hostctl" as *u8, "supervise" as *u8, 9) 2880 let k2: i64 = proc_kill_by_name("nx_media_server_auth.elf" as *u8, 9) 2881 let k3: i64 = proc_kill_by_name("nx_health_eval.elf" as *u8, 9) // cycle the M&E loop so the fresh supervisor respawns the latest ELF (no long-lived orphan on an update) 2882 let k4: i64 = proc_kill_by_name("nx_signaling_v2" as *u8, 9) // cycle the signaling relay onto the latest ELF (clients auto-reconnect lanes ~1.2s + re-hello presence) 2883 hc_puts(" killed supervisors+keepers=" as *u8); hc_putn(k1); hc_puts(" readers=" as *u8); hc_putn(k2); hc_puts(" health=" as *u8); hc_putn(k3); hc_puts(" sig2=" as *u8); hc_putn(k4); hc_puts(" (sites.elf KEPT -> adopted)\n" as *u8) 2884 sys_sleep_ms(800) 2885 hc_sup_clear() // clear the lease so the fresh supervisor claims (the killed old one's heartbeat is stale-by-fiat) 2886 hc_daemonize_supervise() 2887 hc_puts(" ONE sovereign supervisor daemonized (single-fork session leader) -> keeper(reader) + adopts sites.elf\n" as *u8) 2888 return 0 2889} 2890 2891// kickreader: SURGICAL one-shot wedge-clear. Kill ONLY the library reader (:8791) by name; the long-running 2892// supervisor's blocking-wait keeper (cmd_reader_keep) detects the death and respawns a FRESH reader. Does NOT 2893// touch the supervisor/keeper/sites.elf/gallery/wiki -> minimal blast radius (the reader blips ~1s, website 2894// untouched). This is the surgical primitive a wedge needs: a wedge = alive-but-slow, which the keeper can't 2895// see (it only respawns on DEATH). Never-brick #26: the kill is reversible BY the keeper (proven UP at runtime). 2896// The one-shot's own cmdline is "nx_hostctl kickreader" (never contains the reader needle) -> no self-match. 2897func cmd_kickreader() -> i64 { 2898 hc_puts("[hostctl] kickreader: SIGKILL the library reader (:8791) -> the keeper respawns a fresh one\n" as *u8) 2899 let k: i64 = proc_kill_by_name("nx_media_server_auth.elf" as *u8, 9) 2900 hc_puts(" killed reader procs=" as *u8); hc_putn(k); hc_puts(" (keeper blocking-wait4 respawns a FRESH reader)\n" as *u8) 2901 if k == 0 { hc_puts(" NOTE: no reader matched -- nothing to kick (reader not running here?)\n" as *u8) } 2902 return 0 2903} 2904 2905// kicktorrent: SURGICAL one-shot for the media/torrent daemon (:8097). Kill ONLY the daemon by name; the 2906// supervise-loop guard (hc_guard_torrent, proc_alive "nx_torrent_daemon") detects the death and respawns it 2907// via the hub profile (nx_media_env hub) -> the NEW binary. The one-shot's cmdline is "nx_hostctl kicktorrent" 2908// (never contains "nx_torrent_daemon"; "nx_torrent_gw" is a different name) -> no self/gateway match. Use after 2909// staging a new daemon binary. Never-brick #26: reversible BY the guard (proven UP at runtime). 2910func cmd_kicktorrent() -> i64 { 2911 hc_puts("[hostctl] kicktorrent: SIGKILL the media/torrent daemon (:8097) -> the guard respawns the NEW binary\n" as *u8) 2912 let k: i64 = proc_kill_by_name("nx_torrent_daemon" as *u8, 9) 2913 hc_puts(" killed daemon procs=" as *u8); hc_putn(k); hc_puts(" (supervisor guard respawns via the hub profile)\n" as *u8) 2914 if k == 0 { hc_puts(" NOTE: no daemon matched -- nothing to kick (not running here?)\n" as *u8) } 2915 return 0 2916} 2917 2918// kickseed: SURGICAL one-shot for the BitTorrent SEEDER (:6881). Kill it by name; hc_guard_seed respawns the 2919// NEW binary on the next poll. The one-shot cmdline "nx_hostctl kickseed" never contains "nx_torrent_seed.elf" 2920// -> no self-match. Use after staging a new seeder binary or editing seed_index.conf (fresh registry read). 2921func cmd_kickseed() -> i64 { 2922 hc_puts("[hostctl] kickseed: SIGKILL the BitTorrent seeder (:6881) -> hc_guard_seed respawns the NEW binary\n" as *u8) 2923 let k: i64 = proc_kill_by_name("nx_torrent_seed.elf" as *u8, 9) 2924 hc_puts(" killed seeder procs=" as *u8); hc_putn(k); hc_puts(" (supervisor guard respawns from seed_index.conf)\n" as *u8) 2925 if k == 0 { hc_puts(" NOTE: no seeder matched -- guard will start it on the next poll (fail-safe)\n" as *u8) } 2926 return 0 2927} 2928// portmap: auto-open :6881 on the router via NAT-PMP (nx_natpmp) -> WAN-reachable seeder + more peers. Fail-safe. 2929// routerctl: sovereign router dashboard (nx_router_ctl dash over our own SSH -> GL.iNet uci: model/wan-ip/forwards/clients). 2930const HC_ROUTERCTL_CMD: *u8 = "cd /volume1/ai/torrent && chmod +x ./nx_router_ctl.elf 2>/dev/null; : > /tmp/routerctl.out; ./nx_router_ctl.elf dash >> /tmp/routerctl.out 2>&1" as *u8 2931func cmd_routerctl() -> i64 { 2932 let pid: i64 = sys_fork() 2933 if pid == 0 { 2934 let argv: *i64 = sys_mmap(32) as *i64; argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_ROUTERCTL_CMD as i64; argv[3]=0 2935 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2936 sys_execve_clean(HC_SH, argv, envp); sys_exit(127) 2937 } 2938 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 2939 hc_puts("=== routerctl: GL.iNet dashboard (sovereign SSH+uci) ===\n" as *u8) 2940 let lb: *u8 = sys_mmap(HC_MAGIC_16384); let n: i64 = hc_read_cmdline("/tmp/routerctl.out" as *u8, lb, HC_MAGIC_16384) 2941 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(no output)\n" as *u8) } 2942 return 0 2943} 2944const HC_PORTMAP_CMD: *u8 = "cd /volume1/ai/torrent && chmod +x ./nx_natpmp.elf ./nx_upnp_igd.elf 2>/dev/null; : > /tmp/portmap.out; echo === NAT-PMP === >> /tmp/portmap.out; ./nx_natpmp.elf 6881 >> /tmp/portmap.out 2>&1; echo === UPnP-IGD === >> /tmp/portmap.out; ./nx_upnp_igd.elf 6881 >> /tmp/portmap.out 2>&1" as *u8 2945func cmd_portmap() -> i64 { 2946 // The SSDP->desc->SOAP flow can exceed the control-plane relay timeout, so run it DETACHED and show the 2947 // PREVIOUS run's result. Each call: print /tmp/portmap.out (last result) + launch a fresh background run. 2948 hc_puts("=== portmap: last NAT-PMP + UPnP result (/tmp/portmap.out) ===\n" as *u8) 2949 let lb: *u8 = sys_mmap(HC_MAGIC_16384); let n: i64 = hc_read_cmdline("/tmp/portmap.out" as *u8, lb, HC_MAGIC_16384) 2950 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(no prior result yet -- fresh run launching now)\n" as *u8) } 2951 let pid: i64 = sys_fork() // detached (double-fork) so it survives the relay close 2952 if pid == 0 { 2953 let pid2: i64 = sys_fork() 2954 if pid2 == 0 { 2955 let argv: *i64 = sys_mmap(32) as *i64; argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_PORTMAP_CMD as i64; argv[3]=0 2956 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2957 sys_execve_clean(HC_SH, argv, envp); sys_exit(127) 2958 } 2959 sys_exit(0) 2960 } 2961 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 2962 hc_puts(" (fresh portmap launched in background; call `portmap` again in ~15s to read the result)\n" as *u8) 2963 return 0 2964} 2965// trackerrefresh: GROW the tracker list from live public sources (nx_tracker_refresh over sovereign TLS, 2966// merged+deduped into trackers.txt). "grow, not static" -- schedule daily; workers read trackers.txt on spawn. 2967const HC_TRACKERREFRESH_CMD: *u8 = "cd /volume1/ai/torrent && mkdir -p data && chmod +x ./nx_tracker_refresh.elf 2>/dev/null; : > /tmp/trackerrefresh.out; ./nx_tracker_refresh.elf /volume1/ai/torrent/trackers.txt >> /tmp/trackerrefresh.out 2>&1" as *u8 2968func cmd_trackerrefresh() -> i64 { 2969 let pid: i64 = sys_fork() 2970 if pid == 0 { 2971 let argv: *i64 = sys_mmap(32) as *i64; argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_TRACKERREFRESH_CMD as i64; argv[3]=0 2972 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2973 sys_execve_clean(HC_SH, argv, envp); sys_exit(127) 2974 } 2975 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 2976 hc_puts("=== trackerrefresh: grow trackers.txt from live public lists ===\n" as *u8) 2977 let lb: *u8 = sys_mmap(HC_MAGIC_65536); let n: i64 = hc_read_cmdline("/tmp/trackerrefresh.out" as *u8, lb, HC_MAGIC_65536) 2978 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(no output)\n" as *u8) } 2979 return 0 2980} 2981// reseed: register EVERY completed download (both areas) into the seed registry so we SHARE our whole 2982// library back (nx_torrent_reseed computes info_hash=SHA1(meta), plen, total=.part-size; idempotent). 2983const HC_RESEED_CMD: *u8 = "cd /volume1/ai/torrent && chmod +x ./nx_torrent_reseed.elf 2>/dev/null; : > /tmp/reseed.out; ./nx_torrent_reseed.elf /volume1/ai/torrent/media /volume1/ai/torrent/seed_index.conf >> /tmp/reseed.out 2>&1; ./nx_torrent_reseed.elf /volume1/ai/torrent/gallery /volume1/ai/torrent/seed_index.conf >> /tmp/reseed.out 2>&1" as *u8 2984func cmd_reseed() -> i64 { 2985 let pid: i64 = sys_fork() 2986 if pid == 0 { 2987 let argv: *i64 = sys_mmap(32) as *i64; argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_RESEED_CMD as i64; argv[3]=0 2988 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 2989 sys_execve_clean(HC_SH, argv, envp); sys_exit(127) 2990 } 2991 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 2992 hc_puts("=== reseed: register all completed downloads for sharing ===\n" as *u8) 2993 let lb: *u8 = sys_mmap(HC_MAGIC_65536); let n: i64 = hc_read_cmdline("/tmp/reseed.out" as *u8, lb, HC_MAGIC_65536) 2994 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(no output)\n" as *u8) } 2995 return 0 2996} 2997// kickworkers: SIGKILL every torrent download WORKER (nx_torrent_get.sov.elf). kicktorrent only kills the 2998// daemon; workers are double-forked (reparented to init) and survive -> a stale/buggy worker keeps flailing 2999// on a download. After a worker-binary fix, kill the old workers then kicktorrent so autoresume respawns the 3000// NEW binary. NEVER-BRICK: SIGKILL only; the donefile persists progress so the resumed worker continues. 3001func cmd_kickworkers() -> i64 { 3002 hc_puts("[hostctl] kickworkers: SIGKILL all torrent download workers (nx_torrent_get.sov.elf)\n" as *u8) 3003 let k: i64 = proc_kill_by_name("nx_torrent_get.sov.elf" as *u8, 9) 3004 hc_puts(" killed worker procs=" as *u8); hc_putn(k); hc_puts(" (kicktorrent next -> autoresume respawns the NEW worker binary; donefile keeps progress)\n" as *u8) 3005 return 0 3006} 3007// torstat: run nx_torrent_seedeval on every active download -> per-torrent SCARCE/IGNORING/COMPLETE verdict. 3008// Reads root-owned media/<btih>/download.* (hostctl is root), so it works where SFTP-as-elderwesto can't. 3009func cmd_torstat() -> i64 { 3010 let pid: i64 = sys_fork() 3011 if pid == 0 { 3012 let argv: *i64 = sys_mmap(32) as *i64; argv[0]=HC_SH as i64; argv[1]=HC_DASHC as i64; argv[2]=HC_TORSTAT_CMD as i64; argv[3]=0 3013 let envp: *i64 = sys_mmap(16) as *i64; envp[0]="PATH=/usr/bin:/bin" as *u8 as i64; envp[1]=0 3014 sys_execve_clean(HC_SH, argv, envp); sys_exit(127) 3015 } 3016 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 3017 hc_puts("=== torstat: seedeval per active download (media/<btih>) ===\n" as *u8) 3018 let lb: *u8 = sys_mmap(HC_MAGIC_262144); let n: i64 = hc_read_cmdline("/tmp/torstat.out" as *u8, lb, HC_MAGIC_262144) 3019 if n > 0 { sys_write(1, lb, n) } else { hc_puts("(no output -- media/torrents.idx empty or missing?)\n" as *u8) } 3020 return 0 3021} 3022// kickseedann: SIGKILL the DHT announcer -> hc_guard_seedann respawns it (fresh registry read + immediate announce sweep). 3023func cmd_kickseedann() -> i64 { 3024 hc_puts("[hostctl] kickseedann: SIGKILL the DHT announcer -> hc_guard_seedann respawns it (immediate re-announce)\n" as *u8) 3025 let k: i64 = proc_kill_by_name("nx_seed_announce_all.elf" as *u8, 9) 3026 hc_puts(" killed announcer procs=" as *u8); hc_putn(k); hc_puts(" (guard respawns; a sweep announces every registered info_hash)\n" as *u8) 3027 if k == 0 { hc_puts(" NOTE: no announcer matched -- guard will start it on the next poll (fail-safe)\n" as *u8) } 3028 return 0 3029} 3030 3031// kicktorrentgw: SURGICAL one-shot for the torrent OPAQUE gateway (:18793). Kill it by name; the guard 3032// (hc_guard_torrent_gw) respawns the NEW binary. The one-shot cmdline "nx_hostctl kicktorrentgw" never 3033// contains "nx_torrent_gw.elf" -> no self-match. Use after staging a new gateway binary (.new + mv). 3034func cmd_kicktorrentgw() -> i64 { 3035 hc_puts("[hostctl] kicktorrentgw: SIGKILL the torrent OPAQUE gateway (:18793) -> the guard respawns the NEW binary\n" as *u8) 3036 let k: i64 = proc_kill_by_name("nx_torrent_gw.elf" as *u8, 9) 3037 hc_puts(" killed gateway procs=" as *u8); hc_putn(k); hc_puts(" (supervisor guard respawns nx_torrent_gw)\n" as *u8) 3038 if k == 0 { hc_puts(" NOTE: no gateway matched -- nothing to kick (not running here?)\n" as *u8) } 3039 return 0 3040} 3041 3042// One-shot gallery gateway deploy: atomic-publish the staged .new gateway binary, then kill the running gateway 3043// -> the supervisor (the bash loop, OR this nx_hostctl once it supervises the gallery) respawns the NEW binary. 3044// killdurindex: SIGKILL the duration-index batch (+ its ts_dur child). A boot-spawn (now removed) forked the 3045// durindex WITHOUT close-on-exec, so it inherited the gallery's :18090 LISTENING socket and kept it open after 3046// the gallery was killed -> every new gallery serve hit EADDRINUSE on bind -> exit -> crash-loop. Killing it 3047// frees :18090; the supervisor guard then brings nx_gallery_serve back. Surgical one-shot (mirrors kicktorrent). 3048func cmd_killdurindex() -> i64 { 3049 hc_puts("[hostctl] killdurindex: SIGKILL nx_galx_durindex + nx_ts_dur (frees the :18090 socket fd they inherited)\n" as *u8) 3050 let k1: i64 = proc_kill_by_name("nx_galx_durindex.elf" as *u8, 9) 3051 let k2: i64 = proc_kill_by_name("nx_ts_dur.elf" as *u8, 9) 3052 hc_puts(" killed durindex=" as *u8); hc_putn(k1); hc_puts(" ts_dur=" as *u8); hc_putn(k2); hc_puts(" -> :18090 freed; the guard respawns the gallery serve\n" as *u8) 3053 return 0 3054} 3055// Launch the duration-index batch the PROPER ecosystem way: a detached control-plane one-shot, NOT a serve 3056// boot-spawn. setsid (survives the ssh session) + CLOSE every inherited fd 3..255 (the ssh channel and any 3057// sockets -> the long-lived batch holds NOTHING; this is exactly the hygiene the boot-spawn lacked) + flock 3058// single-instance (in the durindex itself) + resumable. cwd /volume1/ai/galx; chmod the elfs +x (recv lands 0644). 3059func hc_spawn_durindex_job() -> i64 { 3060 let pid: i64 = sys_fork() 3061 if pid == 0 { 3062 nx_setsid() 3063 sys_chdir("/volume1/ai/galx" as *u8) 3064 var f: i64 = 3; while f < 256 { sys_close(f); f = f + 1 } 3065 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 3066 let lg: i64 = sys_openat_append("galx_durindex.log" as *u8, 0x1a4) 3067 if dn >= 0 { sys_dup3(dn, 0, 0); sys_close(dn) } 3068 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0); sys_close(lg) } 3069 nx_chmod("./nx_galx_durindex.elf" as *u8, 0x1ed) 3070 nx_chmod("./nx_ts_dur.elf" as *u8, 0x1ed) 3071 let av: *i64 = sys_mmap(64) as *i64 3072 av[0] = "./nx_galx_durindex.elf" as *u8 as i64 3073 av[1] = "knowledge/status/galx_vid_paths.tsv" as *u8 as i64 3074 av[2] = "./nx_ts_dur.elf" as *u8 as i64 3075 av[3] = "knowledge/status/galx_dur.raw" as *u8 as i64 3076 av[4] = 0 3077 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3078 sys_execve_clean("./nx_galx_durindex.elf" as *u8, av, envp) 3079 sys_exit(127) 3080 } 3081 return pid 3082} 3083// galxpipeline: detached analysis-on-INGEST pass (operator 2026-07-14, API/MCP-native media derivation). 3084// Runs nx_media_ingest_pipeline (thumbnail prewarm + NXVI duration/keyframe/markers) from /volume1/ai/galx so 3085// newly-found media gets its thumbnail + duration without a manual step. Mirrors hc_spawn_durindex_job exactly. 3086func hc_spawn_pipeline_job() -> i64 { 3087 let pid: i64 = sys_fork() 3088 if pid == 0 { 3089 nx_setsid() 3090 sys_chdir("/volume1/ai/galx" as *u8) 3091 var f: i64 = 3; while f < 256 { sys_close(f); f = f + 1 } 3092 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 3093 let lg: i64 = sys_openat_append("galx_pipeline.log" as *u8, 0x1a4) 3094 if dn >= 0 { sys_dup3(dn, 0, 0); sys_close(dn) } 3095 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0); sys_close(lg) } 3096 nx_chmod("./nx_media_ingest_pipeline.elf" as *u8, 0x1ed) 3097 let av: *i64 = sys_mmap(64) as *i64 3098 av[0] = "./nx_media_ingest_pipeline.elf" as *u8 as i64 3099 av[1] = 0 3100 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3101 sys_execve_clean("./nx_media_ingest_pipeline.elf" as *u8, av, envp) 3102 sys_exit(127) 3103 } 3104 return pid 3105} 3106// galxidxrun (MV-2; the SEEK half of "seek is broken"): detached NXVI keyframe-index BACKFILL over every 3107// recording lacking one. The index was built LAZILY on first play, so ~98% of recordings had no scrub on 3108// first open -- measured 8,821 of 76,018 indexed while the durindex batch precedent sits at 62,100. Mirrors 3109// hc_spawn_pipeline_job exactly (setsid + fds closed + own log). NEWEST-FIRST by the organ's own design; 3110// sequential fork+wait = bounded IO; idempotent (NXVI v2 skipped) = re-run CONTINUES, never restarts. 3111// DECLARED LIMIT: nx_ts_index parses H.264 only -- HEVC recordings stay unindexed until the MV-4 arc. 3112func hc_spawn_idxbackfill_job() -> i64 { 3113 let pid: i64 = sys_fork() 3114 if pid == 0 { 3115 nx_setsid() 3116 sys_chdir("/volume1/ai/galx" as *u8) 3117 var f: i64 = 3; while f < 256 { sys_close(f); f = f + 1 } 3118 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 3119 let lg: i64 = sys_openat_append("galx_idxbackfill.log" as *u8, 0x1a4) 3120 if dn >= 0 { sys_dup3(dn, 0, 0); sys_close(dn) } 3121 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0); sys_close(lg) } 3122 nx_chmod("./nx_galx_idx_backfill.elf" as *u8, 0x1ed) 3123 nx_chmod("./nx_ts_index.elf" as *u8, 0x1ed) 3124 let av: *i64 = sys_mmap(64) as *i64 3125 av[0] = "./nx_galx_idx_backfill.elf" as *u8 as i64 3126 av[1] = 0 3127 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3128 sys_execve_clean("./nx_galx_idx_backfill.elf" as *u8, av, envp) 3129 sys_exit(127) 3130 } 3131 return pid 3132} 3133// galxidxkick (seq1065): recover a WEDGED backfill. SIGKILL on a D-state process does NOT land until its 3134// I/O completes (measured on the mvault walker, seq955), so the kick VERIFIES death before doing anything 3135// else -- a blind kill+respawn would put TWO writers on the same galxidx_<id>.idx files. Decision tree: 3136// still alive after kill -> WEDGED-UNKILLABLE: report + do nothing (retry later; nothing else is safe) 3137// dead, pipeline alive -> the pipeline's wait4 just returned; IT resumes iteration (no respawn here, 3138// avoids the dual-writer race between standalone + pipeline-forked backfill) 3139// dead, pipeline dead too -> respawn the standalone backfill via hc_spawn_idxbackfill_job 3140func cmd_galxidxkick() -> i64 { 3141 hc_puts("[hostctl] galxidxkick: kill the (possibly wedged) NXVI backfill, verify death, resume safely\n" as *u8) 3142 let k: i64 = proc_kill_by_name("nx_galx_idx_backfill.elf" as *u8, 9) 3143 hc_puts(" SIGKILL sent to procs=" as *u8); hc_putn(k); hc_puts("\n" as *u8) 3144 sys_sleep_ms(800) 3145 if proc_alive_by_name("nx_galx_idx_backfill.elf" as *u8) == 1 { 3146 hc_puts(" VERDICT=WEDGED-UNKILLABLE: still alive after SIGKILL (D-state; the signal lands when its I/O completes). NOT respawning -- retry galxidxkick later.\n" as *u8) 3147 return 1 3148 } 3149 if proc_alive_by_name("nx_media_ingest_pipeline.elf" as *u8) == 1 { 3150 hc_puts(" VERDICT=KILLED; pipeline is alive and its wait4 just returned -> pipeline resumes iteration itself (no standalone respawn, avoids dual writers)\n" as *u8) 3151 return 0 3152 } 3153 hc_spawn_idxbackfill_job() 3154 hc_puts(" VERDICT=KILLED+RESPAWNED standalone (pipeline not running) -> galx_idxbackfill.log\n" as *u8) 3155 return 0 3156} 3157// galxpipelinekick (seq1065): recover a wedged ANALYSIS PIPELINE (thumbs + NXVI). Kills the pipeline AND its 3158// forked backfill child (killing only the parent would orphan a wedged child that keeps the guard matching), 3159// verifies BOTH deaths, then respawns the pipeline fresh. Same D-state honesty as galxidxkick. 3160func cmd_galxpipelinekick() -> i64 { 3161 hc_puts("[hostctl] galxpipelinekick: kill pipeline + backfill child, verify death, respawn pipeline\n" as *u8) 3162 let kp: i64 = proc_kill_by_name("nx_media_ingest_pipeline.elf" as *u8, 9) 3163 let kb: i64 = proc_kill_by_name("nx_galx_idx_backfill.elf" as *u8, 9) 3164 hc_puts(" SIGKILL sent: pipeline=" as *u8); hc_putn(kp); hc_puts(" backfill=" as *u8); hc_putn(kb); hc_puts("\n" as *u8) 3165 sys_sleep_ms(800) 3166 var still: i64 = 0 3167 if proc_alive_by_name("nx_media_ingest_pipeline.elf" as *u8) == 1 { still = 1 } 3168 if proc_alive_by_name("nx_galx_idx_backfill.elf" as *u8) == 1 { still = 1 } 3169 if still == 1 { 3170 hc_puts(" VERDICT=WEDGED-UNKILLABLE: a member is still alive after SIGKILL (D-state). NOT respawning -- retry later.\n" as *u8) 3171 return 1 3172 } 3173 hc_spawn_pipeline_job() 3174 hc_puts(" VERDICT=KILLED+RESPAWNED pipeline (thumb prewarm + NXVI resume from scratch pass; idempotent per item) -> galx_pipeline.log\n" as *u8) 3175 return 0 3176} 3177func cmd_galxidxrun() -> i64 { 3178 hc_puts("[hostctl] galxidxrun: detached NXVI keyframe-seek backfill (newest-first, idempotent, resumable) -> /volume1/ai/galx/galx_idxbackfill.log\n" as *u8) 3179 if proc_alive_by_name("nx_galx_idx_backfill.elf" as *u8) == 1 { hc_puts(" already running -> no-op (idempotent)\n" as *u8); return 0 } 3180 hc_spawn_idxbackfill_job() 3181 hc_puts(" spawned detached\n" as *u8) 3182 return 0 3183} 3184func cmd_galxpipeline() -> i64 { 3185 hc_puts("[hostctl] galxpipeline: detached analysis-on-ingest pass (thumbnail prewarm + NXVI duration/markers) from /volume1/ai/galx -- idempotent, resumable\n" as *u8) 3186 if proc_alive_by_name("nx_media_ingest_pipeline.elf" as *u8) == 1 { hc_puts(" already running -> no-op (idempotent)\n" as *u8); return 0 } 3187 hc_spawn_pipeline_job() 3188 hc_puts(" spawned -> progress in /volume1/ai/galx/galx_pipeline.log; re-run to resume\n" as *u8) 3189 return 0 3190} 3191// SEARCH PIPELINE ON THE NAS (2026-07-23, operator: "i want the NAS doing these things not the laptop where 3192// we dev"). Detached one-shot run of a search-index pipeline organ against the LIVE web shard at 3193// nishihost/knowledge/store/dp-web-pub-. Mirrors hc_spawn_pipeline_job EXACTLY (the proven setsid pattern; 3194// the deep-fork codegen edge only bites re-forking daemons like sites.elf, not one-shot organs). The organs 3195// are mmap-open (measured pagerank ~4.2GB RSS) so they fit the NAS. Safe-by-construction: pagerank APPENDS 3196// pr: segments (rule-13), compact VERIFIES byte-identical before its atomic manifest swap + keeps superseded 3197// files. a2=0 for a 2-arg organ (compact), else the 3rd argv. Log -> knowledge/status/searchpipe.log. 3198// Address-space ceiling for detached search-pipeline jobs. 16 GiB = ~4x the largest MEASURED legitimate 3199// footprint (pagerank ~4.2GB) and well under the 36GB box, so a normal job never notices and a runaway dies 3200// long before the host is endangered. DATA, not taste: raise it here if a real job ever needs more, and 3201// prefer moving it to config if a second consumer appears. 3202const HC_JOB_AS_CAP: i64 = 17179869184 3203func hc_spawn_searchpipe_job(organ: *u8, a1: *u8, a2: *u8) -> i64 { 3204 let pid: i64 = sys_fork() 3205 if pid == 0 { 3206 nx_setsid() 3207 sys_chdir("/volume1/homes/elderwesto/nishihost" as *u8) 3208 var f: i64 = 3; while f < 256 { sys_close(f); f = f + 1 } 3209 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 3210 let lg: i64 = sys_openat_append("knowledge/status/searchpipe.log" as *u8, 0x1a4) 3211 if dn >= 0 { sys_dup3(dn, 0, 0); sys_close(dn) } 3212 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0); sys_close(lg) } 3213 nx_chmod(organ, 0x1ed) 3214 let av: *i64 = sys_mmap(64) as *i64 3215 av[0] = organ as i64 3216 av[1] = a1 as i64 3217 av[2] = a2 as i64 3218 av[3] = 0 3219 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3220 // MEMORY ENVELOPE (2026-07-30). THREE organs drove this host to the OOM edge in ONE session -- 3221 // nx_ssdf ~28GB, nx_mvault ~7.3GB, nx_web_shard_compact ~21GB and still climbing at +88MB/12s with 3222 // 536MB free -- and every time the COLLATERAL was the control plane: mgmt and the tools daemon 3223 // stopped answering, /api/build refused itself, and once sshd could not even complete a handshake. 3224 // The comment above sized these jobs by hope ("~4.2GB RSS so they fit the NAS"); hope is not a limit. 3225 // Cap the CHILD's address space before exec so a runaway JOB dies instead of the HOST. 3226 // ADOPTION, NOT INVENTION: nx_prlimit + RLIMIT_AS already existed in nx_syscalls, and 3227 // nx_supervised_dispatch already proves this exact fork -> cap-self -> exec shape in its gate. It was 3228 // simply never wired at the spawner where production jobs actually run -- this session's recurring 3229 // meta-defect. Applied at the CHOKEPOINT so every searchpipe job inherits it, not per leaf organ. 3230 // Set on SELF (pid 0) after fork, before execve: the limit survives exec and a non-root process 3231 // cannot raise a lowered hard limit back, so the exec'd image cannot opt out. 3232 let rlim: *i64 = sys_mmap(16) as *i64 3233 rlim[0] = HC_JOB_AS_CAP 3234 rlim[1] = HC_JOB_AS_CAP 3235 nx_prlimit(0, RLIMIT_AS, rlim as *u8, 0 as *u8) 3236 sys_execve_clean(organ, av, envp) 3237 sys_exit(127) 3238 } 3239 return pid 3240} 3241func cmd_searchpagerank() -> i64 { 3242 hc_puts("[hostctl] searchpagerank: detached PageRank build on the NAS live web shard -> pr: priors (mmap-open, ~4.2GB)\n" as *u8) 3243 if proc_alive_by_name("nx_pagerank_build.elf" as *u8) == 1 { hc_puts(" already running -> no-op (idempotent)\n" as *u8); return 0 } 3244 if proc_alive_by_name("nx_web_shard_compact.elf" as *u8) == 1 { hc_puts(" a compaction is running -> defer (no-op)\n" as *u8); return 0 } 3245 hc_spawn_searchpipe_job("./_offc/nx_pagerank_build.elf" as *u8, "web" as *u8, "60" as *u8) 3246 hc_puts(" spawned -> progress in knowledge/status/searchpipe.log\n" as *u8) 3247 return 0 3248} 3249func cmd_searchcompact() -> i64 { 3250 hc_puts("[hostctl] searchcompact: detached web-shard compaction on the NAS -> fold segments (verifies before swap)\n" as *u8) 3251 if proc_alive_by_name("nx_web_shard_compact.elf" as *u8) == 1 { hc_puts(" already running -> no-op (idempotent)\n" as *u8); return 0 } 3252 if proc_alive_by_name("nx_pagerank_build.elf" as *u8) == 1 { hc_puts(" a pagerank build is running -> defer (no-op)\n" as *u8); return 0 } 3253 hc_spawn_searchpipe_job("./_offc/nx_web_shard_compact.elf" as *u8, "web" as *u8, 0 as *u8) 3254 hc_puts(" spawned -> progress in knowledge/status/searchpipe.log\n" as *u8) 3255 return 0 3256} 3257// searchcrawl (2026-07-25, seq912): the COVERAGE half of the search pipeline, alongside searchpagerank 3258// (authority) and searchcompact (storage). Runs the EXISTING nx_web_crawl_step -- a frontier-persisted, 3259// resumable crawler whose frontier lives in the shard itself -- detached via the SAME spawner, so a batch 3260// survives the edge's ~15s backend-read timeout that silently defeats synchronous tools/call ingest. 3261// arg1 = max_pages this run; arg2 = optional seed url to bootstrap/inject into the frontier. 3262// Deliberately NOT a new ingest organ: the crawler, its pacing, dedup and outlink filtering already exist. 3263// FIXED-ARG by design: hostctl subs ignore caller argv (the allowlist pins them), so max_pages and the 3264// bootstrap seed are compiled in rather than passed -- no hostile-arg surface. The seed is idempotent: 3265// an existing fr: row just flips last-wins, so re-running only ever CONTINUES the crawl. 3266// seconds since a file was last written, or -1 if it does not exist. Uses the SAME sys_fstatat + struct 3267// stat offsets the docportal shard-cache signature already relies on (st_mtime sec @ +88 on x86_64) -- 3268// existing primitive, no new mechanism. Absent file => -1 => callers must treat it as 'cannot judge'. 3269func hc_file_age_s(path: *u8) -> i64 { 3270 let stb: *u8 = sys_mmap(160) 3271 if sys_fstatat(path, stb) != 0 { return 0 - 1 } 3272 let mtp: *i64 = ((stb as i64) + 88) as *i64 3273 let now: i64 = sys_now_realtime_sec() 3274 let age: i64 = now - mtp[0] 3275 if age < 0 { return 0 } 3276 return age 3277} 3278func cmd_searchcrawl() -> i64 { 3279 let maxp: *u8 = "400" as *u8 3280 // AIMED 2026-07-26: was a single Wikipedia hub, which filled the frontier with encyclopedia-adjacent 3281 // pages -- 113 of them landed and the judged ruler did not move BY ONE POINT, because the queries ask 3282 // for canonical sites (health agency, tax authority, kernel archive) that hub never links to. Now a 3283 // 44-domain CURATED list, selected by a documented rule that never mentions the benchmark. Re-seeding 3284 // is idempotent (a known frontier url is skipped), so every run just continues the crawl. 3285 let seed: *u8 = "knowledge/status/canonical_seeds.txt" as *u8 3286 hc_puts("[hostctl] searchcrawl: detached resumable web crawl on the NAS (frontier lives in the shard; re-run to continue)\n" as *u8) 3287 // STALENESS ESCAPE (seq961, a defect in my own first cut): a bare proc_alive guard turns ONE hang into 3288 // PERMANENT unavailability -- a stalled crawler holds the lock forever and the leg is wedged with no way 3289 // out over the API. Liveness is judged by PROGRESS (searchpipe.log mtime), not by the process existing: 3290 // alive AND log older than HC_CRAWL_STALE_S => reap it (the same hc_kill_by_cmdline every kick sub uses) 3291 // and fall through to a fresh spawn. Alive AND progressing => genuine no-op, still idempotent. 3292 if proc_alive_by_name("nx_web_crawl_step.elf" as *u8) == 1 { 3293 let age: i64 = hc_file_age_s("knowledge/status/searchpipe.log" as *u8) 3294 if age >= 0 { if age < HC_CRAWL_STALE_S { 3295 hc_puts(" already running and progressing (log age " as *u8); hc_putn(age); hc_puts("s) -> no-op (idempotent)\n" as *u8) 3296 return 0 3297 } } 3298 hc_puts(" STALLED: alive but no log progress for " as *u8); hc_putn(age); hc_puts("s -> reaping and respawning\n" as *u8) 3299 let kk: i64 = hc_kill_by_cmdline("nx_web_crawl_step.elf" as *u8) 3300 hc_puts(" reaped procs=" as *u8); hc_putn(kk); hc_puts("\n" as *u8) 3301 } 3302 if proc_alive_by_name("nx_web_shard_compact.elf" as *u8) == 1 { hc_puts(" a compaction is running -> defer (no-op)\n" as *u8); return 0 } 3303 if proc_alive_by_name("nx_pagerank_build.elf" as *u8) == 1 { hc_puts(" a pagerank build is running -> defer (no-op)\n" as *u8); return 0 } 3304 hc_spawn_searchpipe_job("./nx_web_crawl_step.elf" as *u8, maxp, seed) 3305 hc_puts(" spawned -> progress in knowledge/status/searchpipe.log\n" as *u8) 3306 return 0 3307} 3308func cmd_durindexrun() -> i64 { 3309 hc_puts("[hostctl] durindexrun: detached duration-index batch (setsid + ALL fds closed + flock single-instance) from /volume1/ai/galx -- the proper one-shot, NOT a serve boot-spawn\n" as *u8) 3310 if proc_alive_by_name("nx_galx_durindex.elf" as *u8) == 1 { hc_puts(" already running -> no-op (resumable+idempotent)\n" as *u8); return 0 } 3311 hc_spawn_durindex_job() 3312 hc_puts(" spawned -> appends knowledge/status/galx_dur.raw; progress in /volume1/ai/galx/galx_durindex.log; re-run to resume\n" as *u8) 3313 return 0 3314} 3315func hc_count_lines(path: *u8) -> i64 { 3316 let szp: *i64 = sys_mmap(16) as *i64 3317 let b: *u8 = sys_read_file(path, szp) 3318 if (b as i64) == 0 { return 0 - 1 } 3319 let sz: i64 = szp[0] 3320 var n: i64 = 0; var i: i64 = 0 3321 while i < sz { if b[i] == (10 as u8) { n = n + 1 } i = i + 1 } 3322 return n 3323} 3324func hc_tail(path: *u8, nb: i64) -> i64 { 3325 let szp: *i64 = sys_mmap(16) as *i64 3326 let b: *u8 = sys_read_file(path, szp) 3327 if (b as i64) == 0 { hc_puts(" (log absent)\n" as *u8); return 0 } 3328 let sz: i64 = szp[0] 3329 var start: i64 = sz - nb; if start < 0 { start = 0 } 3330 sys_write(1, ((b as i64) + start) as *u8, sz - start); hc_puts("\n" as *u8) 3331 return 0 3332} 3333// durindexstat: report runtime-sort data readiness = galx_dur.raw durations done vs vid_paths total. 3334func cmd_durindexstat() -> i64 { 3335 let dn: i64 = hc_count_lines("/volume1/ai/galx/knowledge/status/galx_dur.raw" as *u8) 3336 let pn: i64 = hc_count_lines("/volume1/ai/galx/knowledge/status/galx_vid_paths.tsv" as *u8) 3337 hc_puts("[hostctl] durindexstat: galx_dur.raw durations=" as *u8); hc_putn(dn); hc_puts(" / vid_paths total=" as *u8); hc_putn(pn); hc_puts("\n" as *u8) 3338 if dn < 0 { hc_puts(" galx_dur.raw ABSENT -> runtime-sort shows building (run durindexrun)\n" as *u8) } 3339 else { if dn >= pn { hc_puts(" -> COMPLETE: runtime-sort fully populated\n" as *u8) } else { hc_puts(" -> PARTIAL: runtime-sort works on the indexed subset; durindexrun to continue\n" as *u8) } } 3340 hc_puts(" --- galx_durindex.log tail ---\n" as *u8) 3341 hc_tail("/volume1/ai/galx/galx_durindex.log" as *u8, 700) 3342 return 0 3343} 3344// copy the n-th (0-indexed) line of buf into out (null-terminated, CR stripped); returns len or -1. 3345func hc_nth_line(buf: *u8, sz: i64, n: i64, out: *u8) -> i64 { 3346 var line: i64 = 0; var i: i64 = 0; var ls: i64 = 0 3347 while i <= sz { 3348 var nl: i64 = 0; if i == sz { nl = 1 } else { if buf[i] == (10 as u8) { nl = 1 } } 3349 if nl == 1 { 3350 if line == n { var ce: i64 = i; if ce > ls { if buf[ce-1] == (13 as u8) { ce = ce - 1 } } var o: i64 = 0; var k: i64 = ls; while k < ce { out[o] = buf[k]; o = o + 1; k = k + 1 } out[o] = 0 as u8; return o } 3351 line = line + 1; ls = i + 1 3352 } 3353 i = i + 1 3354 } 3355 return 0 - 1 3356} 3357// tsdurtest: run nx_ts_dur on the video at vid_paths line 40302 (first non-done) + show its raw output -- to 3358// diagnose why the batch produced no durations for the non-.ts tail. 3359func cmd_tsdurtest() -> i64 { 3360 let szp: *i64 = sys_mmap(16) as *i64 3361 let b: *u8 = sys_read_file("/volume1/ai/galx/knowledge/status/galx_vid_paths.tsv" as *u8, szp) 3362 if (b as i64) == 0 { hc_puts("no vid_paths\n" as *u8); return 0 } 3363 let path: *u8 = sys_mmap(HC_MAGIC_2048) 3364 if hc_nth_line(b, szp[0], HC_MAGIC_40302, path) < 0 { hc_puts("line 40302 not found\n" as *u8); return 0 } 3365 hc_puts("[tsdurtest] line 40302 path=" as *u8); hc_puts(path); hc_puts("\n" as *u8) 3366 let pid: i64 = sys_fork() 3367 if pid == 0 { 3368 sys_chdir("/volume1/ai/galx" as *u8) 3369 let of: i64 = sys_openat_wr("/tmp/tsdurtest.out" as *u8, 0x1a4) 3370 if of >= 0 { sys_dup3(of, 1, 0); sys_dup3(of, 2, 0) } 3371 nx_chmod("./nx_ts_dur.elf" as *u8, 0x1ed) 3372 let av: *i64 = sys_mmap(32) as *i64; av[0] = "./nx_ts_dur.elf" as *u8 as i64; av[1] = path as i64; av[2] = 0 3373 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3374 sys_execve_clean("./nx_ts_dur.elf" as *u8, av, envp); sys_exit(127) 3375 } 3376 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 3377 hc_puts(" --- nx_ts_dur output ---\n" as *u8) 3378 hc_tail("/tmp/tsdurtest.out" as *u8, 400) 3379 return 0 3380} 3381// durbinbuild: compact galx_dur.raw -> galx_dur.bin so the gallery's /vid/<id>/dur seekbar lookup is an 3382// O(1) 8-byte read instead of a 16MB first+last-PCR re-scan. Run after the durindex appends new durations; 3383// the gallery reads the fresh .bin with NO restart (mirrors galx_vid_off.bin). Short-lived: wait + show. 3384func cmd_durbinbuild() -> i64 { 3385 hc_puts("[hostctl] durbinbuild: compacting galx_dur.raw -> galx_dur.bin (O(1) /dur seekbar lookup)\n" as *u8) 3386 let bf: i64 = sys_openat_rd("/volume1/ai/galx/nx_galx_durbin.elf" as *u8) 3387 if bf < 0 { hc_puts(" ERROR nx_galx_durbin.elf MISSING -> re-send it\n" as *u8); return 0 } 3388 let bsz: i64 = sys_lseek(bf, 0, 2); sys_close(bf) 3389 hc_puts(" binary present, size=" as *u8); hc_putn(bsz); hc_puts("\n" as *u8) 3390 let pid: i64 = sys_fork() 3391 if pid == 0 { 3392 sys_chdir("/volume1/ai/galx" as *u8) 3393 let of: i64 = sys_openat_wr("/tmp/durbin.out" as *u8, 0x1a4) 3394 if of >= 0 { sys_dup3(of, 1, 0); sys_dup3(of, 2, 0) } 3395 nx_chmod("./nx_galx_durbin.elf" as *u8, 0x1ed) 3396 let av: *i64 = sys_mmap(32) as *i64; av[0] = "./nx_galx_durbin.elf" as *u8 as i64; av[1] = 0 3397 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3398 sys_execve_clean("./nx_galx_durbin.elf" as *u8, av, envp); sys_exit(127) 3399 } 3400 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 3401 let raw: i64 = st[0] 3402 hc_puts(" child status raw=" as *u8); hc_putn(raw); hc_puts(" exit=" as *u8); hc_putn((raw>>8)&0xff); hc_puts(" sig=" as *u8); hc_putn(raw&0x7f); hc_puts("\n" as *u8) 3403 hc_tail("/tmp/durbin.out" as *u8, 400) 3404 let cf: i64 = sys_openat_rd("/volume1/ai/galx/knowledge/status/galx_dur.bin" as *u8) 3405 if cf < 0 { hc_puts(" galx_dur.bin NOT created\n" as *u8) } else { let csz: i64 = sys_lseek(cf, 0, 2); sys_close(cf); hc_puts(" galx_dur.bin size=" as *u8); hc_putn(csz); hc_puts(" bytes (= 16 + 8*n)\n" as *u8) } 3406 return 0 3407} 3408// streaming copy src->dst (0755). Returns 0 ok, <0 fail. (staging the freshly-built elf for /api/deploy) 3409func hc_copyfile(src: *u8, dst: *u8) -> i64 { 3410 let sfd: i64 = sys_openat_rd(src); if sfd < 0 { return 0 - 1 } 3411 let dfd: i64 = sys_openat_wr(dst, 0x1ed); if dfd < 0 { sys_close(sfd); return 0 - 2 } 3412 let buf: *u8 = sys_mmap(HC_MAGIC_1048576); var go: i64 = 1 3413 while go == 1 { let nr: i64 = sys_read(sfd, buf, HC_MAGIC_1048576); if nr <= 0 { go = 0 } else { sys_write(dfd, buf, nr) } } 3414 sys_close(sfd); sys_close(dfd); return 0 3415} 3416// buildrun <name>: COMPILE a target ON THE NAS. cd buildroot (has _offc/ toolchain + runtime/ synced source), 3417// run _offc/nx_sov_build_run.elf <name> --build-only -> buildroot/_build/<name>.sov.elf (or, for 3418// pre-flock builder versions, /tmp/<name>.sov.elf -- BOTH are probed, see seq1315 below), then STAGE it 3419// as nishihost/<name>.sov.elf.new for /api/deploy. The build half of build-over-API. 3420// Additive + never-brick (#26): only writes the build dir + the .new staging file; never a live binary. 3421// ⚠These three lines said "/tmp/" only, until 2026-07-30. The builder had moved and the doc had not -- 3422// the same stale-doc defect that hid the seq1315 brick hazard in nx_sov_build_run's own header. When an 3423// artifact path changes, the READER, the WRITER and BOTH their headers move together or none do. 3424func cmd_buildrun(name: *u8) -> i64 { 3425 hc_puts("[hostctl] buildrun: compile '" as *u8); hc_puts(name); hc_puts("' on the NAS (nx_cc -> nxasm)\n" as *u8) 3426 let bf: i64 = sys_openat_rd("/volume1/homes/elderwesto/nishihost/buildroot/_offc/nx_sov_build_run.elf" as *u8) 3427 if bf < 0 { hc_puts(" ERROR toolchain missing: buildroot/_offc/nx_sov_build_run.elf (deploy the toolchain first)\n" as *u8); return 1 } 3428 sys_close(bf) 3429 let pid: i64 = sys_fork() 3430 if pid == 0 { 3431 sys_chdir("/volume1/homes/elderwesto/nishihost/buildroot" as *u8) 3432 let of: i64 = sys_openat_wr("/tmp/buildrun.out" as *u8, 0x1a4) 3433 if of >= 0 { sys_dup3(of, 1, 0); sys_dup3(of, 2, 0) } 3434 nx_chmod("./_offc/nx_sov_build_run.elf" as *u8, 0x1ed) 3435 nx_chmod("./_offc/nx_cc_sovereign.elf" as *u8, 0x1ed) 3436 nx_chmod("./_offc/nxasm_x86_main.elf" as *u8, 0x1ed) 3437 let av: *i64 = sys_mmap(32) as *i64 3438 av[0] = "./_offc/nx_sov_build_run.elf" as *u8 as i64 3439 av[1] = name as i64 3440 av[2] = "--build-only" as *u8 as i64 3441 av[3] = 0 3442 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64; envp[1] = 0 3443 sys_execve_clean("./_offc/nx_sov_build_run.elf" as *u8, av, envp); sys_exit(127) 3444 } 3445 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0) 3446 let raw: i64 = st[0] 3447 hc_puts(" child exit=" as *u8); hc_putn((raw>>8)&0xff); hc_puts(" sig=" as *u8); hc_putn(raw&0x7f); hc_puts("\n" as *u8) 3448 hc_tail("/tmp/buildrun.out" as *u8, 600) 3449 // ARTIFACT-PATH PROBE -- ROOT FIX for seq1315 (2026-07-30). nx_sov_build_run moved its output from 3450 // /tmp/<n>.sov.elf to _build/<n>.sov.elf when per-target flock locking landed (concurrent sweeps were 3451 // overwriting each other's artifacts mid-build), but THIS reader was never updated. The two therefore 3452 // became an undeclared MATCHED PAIR: promoting the newer builder alone would make every /api/build in 3453 // the ecosystem fail "BUILD FAIL: no /tmp/<n>.sov.elf" for every seat, from a routine-looking toolchain 3454 // update. That coupling was found by reading the builder's (stale) header, not by any check. 3455 // 3456 // Probing BOTH dissolves the coupling permanently and is backward compatible in BOTH directions -- 3457 // either builder version works with either supervisor version -- so the toolchain can be updated one 3458 // binary at a time instead of as an all-or-nothing pair. Prefer _build/ (where the current builder 3459 // writes); fall back to /tmp/ (pre-flock builders). _build/ is relative to the buildroot the child 3460 // chdir'd into, so the parent must name it absolutely. 3461 let outp: *u8 = sys_mmap(256) 3462 var o: i64 = hc_cat(outp, 0, "/volume1/homes/elderwesto/nishihost/buildroot/_build/" as *u8) 3463 o = hc_cat(outp, o, name); o = hc_cat(outp, o, ".sov.elf" as *u8); outp[o] = 0 as u8 3464 var ef: i64 = sys_openat_rd(outp) 3465 if ef < 0 { 3466 o = hc_cat(outp, 0, "/tmp/" as *u8); o = hc_cat(outp, o, name); o = hc_cat(outp, o, ".sov.elf" as *u8); outp[o] = 0 as u8 3467 ef = sys_openat_rd(outp) 3468 } 3469 if ef < 0 { 3470 hc_puts(" BUILD FAIL: no artifact at buildroot/_build/" as *u8); hc_puts(name) 3471 hc_puts(".sov.elf nor /tmp/" as *u8); hc_puts(name); hc_puts(".sov.elf\n" as *u8) 3472 return 1 3473 } 3474 let esz: i64 = sys_lseek(ef, 0, 2) 3475 // ARTIFACT VALIDATION (2026-07-25). nx_sov_build_run CREATES /tmp/<name>.sov.elf even when nx_cc 3476 // COMPILE-FAILs (empty .s -> empty elf), so the `ef < 0` check above only ever caught a MISSING file -- 3477 // a 0-byte build sailed straight through as "BUILT size=0" -> STAGED -> "BUILDRUN OK" -> return 0. 3478 // That is an instrument reporting SUCCESS for a build that produced nothing, and it is load-bearing: 3479 // nx_dedup_migrate.nx:172 keys its ACCEPT on the literal string "BUILDRUN OK", so a failed compile was 3480 // scoring as a successful one in the self-emitting-code lane. Verify the ARTIFACT (size + ELF magic) -- 3481 // never the exit code alone -- and REFUSE TO STAGE garbage so a 0-byte .new can never reach a promote. 3482 var magic_ok: i64 = 0 3483 if esz > 4 { 3484 sys_lseek(ef, 0, 0) 3485 let hb: *u8 = sys_mmap(8) 3486 if sys_read(ef, hb, 4) == 4 { 3487 if hb[0] == (0x7f as u8) { if hb[1] == (69 as u8) { if hb[2] == (76 as u8) { if hb[3] == (70 as u8) { magic_ok = 1 } } } } 3488 } 3489 } 3490 sys_close(ef) 3491 hc_puts(" BUILT " as *u8); hc_puts(outp); hc_puts(" size=" as *u8); hc_putn(esz); hc_puts("\n" as *u8) 3492 if magic_ok == 0 { 3493 hc_puts(" BUILDRUN FAIL: artifact is not a valid ELF (compile produced nothing) -- NOT staged\n" as *u8) 3494 return 1 3495 } 3496 let stg: *u8 = sys_mmap(256); var s2: i64 = hc_cat(stg, 0, "/volume1/homes/elderwesto/nishihost/" as *u8); s2 = hc_cat(stg, s2, name); s2 = hc_cat(stg, s2, ".sov.elf.new" as *u8); stg[s2] = 0 as u8 3497 if hc_copyfile(outp, stg) != 0 { hc_puts(" STAGE FAIL\n" as *u8); return 1 } 3498 hc_puts(" STAGED -> " as *u8); hc_puts(stg); hc_puts(" (promote via /api/deploy)\n" as *u8) 3499 hc_puts(" BUILDRUN OK\n" as *u8) 3500 return 0 3501} 3502// buildsetup: create the NAS buildroot tree (buildroot/_offc for the toolchain + buildroot/runtime for the 3503// synced source). Idempotent (mkdir ignores EEXIST). Run once before deploying the toolchain / syncing source. 3504func cmd_buildsetup() -> i64 { 3505 sys_mkdir("/volume1/homes/elderwesto/nishihost/buildroot" as *u8, 0x1ed) 3506 sys_mkdir("/volume1/homes/elderwesto/nishihost/buildroot/_offc" as *u8, 0x1ed) 3507 sys_mkdir("/volume1/homes/elderwesto/nishihost/buildroot/runtime" as *u8, 0x1ed) 3508 let cf: i64 = sys_openat_rd("/volume1/homes/elderwesto/nishihost/buildroot/_offc" as *u8) 3509 if cf >= 0 { sys_close(cf); hc_puts("[hostctl] buildsetup: buildroot/{_offc,runtime} ready\n" as *u8); return 0 } 3510 hc_puts("[hostctl] buildsetup: FAILED to create buildroot\n" as *u8) 3511 return 1 3512} 3513// galxdurprobe: LIVE end-to-end proof of the /dur fast-path. Connect to the loopback gallery serve 3514// (127.0.0.1:18090, no auth -- the gateway does auth), GET /vid/0/dur, and assert a duration comes back. 3515// This proves the deployed gallery actually serves db_lookup(galx_dur.bin) over a real socket (and does 3516// NOT crash on a /dur request). No shell, no curl -- raw sovereign sockets from the control plane. 3517func cmd_galxdurprobe() -> i64 { 3518 hc_puts("[hostctl] galxdurprobe: GET 127.0.0.1:18090/vid/0/dur -> assert a duration is served\n" as *u8) 3519 let fd: i64 = sys_socket(2, 1, 0) 3520 if fd < 0 { hc_puts(" FAIL socket\n" as *u8); return 0 } 3521 let sa: *u8 = sys_mmap(16) 3522 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=70 as u8; sa[3]=170 as u8; sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8 3523 var z: i64 = 8; while z < 16 { sa[z]=0 as u8; z=z+1 } 3524 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { hc_puts(" FAIL connect :18090 (serve down?)\n" as *u8); sys_close(fd); return 0 } 3525 let req: *u8 = "GET /vid/0/dur HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n" as *u8 3526 var rl: i64 = 0; while req[rl]!=(0 as u8) { rl=rl+1 } 3527 sys_write(fd, req, rl) 3528 let buf: *u8 = sys_mmap(HC_MAGIC_16384); var tot: i64 = 0; var go: i64 = 1 3529 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, HC_MAGIC_16000-tot); if r <= 0 { go=0 } else { tot=tot+r } } 3530 sys_close(fd); buf[tot]=0 as u8 3531 var i: i64 = 0; var bs: i64 = 0 - 1 3532 while i + 3 < tot { 3533 var m4: i64 = 0 3534 if buf[i]==(13 as u8) { if buf[i+1]==(10 as u8) { if buf[i+2]==(13 as u8) { if buf[i+3]==(10 as u8) { m4=1 } } } } 3535 if m4 == 1 { bs=i+4; i=tot } else { i=i+1 } 3536 } 3537 if bs < 0 { hc_puts(" FAIL no \\r\\n\\r\\n body separator in response\n" as *u8); return 0 } 3538 var dur: i64 = 0; var j: i64 = bs 3539 while j < tot { let c: i64 = buf[j] as i64; if c < 48 { j=tot } else { if c > 57 { j=tot } else { dur=dur*10+(c-48); j=j+1 } } } 3540 hc_puts(" /vid/0/dur -> dur_ms=" as *u8); hc_putn(dur); hc_puts("\n" as *u8) 3541 if dur > 0 { hc_puts(" GALXDURPROBE PASS: gallery served a duration over loopback (fast-path LIVE, no crash)\n" as *u8) } 3542 else { hc_puts(" GALXDURPROBE: served dur=0 (recording 25000 may lack PCRs); the path works, value is zero\n" as *u8) } 3543 return 0 3544} 3545// connect to the loopback gallery serve, send `req` (null-terminated text), read the full response into 3546// buf[0..cap); set totp[0]=total; return the body offset (past CRLFCRLF) or -1. 3547func hc_loopback_req(req: *u8, buf: *u8, cap: i64, totp: *i64) -> i64 { 3548 let fd: i64 = sys_socket(2, 1, 0); if fd < 0 { return 0 - 1 } 3549 let sa: *u8 = sys_mmap(16) 3550 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=70 as u8; sa[3]=170 as u8; sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8 3551 var z: i64 = 8; while z < 16 { sa[z]=0 as u8; z=z+1 } 3552 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 } 3553 var rl: i64 = 0; while req[rl]!=(0 as u8) { rl=rl+1 } 3554 sys_write(fd, req, rl) 3555 var tot: i64 = 0; var go: i64 = 1 3556 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, cap-1-tot); if r <= 0 { go=0 } else { tot=tot+r } } 3557 sys_close(fd); buf[tot]=0 as u8; totp[0]=tot 3558 var i: i64 = 0; var bs: i64 = 0 - 1 3559 while i + 3 < tot { var m: i64 = 0; if buf[i]==(13 as u8) { if buf[i+1]==(10 as u8) { if buf[i+2]==(13 as u8) { if buf[i+3]==(10 as u8) { m=1 } } } } if m == 1 { bs=i+4; i=tot } else { i=i+1 } } 3560 return bs 3561} 3562// exact-compare the response body buf[bs..tot) to a null-terminated expected string. 3563func hc_body_eq(buf: *u8, bs: i64, tot: i64, exp: *u8) -> i64 { 3564 if bs < 0 { return 0 } 3565 var i: i64 = bs; var j: i64 = 0 3566 while exp[j] != (0 as u8) { if i >= tot { return 0 } if buf[i] != exp[j] { return 0 } i=i+1; j=j+1 } 3567 if i != tot { return 0 } 3568 return 1 3569} 3570// galxtagprobe: LIVE round-trip proof of the R4 tag store. POST /tag add -> GET /api/tags (expect 3571// ["nxprobe"]) -> POST /tag remove -> GET /api/tags (expect []). Host: 127.0.0.1:18090 + no Origin passes 3572// gs_same_origin. Throwaway id 999999 -> 2 additive lines in galx_tags.log, net no tag (last-wins). 3573func cmd_galxtagprobe() -> i64 { 3574 hc_puts("[hostctl] galxtagprobe: POST /tag -> GET /api/tags round-trip (tag write+read+last-wins LIVE)\n" as *u8) 3575 let buf: *u8 = sys_mmap(HC_MAGIC_16384); let tp: *i64 = sys_mmap(16) as *i64 3576 hc_loopback_req("POST /tag HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 25\r\nConnection: close\r\n\r\nid=999999&tag=nxprobe&v=1" as *u8, buf, HC_MAGIC_16384, tp) 3577 let b2: i64 = hc_loopback_req("GET /api/tags?id=999999 HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16384, tp) 3578 hc_puts(" after add -> " as *u8); if b2 >= 0 { sys_write(1, ((buf as i64)+b2) as *u8, tp[0]-b2) } hc_puts("\n" as *u8) 3579 let ok1: i64 = hc_body_eq(buf, b2, tp[0], "[\"nxprobe\"]" as *u8) 3580 let bf3: *u8 = sys_mmap(HC_MAGIC_16384); let tp3: *i64 = sys_mmap(16) as *i64 3581 let b3: i64 = hc_loopback_req("GET /api/tagged?tag=nxprobe HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, bf3, HC_MAGIC_16384, tp3) 3582 hc_puts(" by-tag -> " as *u8); if b3 >= 0 { sys_write(1, ((bf3 as i64)+b3) as *u8, tp3[0]-b3) } hc_puts("\n" as *u8) 3583 let ok1b: i64 = hc_body_eq(bf3, b3, tp3[0], "[\"999999\"]" as *u8) 3584 let bf2: *u8 = sys_mmap(HC_MAGIC_16384); let tp2: *i64 = sys_mmap(16) as *i64 3585 hc_loopback_req("POST /tag HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 25\r\nConnection: close\r\n\r\nid=999999&tag=nxprobe&v=0" as *u8, bf2, HC_MAGIC_16384, tp2) 3586 let b4: i64 = hc_loopback_req("GET /api/tags?id=999999 HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, bf2, HC_MAGIC_16384, tp2) 3587 hc_puts(" after remove -> " as *u8); if b4 >= 0 { sys_write(1, ((bf2 as i64)+b4) as *u8, tp2[0]-b4) } hc_puts("\n" as *u8) 3588 let ok2: i64 = hc_body_eq(bf2, b4, tp2[0], "[]" as *u8) 3589 if ok1 == 1 { if ok1b == 1 { if ok2 == 1 { hc_puts(" GALXTAGPROBE PASS: add->[\"nxprobe\"], by-tag->[\"999999\"], remove->[] over loopback (string-keyed tag store LIVE)\n" as *u8); return 0 } } } 3590 hc_puts(" GALXTAGPROBE FAIL (bodies above)\n" as *u8); return 0 3591} 3592// substring search in the response body buf[bs..tot). 3593func hc_body_has(buf: *u8, bs: i64, tot: i64, needle: *u8) -> i64 { 3594 if bs < 0 { return 0 } 3595 var nl: i64 = 0; while needle[nl]!=(0 as u8) { nl=nl+1 } 3596 if nl == 0 { return 0 } 3597 var i: i64 = bs 3598 while i + nl <= tot { 3599 var k: i64 = 0; var m: i64 = 1 3600 while k < nl { if buf[i+k] != needle[k] { m=0; k=nl } else { k=k+1 } } 3601 if m == 1 { return 1 } 3602 i = i + 1 3603 } 3604 return 0 3605} 3606// galxstreamprobe: MEASURE the .ts streaming path for recording 25000 (read-only -- it builds the normal .idx 3607// cache + forks the normal muxer, exactly as a first play would; no playback change). Times /segs, the lazy 3608// .idx build (the first-play stall), /init.mp4, and /seg (the per-segment fork+mux = the choppiness metric). 3609func cmd_galxstreamprobe() -> i64 { 3610 hc_puts("[hostctl] galxstreamprobe: measuring the .ts streaming path for recording 25000 (read-only)\n" as *u8) 3611 let buf: *u8 = sys_mmap(HC_MAGIC_16777232); let tp: *i64 = sys_mmap(16) as *i64 3612 let t0: i64 = sys_now_ms() 3613 let b1: i64 = hc_loopback_req("GET /vid/25000/segs HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16777216, tp) 3614 let t1: i64 = sys_now_ms() 3615 hc_puts(" /segs: " as *u8); hc_putn(t1-t0); hc_puts(" ms body=" as *u8); if b1>=0 { sys_write(1, ((buf as i64)+b1) as *u8, tp[0]-b1) } hc_puts("\n" as *u8) 3616 var ready: i64 = 0; if hc_body_has(buf, b1, tp[0], "\"ready\":1" as *u8) == 1 { ready = 1 } 3617 if ready == 0 { 3618 hc_puts(" .idx not ready -> lazy-index build spawned; polling to time the first-play stall...\n" as *u8) 3619 var waited: i64 = 0; var done: i64 = 0 3620 while done == 0 { 3621 if waited >= HC_MAGIC_90000 { done = 1; hc_puts(" .idx build NOT ready after 90s\n" as *u8) } else { 3622 sys_sleep_ms(300); waited = waited + 300 3623 let bp: i64 = hc_loopback_req("GET /vid/25000/segs HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16777216, tp) 3624 if hc_body_has(buf, bp, tp[0], "\"ready\":1" as *u8) == 1 { done = 1; ready = 1; hc_puts(" >>> .idx BUILD (first-play stall) ~" as *u8); hc_putn(waited); hc_puts(" ms\n" as *u8) } 3625 } 3626 } 3627 } 3628 if ready == 1 { 3629 let t2: i64 = sys_now_ms() 3630 hc_loopback_req("GET /vid/25000/init.mp4 HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16777216, tp) 3631 let t3: i64 = sys_now_ms() 3632 hc_puts(" /init.mp4: " as *u8); hc_putn(t3-t2); hc_puts(" ms (" as *u8); hc_putn(tp[0]); hc_puts(" bytes)\n" as *u8) 3633 let t4: i64 = sys_now_ms() 3634 hc_loopback_req("GET /vid/25000/seg?t=5000 HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16777216, tp) 3635 let t5: i64 = sys_now_ms() 3636 hc_puts(" /seg?t=5000: " as *u8); hc_putn(t5-t4); hc_puts(" ms (" as *u8); hc_putn(tp[0]); hc_puts(" bytes) <<< per-segment fork+mux cost\n" as *u8) 3637 let t6: i64 = sys_now_ms() 3638 hc_loopback_req("GET /vid/25000/seg?t=20000 HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16777216, tp) 3639 let t7: i64 = sys_now_ms() 3640 hc_puts(" /seg?t=20000: " as *u8); hc_putn(t7-t6); hc_puts(" ms\n" as *u8) 3641 let t8: i64 = sys_now_ms() 3642 hc_loopback_req("GET /vid/25000 HTTP/1.1\r\nHost: 127.0.0.1:18090\r\nConnection: close\r\n\r\n" as *u8, buf, HC_MAGIC_16777216, tp) 3643 let t9: i64 = sys_now_ms() 3644 hc_puts(" /vid DIRECT-REMUX (the cold first-play path): " as *u8); hc_putn(t9-t8); hc_puts(" ms for " as *u8); hc_putn(tp[0]); hc_puts(" B (~" as *u8); hc_putn(tp[0]*1000/((t9-t8)+1)/HC_MAGIC_1024); hc_puts(" KB/s; bitrate ~250KB/s so >>1x = the first-play streams smooth)\n" as *u8) 3645 hc_puts(" READ: /seg tens-of-ms => muxer fine; hundreds+ => fork-per-segment IS the choppiness\n" as *u8) 3646 } 3647 return 0 3648} 3649// galxgwprobe: read-only loopback GET 127.0.0.1:18190/gallery -- ISOLATES the /gallery 404 (gateway vs sites.elf). 3650// CONNECT-FAIL => gateway :18190 not accepting (down). CONNECTED-but-no-bytes => gateway accepts then doesn't 3651// answer (the bug). Answered + a status line => the gateway is FINE and the 404 is in sites.elf's /gallery route. 3652func cmd_galxgwprobe() -> i64 { 3653 hc_puts("[hostctl] galxgwprobe: loopback GET 127.0.0.1:18190/gallery (read-only -- does the gateway answer?)\n" as *u8) 3654 let fd: i64 = sys_socket(2, 1, 0) 3655 if fd < 0 { hc_puts(" socket fail\n" as *u8); return 0 } 3656 let sa: *u8 = sys_mmap(16) 3657 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=71 as u8; sa[3]=14 as u8; sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8 3658 var z: i64 = 8; while z < 16 { sa[z]=0 as u8; z=z+1 } 3659 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { hc_puts(" CONNECT FAIL -> gateway :18190 NOT accepting (down/not-bound)\n" as *u8); sys_close(fd); return 0 } 3660 let req: *u8 = "GET /gallery HTTP/1.1\r\nHost: 127.0.0.1:18190\r\nConnection: close\r\n\r\n" as *u8 3661 var rl: i64 = 0; while req[rl]!=(0 as u8) { rl=rl+1 } 3662 sys_write(fd, req, rl) 3663 let buf: *u8 = sys_mmap(HC_MAGIC_65536) 3664 var tot: i64 = 0; var go: i64 = 1 3665 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, HC_MAGIC_65535-tot); if r <= 0 { go=0 } else { tot=tot+r } } 3666 sys_close(fd) 3667 if tot <= 0 { hc_puts(" CONNECTED but ZERO response bytes -> gateway accepts then does NOT answer (THE BUG is the gateway)\n" as *u8); return 0 } 3668 hc_puts(" gateway ANSWERED " as *u8); hc_putn(tot); hc_puts(" bytes; status: " as *u8) 3669 var i: i64 = 0 3670 while i < tot { if buf[i]==(13 as u8) { i=tot } else { sys_write(1, ((buf as i64)+i) as *u8, 1); i=i+1 } } 3671 hc_puts("\n => gateway is FINE; the /gallery 404 is in the live sites.elf route (source-divergence fix).\n" as *u8) 3672 return 0 3673} 3674// galxauthprobe: loopback POST 127.0.0.1:18190/gallery/auth/login (15s timeout) -- ISOLATES the login hang. HUNG 3675// (timeout, 0 bytes) => the gateway's olg_login does not answer = THE BUG is the gateway (store/keys/olg_login), 3676// not v2's proxy. ANSWERED fast => olg_login is fine and the hang is v2's POST proxy. Definitive, one shot. 3677func cmd_galxauthprobe() -> i64 { 3678 hc_puts("[hostctl] galxauthprobe: loopback POST 127.0.0.1:18190/gallery/auth/login (does olg_login answer, or hang?)\n" as *u8) 3679 let fd: i64 = sys_socket(2, 1, 0) 3680 if fd < 0 { hc_puts(" socket fail\n" as *u8); return 0 } 3681 sys_set_socket_timeout(fd, 15) 3682 let sa: *u8 = sys_mmap(16) 3683 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=71 as u8; sa[3]=14 as u8; sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8 3684 var z: i64 = 8; while z < 16 { sa[z]=0 as u8; z=z+1 } 3685 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { hc_puts(" CONNECT FAIL -> gateway :18190 NOT accepting\n" as *u8); sys_close(fd); return 0 } 3686 let req: *u8 = "POST /gallery/auth/login HTTP/1.1\r\nHost: 127.0.0.1:18190\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 32\r\nConnection: close\r\n\r\nhandle=authprobe&pw=ProbeTest123" as *u8 3687 var rl: i64 = 0; while req[rl]!=(0 as u8) { rl=rl+1 } 3688 let t0: i64 = sys_now_us() 3689 sys_write(fd, req, rl) 3690 let buf: *u8 = sys_mmap(HC_MAGIC_65536) 3691 var tot: i64 = 0; var go: i64 = 1 3692 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, HC_MAGIC_65535-tot); if r <= 0 { go=0 } else { tot=tot+r } } 3693 let ms: i64 = (sys_now_us() - t0) / 1000 3694 sys_close(fd) 3695 hc_puts(" elapsed_ms=" as *u8); hc_putn(ms); hc_puts(" resp_bytes=" as *u8); hc_putn(tot); hc_puts("\n" as *u8) 3696 if tot <= 0 { hc_puts(" HUNG -> olg_login (OPAQUE auth) does NOT answer on loopback = THE BUG is the GATEWAY (store/keys/olg_login), not v2's proxy\n" as *u8); return 0 } 3697 hc_puts(" gateway ANSWERED; status: " as *u8) 3698 var i: i64 = 0 3699 while i < tot { if buf[i]==(13 as u8) { i=tot } else { sys_write(1, ((buf as i64)+i) as *u8, 1); i=i+1 } } 3700 hc_puts("\n => olg_login ANSWERS on loopback -> the hang is v2's PROXY of the POST, NOT the gateway\n" as *u8) 3701 return 0 3702} 3703// galxregprobe: loopback POST /gallery/auth/register (15s timeout) -- proves registration works end-to-end, so a 3704// user can CREATE an account (the "invalid credentials" = no account, because register hung before the proxy fix). 3705func cmd_galxregprobe() -> i64 { 3706 hc_puts("[hostctl] galxregprobe: loopback POST 127.0.0.1:18190/gallery/auth/register (does registration work?)\n" as *u8) 3707 let fd: i64 = sys_socket(2, 1, 0) 3708 if fd < 0 { hc_puts(" socket fail\n" as *u8); return 0 } 3709 sys_set_socket_timeout(fd, 20) 3710 let sa: *u8 = sys_mmap(16) 3711 sa[0]=2 as u8; sa[1]=0 as u8; sa[2]=71 as u8; sa[3]=14 as u8; sa[4]=127 as u8; sa[5]=0 as u8; sa[6]=0 as u8; sa[7]=1 as u8 3712 var z: i64 = 8; while z < 16 { sa[z]=0 as u8; z=z+1 } 3713 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) < 0 { hc_puts(" CONNECT FAIL\n" as *u8); sys_close(fd); return 0 } 3714 let req: *u8 = "POST /gallery/auth/register HTTP/1.1\r\nHost: 127.0.0.1:18190\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 34\r\nConnection: close\r\n\r\nhandle=nxregtest&pw=NxRegTest12345" as *u8 3715 var rl: i64 = 0; while req[rl]!=(0 as u8) { rl=rl+1 } 3716 let t0: i64 = sys_now_us() 3717 sys_write(fd, req, rl) 3718 let buf: *u8 = sys_mmap(HC_MAGIC_65536) 3719 var tot: i64 = 0; var go: i64 = 1 3720 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64)+tot) as *u8, HC_MAGIC_65535-tot); if r <= 0 { go=0 } else { tot=tot+r } } 3721 let ms: i64 = (sys_now_us() - t0) / 1000 3722 sys_close(fd) 3723 hc_puts(" elapsed_ms=" as *u8); hc_putn(ms); hc_puts(" resp_bytes=" as *u8); hc_putn(tot); hc_puts("\n response: " as *u8) 3724 if tot > 0 { sys_write(1, buf, tot) } else { hc_puts("(NO RESPONSE -- register endpoint hung)" as *u8) } 3725 hc_puts("\n" as *u8) 3726 return 0 3727} 3728// kicksites: kill the running sites.elf -> the supervisor respawns the LIVE binary (re-reads proxy_routes.conf / 3729// data-driven routes; NO binary swap, so the stale broken sites.elf.new is NOT promoted). Mirrors kickreader. 3730func cmd_kicksites() -> i64 { 3731 hc_puts("[hostctl] kicksites: kill sites.elf -> supervisor respawns the LIVE binary (re-reads proxy_routes.conf; NO binary swap)\n" as *u8) 3732 let k: i64 = proc_kill_by_name("sites.elf" as *u8, 9) 3733 hc_puts(" killed sites.elf procs=" as *u8); hc_putn(k); hc_puts(" -> supervisor will respawn within its poll window\n" as *u8) 3734 return 0 3735} 3736// kicksynth: kill the Synth Studio daemon (:18796) by FULL cmdline (its 25-char name truncates under proc_kill_by_name) 3737// -> hc_guard_synth respawns the freshly-staged /volume1/ai/synth/nx_synth_serve_daemon.elf. The redeploy verb. 3738func cmd_kicksynth() -> i64 { 3739 hc_puts("[hostctl] kicksynth: kill nx_synth_serve_daemon.elf (full-cmdline) -> hc_guard_synth respawns the NEW binary (<=15s)\n" as *u8) 3740 let k: i64 = hc_kill_by_cmdline("nx_synth_serve_daemon.elf" as *u8) 3741 hc_puts(" killed synth daemon procs=" as *u8); hc_putn(k); hc_puts(" -> supervisor guard respawns within its poll window\n" as *u8) 3742 return 0 3743} 3744// kickdocportal: kill the doc-portal admin daemon (:8456) by FULL cmdline (hc_kill_by_cmdline handles the 28-char 3745// name that proc_kill_by_name's comm-match truncates + misses) -> hc_guard_admin respawns a fresh one that binds 3746// :8456. Clears a stale/hung instance. Driven by /api/restart docportal -> md_exec_hostctl -> nx_hostctl kickdocportal. 3747func cmd_kickdocportal() -> i64 { 3748 hc_puts("[hostctl] kickdocportal: kill nx_docportal_admin_daemon.elf (full-cmdline) -> guard respawns a fresh :8456\n" as *u8) 3749 let k: i64 = hc_kill_by_cmdline("nx_docportal_admin_daemon.elf" as *u8) 3750 hc_puts(" killed docportal procs=" as *u8); hc_putn(k); hc_puts(" -> supervisor guard respawns within its poll window\n" as *u8) 3751 return 0 3752} 3753// restart <service>: GENERIC heal action -- kill a KNOWN supervised service so the guard respawns it FRESH (e.g. a 3754// wedged OPAQUE-auth context in the gallery gateway). FAIL-CLOSED safety allowlist: only the supervised daemons can 3755// be killed (never an arbitrary name). funcheck verifies the heal; the keep-up loop composes this. 3756// hc_cmdl_has: 1 iff buf[0..n) contains "/" + name (so an ARG mention like "...restart nx_gallery_gateway.elf" 3757// is NOT matched -- only the binary path "./nx_gallery_gateway.elf"). This is why we don't kill our own command. 3758func hc_cmdl_has(buf: *u8, n: i64, name: *u8) -> i64 { 3759 var nl: i64 = 0; while name[nl] != (0 as u8) { nl = nl + 1 } 3760 if nl == 0 { return 0 } 3761 var i: i64 = 0 3762 while i + 1 + nl <= n { 3763 if (buf[i] as i64) == 47 { 3764 var j: i64 = 0; var ok: i64 = 1 3765 while j < nl { if buf[i+1+j] != name[j] { ok = 0; j = nl } else { j = j + 1 } } 3766 if ok == 1 { return 1 } 3767 } 3768 i = i + 1 3769 } 3770 return 0 3771} 3772// hc_kill_by_cmdline: scan /proc and SIGKILL every process whose /proc/<pid>/cmdline (the FULL command, NOT the 3773// 15-char comm that silently failed for long names) contains "/"+name. Returns the REAL killed count so the caller 3774// can report honestly (killed=0 => the op FAILED). The root-cause fix for the 2026-06-29 stale-gateway disaster. 3775func hc_kill_by_cmdline(name: *u8) -> i64 { 3776 let fd: i64 = sys_openat_rd("/proc" as *u8) 3777 if fd < 0 { return 0 } 3778 let gbuf: *u8 = sys_mmap(HC_MAGIC_65536); let path: *u8 = sys_mmap(256); let cmdl: *u8 = sys_mmap(HC_MAGIC_8192) 3779 var killed: i64 = 0; var go: i64 = 1 3780 while go == 1 { 3781 let n: i64 = sys_getdents64(fd, gbuf, HC_MAGIC_65536) 3782 if n <= 0 { go = 0 } else { 3783 var off: i64 = 0 3784 while off < n { 3785 let rec: *u8 = ((gbuf as i64) + off) as *u8 3786 let reclen: i64 = dirent_reclen(rec) 3787 let pname: *u8 = dirent_name(rec) 3788 var pid: i64 = 0; var valid: i64 = 1; var di: i64 = 0 3789 if pname[0] == (0 as u8) { valid = 0 } 3790 while pname[di] != (0 as u8) { let c: i64 = pname[di] as i64; if c < 48 { valid = 0 } else { if c > 57 { valid = 0