@serfab/cadre-host 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/README.md +347 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +332 -524
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +54 -0
  9. package/dist/bin/nat-output.js +138 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/bin/ui-access.d.ts +22 -0
  12. package/dist/bin/ui-access.js +44 -0
  13. package/dist/bin/ui-access.js.map +1 -0
  14. package/dist/hosted/claim-details.d.ts +66 -0
  15. package/dist/hosted/claim-details.js +60 -0
  16. package/dist/hosted/claim-details.js.map +1 -0
  17. package/dist/hosted/hosted-node-service.d.ts +325 -0
  18. package/dist/hosted/hosted-node-service.js +678 -0
  19. package/dist/hosted/hosted-node-service.js.map +1 -0
  20. package/dist/hosted/hosted-node-store.d.ts +40 -0
  21. package/dist/hosted/hosted-node-store.js +126 -0
  22. package/dist/hosted/hosted-node-store.js.map +1 -0
  23. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  24. package/dist/hosted/hosted-node-supervisor.js +396 -0
  25. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  26. package/dist/hosted/hosted-node-watcher.d.ts +95 -0
  27. package/dist/hosted/hosted-node-watcher.js +260 -0
  28. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  29. package/dist/hosted/index.d.ts +16 -0
  30. package/dist/hosted/index.js +13 -0
  31. package/dist/hosted/index.js.map +1 -0
  32. package/dist/hosted/node-status.d.ts +51 -0
  33. package/dist/hosted/node-status.js +77 -0
  34. package/dist/hosted/node-status.js.map +1 -0
  35. package/dist/hosted/types.d.ts +141 -0
  36. package/dist/hosted/types.js +22 -0
  37. package/dist/hosted/types.js.map +1 -0
  38. package/dist/index.d.ts +12 -24
  39. package/dist/index.js +9 -19
  40. package/dist/index.js.map +1 -1
  41. package/dist/installer/config.d.ts +27 -34
  42. package/dist/installer/config.js +46 -15
  43. package/dist/installer/config.js.map +1 -1
  44. package/dist/installer/index.d.ts +9 -19
  45. package/dist/installer/index.js +21 -85
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/installer/paths.d.ts +0 -2
  48. package/dist/installer/paths.js +0 -4
  49. package/dist/installer/paths.js.map +1 -1
  50. package/dist/installer/wizard.d.ts +12 -9
  51. package/dist/installer/wizard.js +15 -14
  52. package/dist/installer/wizard.js.map +1 -1
  53. package/dist/nat/address-resolver.d.ts +18 -17
  54. package/dist/nat/address-resolver.js +79 -25
  55. package/dist/nat/address-resolver.js.map +1 -1
  56. package/dist/nat/address-watch.d.ts +37 -0
  57. package/dist/nat/address-watch.js +84 -0
  58. package/dist/nat/address-watch.js.map +1 -0
  59. package/dist/nat/ddns/duckdns.d.ts +2 -1
  60. package/dist/nat/ddns/duckdns.js +5 -1
  61. package/dist/nat/ddns/duckdns.js.map +1 -1
  62. package/dist/nat/ddns/index.d.ts +6 -0
  63. package/dist/nat/ddns/index.js.map +1 -1
  64. package/dist/nat/index.d.ts +13 -9
  65. package/dist/nat/index.js +7 -5
  66. package/dist/nat/index.js.map +1 -1
  67. package/dist/nat/nat-service.d.ts +220 -77
  68. package/dist/nat/nat-service.js +653 -239
  69. package/dist/nat/nat-service.js.map +1 -1
  70. package/dist/nat/nat-store.d.ts +12 -5
  71. package/dist/nat/nat-store.js +124 -29
  72. package/dist/nat/nat-store.js.map +1 -1
  73. package/dist/nat/network-mode.d.ts +19 -0
  74. package/dist/nat/network-mode.js +32 -0
  75. package/dist/nat/network-mode.js.map +1 -0
  76. package/dist/nat/port-mapper.d.ts +83 -79
  77. package/dist/nat/port-mapper.js +148 -189
  78. package/dist/nat/port-mapper.js.map +1 -1
  79. package/dist/nat/reachability.d.ts +32 -15
  80. package/dist/nat/reachability.js +110 -22
  81. package/dist/nat/reachability.js.map +1 -1
  82. package/dist/nat/types.d.ts +98 -27
  83. package/dist/nat/types.js +8 -5
  84. package/dist/nat/types.js.map +1 -1
  85. package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
  86. package/dist/orchestrator/host-process-orchestrator.js +84 -233
  87. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  88. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  89. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  90. package/dist/orchestrator/identity-file.js.map +1 -0
  91. package/dist/orchestrator/index.d.ts +2 -3
  92. package/dist/orchestrator/index.js +1 -1
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/node-identity.d.ts +6 -6
  95. package/dist/orchestrator/node-identity.js +7 -7
  96. package/dist/orchestrator/node-identity.js.map +1 -1
  97. package/dist/orchestrator/port-allocator.d.ts +5 -6
  98. package/dist/orchestrator/port-allocator.js +9 -11
  99. package/dist/orchestrator/port-allocator.js.map +1 -1
  100. package/dist/orchestrator/state-store.d.ts +3 -5
  101. package/dist/orchestrator/state-store.js +2 -10
  102. package/dist/orchestrator/state-store.js.map +1 -1
  103. package/dist/orchestrator/types.d.ts +33 -22
  104. package/dist/orchestrator/types.js.map +1 -1
  105. package/dist/package-version.d.ts +1 -0
  106. package/dist/package-version.js +4 -0
  107. package/dist/package-version.js.map +1 -0
  108. package/dist/server/error-handler.d.ts +5 -25
  109. package/dist/server/error-handler.js +11 -74
  110. package/dist/server/error-handler.js.map +1 -1
  111. package/dist/server/events/types.d.ts +7 -11
  112. package/dist/server/events/types.js +2 -1
  113. package/dist/server/events/types.js.map +1 -1
  114. package/dist/server/index.d.ts +11 -37
  115. package/dist/server/index.js +30 -48
  116. package/dist/server/index.js.map +1 -1
  117. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  118. package/dist/server/routes/hosted-nodes.js +68 -0
  119. package/dist/server/routes/hosted-nodes.js.map +1 -0
  120. package/dist/server/routes/nat.d.ts +4 -1
  121. package/dist/server/routes/nat.js +13 -2
  122. package/dist/server/routes/nat.js.map +1 -1
  123. package/dist/server/routes/nodes.d.ts +6 -20
  124. package/dist/server/routes/nodes.js +7 -86
  125. package/dist/server/routes/nodes.js.map +1 -1
  126. package/dist/server/routes/settings.d.ts +3 -7
  127. package/dist/server/routes/settings.js +18 -8
  128. package/dist/server/routes/settings.js.map +1 -1
  129. package/dist/server/routes/status.d.ts +2 -25
  130. package/dist/server/routes/status.js +3 -43
  131. package/dist/server/routes/status.js.map +1 -1
  132. package/dist/server/server.js +8 -8
  133. package/dist/server/settings-store.d.ts +2 -2
  134. package/dist/server/settings-store.js +2 -2
  135. package/dist/server/static.js +1 -2
  136. package/dist/server/static.js.map +1 -1
  137. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  138. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  139. package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
  140. package/dist/ui/index.html +2 -2
  141. package/dist/update/index.d.ts +2 -2
  142. package/dist/update/index.js +2 -2
  143. package/dist/update/store.d.ts +1 -1
  144. package/dist/update/store.js +1 -1
  145. package/dist/update/types.d.ts +1 -1
  146. package/dist/update/types.js +1 -1
  147. package/package.json +5 -7
  148. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  149. package/src/__tests__/cli.smoke.test.ts +2 -2
  150. package/src/__tests__/global-setup.ts +2 -3
  151. package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
  152. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  153. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  154. package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
  155. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  156. package/src/__tests__/orchestrator-push.test.ts +138 -146
  157. package/src/__tests__/orchestrator.test.ts +1017 -1063
  158. package/src/__tests__/ui-access.test.ts +30 -0
  159. package/src/bin/host.ts +397 -572
  160. package/src/bin/join-output.ts +120 -0
  161. package/src/bin/nat-output.ts +188 -0
  162. package/src/bin/ui-access.ts +50 -0
  163. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
  164. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
  165. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
  166. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
  167. package/src/hosted/__tests__/test-invitation.ts +33 -0
  168. package/src/hosted/claim-details.ts +103 -0
  169. package/src/hosted/hosted-node-service.ts +814 -0
  170. package/src/hosted/hosted-node-store.ts +144 -0
  171. package/src/hosted/hosted-node-supervisor.ts +439 -0
  172. package/src/hosted/hosted-node-watcher.ts +288 -0
  173. package/src/hosted/index.ts +53 -0
  174. package/src/hosted/node-status.ts +97 -0
  175. package/src/hosted/types.ts +160 -0
  176. package/src/index.ts +72 -91
  177. package/src/installer/__tests__/config.test.ts +15 -40
  178. package/src/installer/__tests__/installer.smoke.test.ts +184 -263
  179. package/src/installer/__tests__/wizard.test.ts +25 -13
  180. package/src/installer/config.ts +201 -175
  181. package/src/installer/index.ts +252 -327
  182. package/src/installer/paths.ts +104 -109
  183. package/src/installer/wizard.ts +24 -26
  184. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  185. package/src/nat/__tests__/nat-service.test.ts +608 -515
  186. package/src/nat/__tests__/nat-store.test.ts +49 -12
  187. package/src/nat/__tests__/network-mode.test.ts +38 -0
  188. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  189. package/src/nat/__tests__/reachability.test.ts +96 -13
  190. package/src/nat/address-resolver.ts +94 -52
  191. package/src/nat/address-watch.ts +92 -0
  192. package/src/nat/ddns/duckdns.ts +5 -1
  193. package/src/nat/ddns/index.ts +6 -0
  194. package/src/nat/index.ts +33 -13
  195. package/src/nat/nat-service.ts +942 -501
  196. package/src/nat/nat-store.ts +126 -31
  197. package/src/nat/network-mode.ts +36 -0
  198. package/src/nat/port-mapper.ts +241 -329
  199. package/src/nat/reachability.ts +155 -34
  200. package/src/nat/types.ts +110 -37
  201. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  202. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  203. package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
  204. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  205. package/src/orchestrator/index.ts +9 -10
  206. package/src/orchestrator/node-identity.ts +41 -41
  207. package/src/orchestrator/port-allocator.ts +71 -73
  208. package/src/orchestrator/state-store.ts +76 -90
  209. package/src/orchestrator/types.ts +34 -23
  210. package/src/package-version.ts +4 -0
  211. package/src/server/__tests__/bus.test.ts +38 -38
  212. package/src/server/__tests__/error-handler.test.ts +81 -118
  213. package/src/server/__tests__/fakes.ts +15 -18
  214. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
  215. package/src/server/__tests__/nodes-route.test.ts +114 -283
  216. package/src/server/__tests__/publishers.test.ts +193 -180
  217. package/src/server/__tests__/server.smoke.test.ts +134 -223
  218. package/src/server/__tests__/settings-route.test.ts +210 -195
  219. package/src/server/__tests__/sse-route.test.ts +119 -119
  220. package/src/server/__tests__/status-route.test.ts +82 -121
  221. package/src/server/error-handler.ts +112 -178
  222. package/src/server/events/types.ts +20 -24
  223. package/src/server/index.ts +223 -267
  224. package/src/server/routes/hosted-nodes.ts +83 -0
  225. package/src/server/routes/nat.ts +75 -63
  226. package/src/server/routes/nodes.ts +96 -188
  227. package/src/server/routes/settings.ts +20 -15
  228. package/src/server/routes/status.ts +5 -67
  229. package/src/server/server.ts +8 -8
  230. package/src/server/settings-store.ts +2 -2
  231. package/src/server/static.ts +1 -2
  232. package/src/update/index.ts +2 -2
  233. package/src/update/store.ts +1 -1
  234. package/src/update/types.ts +1 -1
  235. package/dist/auth/duration.d.ts +0 -1
  236. package/dist/auth/duration.js +0 -32
  237. package/dist/auth/duration.js.map +0 -1
  238. package/dist/auth/index.d.ts +0 -11
  239. package/dist/auth/index.js +0 -9
  240. package/dist/auth/index.js.map +0 -1
  241. package/dist/auth/self-label.d.ts +0 -36
  242. package/dist/auth/self-label.js +0 -57
  243. package/dist/auth/self-label.js.map +0 -1
  244. package/dist/auth/trust-circle-store.d.ts +0 -26
  245. package/dist/auth/trust-circle-store.js +0 -129
  246. package/dist/auth/trust-circle-store.js.map +0 -1
  247. package/dist/auth/trust-circle.d.ts +0 -156
  248. package/dist/auth/trust-circle.js +0 -307
  249. package/dist/auth/trust-circle.js.map +0 -1
  250. package/dist/auth/types.d.ts +0 -84
  251. package/dist/auth/types.js +0 -24
  252. package/dist/auth/types.js.map +0 -1
  253. package/dist/donation/donation-service.d.ts +0 -389
  254. package/dist/donation/donation-service.js +0 -828
  255. package/dist/donation/donation-service.js.map +0 -1
  256. package/dist/donation/donation-store.d.ts +0 -51
  257. package/dist/donation/donation-store.js +0 -152
  258. package/dist/donation/donation-store.js.map +0 -1
  259. package/dist/donation/donation-supervisor.js +0 -364
  260. package/dist/donation/donation-supervisor.js.map +0 -1
  261. package/dist/donation/grant-service.d.ts +0 -80
  262. package/dist/donation/grant-service.js +0 -210
  263. package/dist/donation/grant-service.js.map +0 -1
  264. package/dist/donation/grant-store.d.ts +0 -38
  265. package/dist/donation/grant-store.js +0 -135
  266. package/dist/donation/grant-store.js.map +0 -1
  267. package/dist/donation/index.d.ts +0 -20
  268. package/dist/donation/index.js +0 -17
  269. package/dist/donation/index.js.map +0 -1
  270. package/dist/donation/types.d.ts +0 -224
  271. package/dist/donation/types.js +0 -40
  272. package/dist/donation/types.js.map +0 -1
  273. package/dist/installer/identity.js.map +0 -1
  274. package/dist/owner/index.d.ts +0 -11
  275. package/dist/owner/index.js +0 -11
  276. package/dist/owner/index.js.map +0 -1
  277. package/dist/owner/owner-node-client.d.ts +0 -110
  278. package/dist/owner/owner-node-client.js +0 -184
  279. package/dist/owner/owner-node-client.js.map +0 -1
  280. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  281. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  282. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  283. package/dist/server/routes/grants-admin.d.ts +0 -29
  284. package/dist/server/routes/grants-admin.js +0 -58
  285. package/dist/server/routes/grants-admin.js.map +0 -1
  286. package/dist/server/routes/grants.d.ts +0 -28
  287. package/dist/server/routes/grants.js +0 -165
  288. package/dist/server/routes/grants.js.map +0 -1
  289. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  290. package/dist/server/routes/provision-request-validation.js +0 -97
  291. package/dist/server/routes/provision-request-validation.js.map +0 -1
  292. package/dist/server/routes/strands.d.ts +0 -20
  293. package/dist/server/routes/strands.js +0 -38
  294. package/dist/server/routes/strands.js.map +0 -1
  295. package/dist/server/routes/trust-circle.d.ts +0 -17
  296. package/dist/server/routes/trust-circle.js +0 -35
  297. package/dist/server/routes/trust-circle.js.map +0 -1
  298. package/dist/strands/index.d.ts +0 -7
  299. package/dist/strands/index.js +0 -6
  300. package/dist/strands/index.js.map +0 -1
  301. package/dist/strands/strand-service.d.ts +0 -50
  302. package/dist/strands/strand-service.js +0 -103
  303. package/dist/strands/strand-service.js.map +0 -1
  304. package/dist/strands/types.d.ts +0 -93
  305. package/dist/strands/types.js +0 -23
  306. package/dist/strands/types.js.map +0 -1
  307. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  308. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  309. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  310. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  311. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  312. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  313. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  314. package/src/auth/__tests__/duration.test.ts +0 -28
  315. package/src/auth/__tests__/self-label.test.ts +0 -85
  316. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  317. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  318. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  319. package/src/auth/duration.ts +0 -33
  320. package/src/auth/index.ts +0 -21
  321. package/src/auth/self-label.ts +0 -74
  322. package/src/auth/trust-circle-store.ts +0 -155
  323. package/src/auth/trust-circle.ts +0 -395
  324. package/src/auth/types.ts +0 -99
  325. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  326. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  327. package/src/donation/__tests__/grant-service.test.ts +0 -213
  328. package/src/donation/__tests__/grant-store.test.ts +0 -113
  329. package/src/donation/donation-service.ts +0 -1033
  330. package/src/donation/donation-store.ts +0 -176
  331. package/src/donation/donation-supervisor.ts +0 -411
  332. package/src/donation/grant-service.ts +0 -252
  333. package/src/donation/grant-store.ts +0 -152
  334. package/src/donation/index.ts +0 -65
  335. package/src/donation/types.ts +0 -260
  336. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  337. package/src/owner/index.ts +0 -12
  338. package/src/owner/owner-node-client.ts +0 -259
  339. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  340. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  341. package/src/server/__tests__/grants-route.test.ts +0 -394
  342. package/src/server/__tests__/strands-route.test.ts +0 -234
  343. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  344. package/src/server/routes/grants-admin.ts +0 -74
  345. package/src/server/routes/grants.ts +0 -193
  346. package/src/server/routes/provision-request-validation.ts +0 -105
  347. package/src/server/routes/strands.ts +0 -54
  348. package/src/server/routes/trust-circle.ts +0 -49
  349. package/src/strands/__tests__/strand-service.test.ts +0 -165
  350. package/src/strands/index.ts +0 -14
  351. package/src/strands/strand-service.ts +0 -135
  352. package/src/strands/types.ts +0 -102
@@ -1,175 +1,201 @@
1
- /**
2
- * `host.config.json` schema + I/O.
3
- *
4
- * This file captures the wizard's output. Subsystems (trust-circle, NAT,
5
- * orchestrator) each own their own files under `<dataDir>/`; this is NOT
6
- * an umbrella config.
7
- *
8
- * Schema version: 2. `version` is a forward guard — a file whose version
9
- * does not match `CURRENT_VERSION` is rejected on read rather than
10
- * reinterpreted, so a future incompatible layout fails loudly instead of
11
- * being silently misread.
12
- */
13
-
14
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
- import { dirname } from 'node:path';
16
-
17
- export interface UpdatesConfig {
18
- /** When true, the daily-check timer auto-applies updates. Default false. */
19
- autoApply: boolean;
20
- /** Optional override for the manifest URL (env var still wins). */
21
- manifestUrl?: string;
22
- }
23
-
24
- /**
25
- * Whether this host also runs its own personal cadre (the opt-in "founder"
26
- * persona), on top of the always-on node-donor role.
27
- *
28
- * Default (absent or `enabled: false`) is donor-only: `cadre-host start` brings
29
- * up the donation surface but spawns **no** owner node, so the trust-circle and
30
- * NAT surfaces stay inactive. Set `enabled: true` (install-time choice) to also
31
- * spawn the host's own owner node and enable `/auth` + `/nat`. See
32
- * docs/cadre-host.md § Two roles: donor and founder.
33
- */
34
- export interface OwnCadreConfig {
35
- /** When true, `start` spawns the host-owned owner node + trust-circle + NAT. */
36
- enabled: boolean;
37
- }
38
-
39
- /**
40
- * Non-secret push (FCM/APNs) settings. The actual private keys (and the FCM
41
- * service-account / APNs key identifiers) live in the secret store — see
42
- * `src/push/`. Only these app-config bits, which are safe to keep in plaintext
43
- * `host.config.json`, are stored here. Absent ⇒ push is not configured.
44
- */
45
- export interface PushSettings {
46
- /** APNs app-config (the `.p8` private key + key/team ids stay in the secret store). */
47
- apns?: {
48
- /** App bundle id → APNs `apns-topic` header. */
49
- bundleId: string;
50
- /** `true` → production APNs host; false/undefined → sandbox (dev/TestFlight). */
51
- production?: boolean;
52
- };
53
- /** Per-(peer,strand) anti-spam cooldown (ms) forwarded into the node's push block. */
54
- cooldownMs?: number;
55
- /** Per-strand burst-coalescing window (ms) forwarded into the node's push block. */
56
- debounceMs?: number;
57
- }
58
-
59
- export interface HostConfigFile {
60
- version: 2;
61
- /** A stable per-install id; useful for log correlation and the control-network partyId. */
62
- installId: string;
63
- /** Localhost-only management UI + API listener port. */
64
- uiPort: number;
65
- /** External cadre libp2p port (NAT will map to this). */
66
- libp2pPort: number;
67
- /** Self-reference (convenient for service-host ExecStart). */
68
- dataDir: string;
69
- /** Absolute path to the Ed25519 protobuf identity. */
70
- identityPath: string;
71
- /** Whether UPnP/NAT-PMP should be attempted at start time. */
72
- upnpEnabled: boolean;
73
- /** ISO timestamp the wizard wrote this file. */
74
- installedAt: string;
75
- /** Installer (= package) version that wrote this file. */
76
- installerVersion: string;
77
- /** Update-flow settings; defaults: { autoApply: false }. */
78
- updates: UpdatesConfig;
79
- /**
80
- * Host-own-cadre (founder persona) opt-in. Optional — absent is treated as
81
- * `{ enabled: false }` (donor-only). Written by the installer; read via
82
- * `hostOwnsCadre()`. Not editable through /api/settings (install-time only).
83
- */
84
- ownCadre?: OwnCadreConfig;
85
- /**
86
- * Non-secret push settings (bundle id, sandbox/prod toggle, cooldown/debounce).
87
- * Optional — absent when push is not configured. Private keys never live here;
88
- * they are read from the secret store at node-spawn time (`src/push/`).
89
- */
90
- push?: PushSettings;
91
- }
92
-
93
- /**
94
- * Whether this host runs its own personal cadre (founder persona). Absent
95
- * `ownCadre` ⇒ donor-only (false). The single source of truth for the
96
- * founder/donor gate in `bin/host.ts start`.
97
- */
98
- export function hostOwnsCadre(cfg: HostConfigFile): boolean {
99
- return cfg.ownCadre?.enabled === true;
100
- }
101
-
102
- const CURRENT_VERSION = 2;
103
-
104
- export function writeHostConfig(filePath: string, cfg: HostConfigFile): void {
105
- if (cfg.version !== CURRENT_VERSION) {
106
- throw new Error(`Refusing to write host.config.json with version=${cfg.version} (current: ${CURRENT_VERSION})`);
107
- }
108
- mkdirSync(dirname(filePath), { recursive: true });
109
- writeFileSync(filePath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
110
- }
111
-
112
- /** Apply a partial patch to the persisted config (read-modify-write). */
113
- export function updateHostConfig(
114
- filePath: string,
115
- patch: Partial<Omit<HostConfigFile, 'version'>>,
116
- ): HostConfigFile {
117
- const current = readHostConfig(filePath);
118
- const next: HostConfigFile = {
119
- ...current,
120
- ...patch,
121
- updates: { ...current.updates, ...(patch.updates ?? {}) },
122
- version: CURRENT_VERSION,
123
- };
124
- writeHostConfig(filePath, next);
125
- return next;
126
- }
127
-
128
- export function readHostConfig(filePath: string): HostConfigFile {
129
- const raw = readFileSync(filePath, 'utf8');
130
- let parsed: unknown;
131
- try {
132
- parsed = JSON.parse(raw);
133
- } catch (err) {
134
- throw new Error(`Failed to parse host.config.json at ${filePath}: ${(err as Error).message}`, { cause: err });
135
- }
136
- if (!parsed || typeof parsed !== 'object') {
137
- throw new Error(`host.config.json at ${filePath} is not a JSON object`);
138
- }
139
- const obj = parsed as Record<string, unknown>;
140
- if (obj.version !== CURRENT_VERSION) {
141
- throw new Error(
142
- `host.config.json at ${filePath} has unsupported version=${obj.version} (this build expects ${CURRENT_VERSION})`,
143
- );
144
- }
145
- if (!isHostConfigShape(obj)) {
146
- throw new Error(`host.config.json at ${filePath} is missing required fields`);
147
- }
148
- return obj;
149
- }
150
-
151
- function isHostConfigShape(v: unknown): v is HostConfigFile {
152
- if (!v || typeof v !== 'object') return false;
153
- const o = v as Record<string, unknown>;
154
- if (
155
- typeof o.installId !== 'string' ||
156
- typeof o.uiPort !== 'number' ||
157
- typeof o.libp2pPort !== 'number' ||
158
- typeof o.dataDir !== 'string' ||
159
- typeof o.identityPath !== 'string' ||
160
- typeof o.upnpEnabled !== 'boolean' ||
161
- typeof o.installedAt !== 'string' ||
162
- typeof o.installerVersion !== 'string'
163
- ) {
164
- return false;
165
- }
166
- const updates = o.updates as Record<string, unknown> | undefined;
167
- if (!updates || typeof updates !== 'object') return false;
168
- if (typeof updates.autoApply !== 'boolean') return false;
169
- if (updates.manifestUrl !== undefined && typeof updates.manifestUrl !== 'string') return false;
170
- if (o.ownCadre !== undefined) {
171
- const own = o.ownCadre as Record<string, unknown>;
172
- if (!own || typeof own !== 'object' || typeof own.enabled !== 'boolean') return false;
173
- }
174
- return true;
175
- }
1
+ /**
2
+ * `host.config.json` schema + I/O.
3
+ *
4
+ * This file captures the wizard's output. Subsystems (hosted nodes, NAT,
5
+ * orchestrator) each own their own files under `<dataDir>/`; this is NOT
6
+ * an umbrella config.
7
+ *
8
+ * Schema version: 3. `version` is a forward guard — a file whose version
9
+ * does not match `CURRENT_VERSION` is rejected on read rather than
10
+ * reinterpreted, so a future incompatible layout fails loudly instead of
11
+ * being silently misread.
12
+ */
13
+
14
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { isIP } from 'node:net';
16
+ import { dirname } from 'node:path';
17
+
18
+ import { destinationPeerIdOf } from '@serfab/cadre-core';
19
+
20
+ export interface UpdatesConfig {
21
+ /** When true, the daily-check timer auto-applies updates. Default false. */
22
+ autoApply: boolean;
23
+ /** Optional override for the manifest URL (env var still wins). */
24
+ manifestUrl?: string;
25
+ }
26
+
27
+ /**
28
+ * Non-secret push (FCM/APNs) settings. The actual private keys (and the FCM
29
+ * service-account / APNs key identifiers) live in the secret store — see
30
+ * `src/push/`. Only these app-config bits, which are safe to keep in plaintext
31
+ * `host.config.json`, are stored here. Absent ⇒ push is not configured.
32
+ */
33
+ export interface PushSettings {
34
+ /** APNs app-config (the `.p8` private key + key/team ids stay in the secret store). */
35
+ apns?: {
36
+ /** App bundle id → APNs `apns-topic` header. */
37
+ bundleId: string;
38
+ /** `true` → production APNs host; false/undefined → sandbox (dev/TestFlight). */
39
+ production?: boolean;
40
+ };
41
+ /** Per-(peer,strand) anti-spam cooldown (ms) forwarded into the node's push block. */
42
+ cooldownMs?: number;
43
+ /** Per-strand burst-coalescing window (ms) forwarded into the node's push block. */
44
+ debounceMs?: number;
45
+ }
46
+
47
+ /**
48
+ * Which addresses a hosted node's QR code carries (cadre-core's `selectNodeClaimAddresses`).
49
+ * Absent ⇒ `{ lan: 'auto' }`. Read on every claim-code request, so an edit applies to the
50
+ * next code shown.
51
+ */
52
+ export interface ClaimAddressSettings {
53
+ /**
54
+ * The LAN address a phone at home dials: `'auto'` (the address on the router's subnet, else
55
+ * the machine's primary one; none in public-IP mode), `'none'` (public addresses only), or an
56
+ * IP address. Other private addresses (Docker bridges, VPNs) are left out either way.
57
+ */
58
+ lan?: string;
59
+ /**
60
+ * Exact addresses to put in every code instead, for an operator who knows better than the
61
+ * rule, such as one name behind a proxy. Each names no destination peer (a relay address ends
62
+ * at its `/p2p-circuit` hop); every node's code appends its own peer id.
63
+ */
64
+ addrs?: string[];
65
+ }
66
+
67
+ /** Why a `claimAddresses` value is unacceptable, naming the field; undefined when it is fine. */
68
+ export function claimAddressSettingsProblem(value: unknown): string | undefined {
69
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return '"claimAddresses" must be an object';
70
+ const o = value as Record<string, unknown>;
71
+ for (const key of Object.keys(o)) {
72
+ if (key !== 'lan' && key !== 'addrs') return `Unknown setting "claimAddresses.${key}"`;
73
+ }
74
+ if (o.lan !== undefined) {
75
+ if (typeof o.lan !== 'string' || (o.lan !== 'auto' && o.lan !== 'none' && isIP(o.lan) === 0)) {
76
+ return '"claimAddresses.lan" must be "auto", "none" or an IP address';
77
+ }
78
+ }
79
+ if (o.addrs !== undefined) {
80
+ if (!Array.isArray(o.addrs) || !o.addrs.every((a) => typeof a === 'string')) {
81
+ return '"claimAddresses.addrs" must be a list of multiaddr strings';
82
+ }
83
+ for (const addr of o.addrs as string[]) {
84
+ const problem = exactClaimAddressProblem(addr);
85
+ if (problem) return `"claimAddresses.addrs" entry "${addr}" ${problem}`;
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ /**
92
+ * Why one `claimAddresses.addrs` entry is unacceptable. It must not name its destination peer:
93
+ * the list is host-wide and each hosted node's code appends its own peer id, so a named one
94
+ * would be some other node's address in every code but that node's.
95
+ */
96
+ function exactClaimAddressProblem(addr: string): string | undefined {
97
+ let destination: string | null;
98
+ try {
99
+ destination = destinationPeerIdOf(addr.trim());
100
+ } catch {
101
+ return 'is not a valid multiaddr';
102
+ }
103
+ return destination === null ? undefined : 'must not end in /p2p/<peer id>: each node\'s own id is appended';
104
+ }
105
+
106
+ export interface HostConfigFile {
107
+ version: 3;
108
+ /** A stable per-install id, for log correlation. */
109
+ installId: string;
110
+ /** Localhost-only management UI + API listener port. */
111
+ uiPort: number;
112
+ /** Self-reference (convenient for service-host ExecStart). */
113
+ dataDir: string;
114
+ /** Whether UPnP/NAT-PMP should be attempted at start time. */
115
+ upnpEnabled: boolean;
116
+ /** ISO timestamp the wizard wrote this file. */
117
+ installedAt: string;
118
+ /** Installer (= package) version that wrote this file. */
119
+ installerVersion: string;
120
+ /** Update-flow settings; defaults: { autoApply: false }. */
121
+ updates: UpdatesConfig;
122
+ /**
123
+ * Non-secret push settings (bundle id, sandbox/prod toggle, cooldown/debounce).
124
+ * Optional — absent when push is not configured. Private keys never live here;
125
+ * they are read from the secret store at node-spawn time (`src/push/`).
126
+ */
127
+ push?: PushSettings;
128
+ /** Which addresses claim codes carry; absent ⇒ automatic. */
129
+ claimAddresses?: ClaimAddressSettings;
130
+ }
131
+
132
+ const CURRENT_VERSION = 3;
133
+
134
+ export function writeHostConfig(filePath: string, cfg: HostConfigFile): void {
135
+ if (cfg.version !== CURRENT_VERSION) {
136
+ throw new Error(`Refusing to write host.config.json with version=${cfg.version} (current: ${CURRENT_VERSION})`);
137
+ }
138
+ mkdirSync(dirname(filePath), { recursive: true });
139
+ writeFileSync(filePath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
140
+ }
141
+
142
+ /** Apply a partial patch to the persisted config (read-modify-write). */
143
+ export function updateHostConfig(
144
+ filePath: string,
145
+ patch: Partial<Omit<HostConfigFile, 'version'>>,
146
+ ): HostConfigFile {
147
+ const current = readHostConfig(filePath);
148
+ const next: HostConfigFile = {
149
+ ...current,
150
+ ...patch,
151
+ updates: { ...current.updates, ...(patch.updates ?? {}) },
152
+ version: CURRENT_VERSION,
153
+ };
154
+ writeHostConfig(filePath, next);
155
+ return next;
156
+ }
157
+
158
+ export function readHostConfig(filePath: string): HostConfigFile {
159
+ const raw = readFileSync(filePath, 'utf8');
160
+ let parsed: unknown;
161
+ try {
162
+ parsed = JSON.parse(raw);
163
+ } catch (err) {
164
+ throw new Error(`Failed to parse host.config.json at ${filePath}: ${(err as Error).message}`, { cause: err });
165
+ }
166
+ if (!parsed || typeof parsed !== 'object') {
167
+ throw new Error(`host.config.json at ${filePath} is not a JSON object`);
168
+ }
169
+ const obj = parsed as Record<string, unknown>;
170
+ if (obj.version !== CURRENT_VERSION) {
171
+ throw new Error(
172
+ `host.config.json at ${filePath} has unsupported version=${obj.version} (this build expects ${CURRENT_VERSION})`,
173
+ );
174
+ }
175
+ if (!isHostConfigShape(obj)) {
176
+ throw new Error(`host.config.json at ${filePath} is missing required fields`);
177
+ }
178
+ const claimAddressesProblem = obj.claimAddresses === undefined ? undefined : claimAddressSettingsProblem(obj.claimAddresses);
179
+ if (claimAddressesProblem) throw new Error(`host.config.json at ${filePath}: ${claimAddressesProblem}`);
180
+ return obj;
181
+ }
182
+
183
+ function isHostConfigShape(v: unknown): v is HostConfigFile {
184
+ if (!v || typeof v !== 'object') return false;
185
+ const o = v as Record<string, unknown>;
186
+ if (
187
+ typeof o.installId !== 'string' ||
188
+ typeof o.uiPort !== 'number' ||
189
+ typeof o.dataDir !== 'string' ||
190
+ typeof o.upnpEnabled !== 'boolean' ||
191
+ typeof o.installedAt !== 'string' ||
192
+ typeof o.installerVersion !== 'string'
193
+ ) {
194
+ return false;
195
+ }
196
+ const updates = o.updates as Record<string, unknown> | undefined;
197
+ if (!updates || typeof updates !== 'object') return false;
198
+ if (typeof updates.autoApply !== 'boolean') return false;
199
+ if (updates.manifestUrl !== undefined && typeof updates.manifestUrl !== 'string') return false;
200
+ return true;
201
+ }