@serfab/cadre-host 1.13.0 → 1.14.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 (337) hide show
  1. package/README.md +332 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +301 -505
  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 +49 -0
  9. package/dist/bin/nat-output.js +127 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/hosted/claim-details.d.ts +44 -0
  12. package/dist/hosted/claim-details.js +66 -0
  13. package/dist/hosted/claim-details.js.map +1 -0
  14. package/dist/hosted/hosted-node-service.d.ts +318 -0
  15. package/dist/hosted/hosted-node-service.js +666 -0
  16. package/dist/hosted/hosted-node-service.js.map +1 -0
  17. package/dist/hosted/hosted-node-store.d.ts +40 -0
  18. package/dist/hosted/hosted-node-store.js +126 -0
  19. package/dist/hosted/hosted-node-store.js.map +1 -0
  20. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  21. package/dist/hosted/hosted-node-supervisor.js +396 -0
  22. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  23. package/dist/hosted/hosted-node-watcher.d.ts +83 -0
  24. package/dist/hosted/hosted-node-watcher.js +224 -0
  25. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  26. package/dist/hosted/index.d.ts +16 -0
  27. package/dist/hosted/index.js +13 -0
  28. package/dist/hosted/index.js.map +1 -0
  29. package/dist/hosted/node-status.d.ts +35 -0
  30. package/dist/hosted/node-status.js +61 -0
  31. package/dist/hosted/node-status.js.map +1 -0
  32. package/dist/hosted/types.d.ts +134 -0
  33. package/dist/hosted/types.js +22 -0
  34. package/dist/hosted/types.js.map +1 -0
  35. package/dist/index.d.ts +12 -24
  36. package/dist/index.js +9 -19
  37. package/dist/index.js.map +1 -1
  38. package/dist/installer/config.d.ts +4 -34
  39. package/dist/installer/config.js +3 -18
  40. package/dist/installer/config.js.map +1 -1
  41. package/dist/installer/index.d.ts +7 -19
  42. package/dist/installer/index.js +14 -69
  43. package/dist/installer/index.js.map +1 -1
  44. package/dist/installer/paths.d.ts +0 -2
  45. package/dist/installer/paths.js +0 -4
  46. package/dist/installer/paths.js.map +1 -1
  47. package/dist/installer/wizard.d.ts +0 -7
  48. package/dist/installer/wizard.js +1 -8
  49. package/dist/installer/wizard.js.map +1 -1
  50. package/dist/nat/address-resolver.d.ts +18 -17
  51. package/dist/nat/address-resolver.js +79 -25
  52. package/dist/nat/address-resolver.js.map +1 -1
  53. package/dist/nat/address-watch.d.ts +37 -0
  54. package/dist/nat/address-watch.js +84 -0
  55. package/dist/nat/address-watch.js.map +1 -0
  56. package/dist/nat/ddns/duckdns.d.ts +2 -1
  57. package/dist/nat/ddns/duckdns.js +5 -1
  58. package/dist/nat/ddns/duckdns.js.map +1 -1
  59. package/dist/nat/ddns/index.d.ts +6 -0
  60. package/dist/nat/ddns/index.js.map +1 -1
  61. package/dist/nat/index.d.ts +12 -9
  62. package/dist/nat/index.js +6 -5
  63. package/dist/nat/index.js.map +1 -1
  64. package/dist/nat/nat-service.d.ts +203 -77
  65. package/dist/nat/nat-service.js +602 -237
  66. package/dist/nat/nat-service.js.map +1 -1
  67. package/dist/nat/nat-store.d.ts +12 -5
  68. package/dist/nat/nat-store.js +114 -30
  69. package/dist/nat/nat-store.js.map +1 -1
  70. package/dist/nat/port-mapper.d.ts +83 -79
  71. package/dist/nat/port-mapper.js +148 -189
  72. package/dist/nat/port-mapper.js.map +1 -1
  73. package/dist/nat/reachability.d.ts +31 -15
  74. package/dist/nat/reachability.js +97 -22
  75. package/dist/nat/reachability.js.map +1 -1
  76. package/dist/nat/types.d.ts +78 -27
  77. package/dist/nat/types.js +8 -5
  78. package/dist/nat/types.js.map +1 -1
  79. package/dist/orchestrator/host-process-orchestrator.d.ts +29 -71
  80. package/dist/orchestrator/host-process-orchestrator.js +78 -218
  81. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  82. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  83. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  84. package/dist/orchestrator/identity-file.js.map +1 -0
  85. package/dist/orchestrator/index.d.ts +2 -3
  86. package/dist/orchestrator/index.js +1 -1
  87. package/dist/orchestrator/index.js.map +1 -1
  88. package/dist/orchestrator/node-identity.d.ts +6 -6
  89. package/dist/orchestrator/node-identity.js +7 -7
  90. package/dist/orchestrator/node-identity.js.map +1 -1
  91. package/dist/orchestrator/port-allocator.d.ts +5 -6
  92. package/dist/orchestrator/port-allocator.js +9 -11
  93. package/dist/orchestrator/port-allocator.js.map +1 -1
  94. package/dist/orchestrator/state-store.d.ts +3 -5
  95. package/dist/orchestrator/state-store.js +2 -10
  96. package/dist/orchestrator/state-store.js.map +1 -1
  97. package/dist/orchestrator/types.d.ts +33 -22
  98. package/dist/orchestrator/types.js.map +1 -1
  99. package/dist/server/error-handler.d.ts +5 -25
  100. package/dist/server/error-handler.js +11 -74
  101. package/dist/server/error-handler.js.map +1 -1
  102. package/dist/server/events/types.d.ts +7 -11
  103. package/dist/server/events/types.js +2 -1
  104. package/dist/server/events/types.js.map +1 -1
  105. package/dist/server/index.d.ts +11 -37
  106. package/dist/server/index.js +30 -48
  107. package/dist/server/index.js.map +1 -1
  108. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  109. package/dist/server/routes/hosted-nodes.js +68 -0
  110. package/dist/server/routes/hosted-nodes.js.map +1 -0
  111. package/dist/server/routes/nat.d.ts +4 -1
  112. package/dist/server/routes/nat.js +13 -2
  113. package/dist/server/routes/nat.js.map +1 -1
  114. package/dist/server/routes/nodes.d.ts +6 -20
  115. package/dist/server/routes/nodes.js +7 -86
  116. package/dist/server/routes/nodes.js.map +1 -1
  117. package/dist/server/routes/settings.d.ts +2 -7
  118. package/dist/server/routes/settings.js +3 -7
  119. package/dist/server/routes/settings.js.map +1 -1
  120. package/dist/server/routes/status.d.ts +2 -25
  121. package/dist/server/routes/status.js +1 -11
  122. package/dist/server/routes/status.js.map +1 -1
  123. package/dist/server/server.js +8 -8
  124. package/dist/server/settings-store.d.ts +2 -2
  125. package/dist/server/settings-store.js +2 -2
  126. package/dist/server/static.js +1 -2
  127. package/dist/server/static.js.map +1 -1
  128. package/dist/ui/assets/index-4FWuU8ye.js +19 -0
  129. package/dist/ui/assets/index-4FWuU8ye.js.map +1 -0
  130. package/dist/ui/assets/index-Di9Q4d01.css +1 -0
  131. package/dist/ui/index.html +2 -2
  132. package/dist/update/index.d.ts +2 -2
  133. package/dist/update/index.js +2 -2
  134. package/dist/update/store.d.ts +1 -1
  135. package/dist/update/store.js +1 -1
  136. package/dist/update/types.d.ts +1 -1
  137. package/dist/update/types.js +1 -1
  138. package/package.json +5 -6
  139. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  140. package/src/__tests__/cli.smoke.test.ts +1 -1
  141. package/src/__tests__/global-setup.ts +2 -3
  142. package/src/__tests__/orchestrator-claim-secret.test.ts +112 -0
  143. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  144. package/src/__tests__/orchestrator-node-identity.test.ts +182 -203
  145. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  146. package/src/__tests__/orchestrator-push.test.ts +138 -146
  147. package/src/__tests__/orchestrator.test.ts +1019 -1063
  148. package/src/bin/host.ts +368 -553
  149. package/src/bin/join-output.ts +120 -0
  150. package/src/bin/nat-output.ts +177 -0
  151. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +173 -173
  152. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +265 -243
  153. package/src/hosted/__tests__/hosted-node-service.test.ts +609 -0
  154. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +474 -0
  155. package/src/hosted/__tests__/test-invitation.ts +33 -0
  156. package/src/hosted/claim-details.ts +96 -0
  157. package/src/hosted/hosted-node-service.ts +795 -0
  158. package/src/hosted/hosted-node-store.ts +144 -0
  159. package/src/hosted/hosted-node-supervisor.ts +439 -0
  160. package/src/hosted/hosted-node-watcher.ts +250 -0
  161. package/src/hosted/index.ts +53 -0
  162. package/src/hosted/node-status.ts +71 -0
  163. package/src/hosted/types.ts +153 -0
  164. package/src/index.ts +72 -91
  165. package/src/installer/__tests__/config.test.ts +15 -40
  166. package/src/installer/__tests__/installer.smoke.test.ts +5 -86
  167. package/src/installer/__tests__/wizard.test.ts +56 -64
  168. package/src/installer/config.ts +135 -175
  169. package/src/installer/index.ts +15 -83
  170. package/src/installer/paths.ts +104 -109
  171. package/src/installer/wizard.ts +87 -103
  172. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  173. package/src/nat/__tests__/nat-service.test.ts +433 -408
  174. package/src/nat/__tests__/nat-store.test.ts +127 -93
  175. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  176. package/src/nat/__tests__/reachability.test.ts +96 -13
  177. package/src/nat/address-resolver.ts +94 -52
  178. package/src/nat/address-watch.ts +92 -0
  179. package/src/nat/ddns/duckdns.ts +5 -1
  180. package/src/nat/ddns/index.ts +6 -0
  181. package/src/nat/index.ts +87 -71
  182. package/src/nat/nat-service.ts +889 -501
  183. package/src/nat/nat-store.ts +237 -154
  184. package/src/nat/port-mapper.ts +241 -329
  185. package/src/nat/reachability.ts +134 -25
  186. package/src/nat/types.ts +209 -159
  187. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  188. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  189. package/src/orchestrator/host-process-orchestrator.ts +81 -243
  190. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  191. package/src/orchestrator/index.ts +9 -10
  192. package/src/orchestrator/node-identity.ts +41 -41
  193. package/src/orchestrator/port-allocator.ts +71 -73
  194. package/src/orchestrator/state-store.ts +76 -90
  195. package/src/orchestrator/types.ts +34 -23
  196. package/src/server/__tests__/bus.test.ts +38 -38
  197. package/src/server/__tests__/error-handler.test.ts +81 -118
  198. package/src/server/__tests__/fakes.ts +40 -44
  199. package/src/server/__tests__/hosted-nodes-route.test.ts +160 -0
  200. package/src/server/__tests__/nodes-route.test.ts +114 -283
  201. package/src/server/__tests__/publishers.test.ts +193 -180
  202. package/src/server/__tests__/server.smoke.test.ts +134 -223
  203. package/src/server/__tests__/settings-route.test.ts +179 -195
  204. package/src/server/__tests__/sse-route.test.ts +119 -119
  205. package/src/server/__tests__/status-route.test.ts +82 -121
  206. package/src/server/error-handler.ts +112 -178
  207. package/src/server/events/types.ts +20 -24
  208. package/src/server/index.ts +223 -267
  209. package/src/server/routes/hosted-nodes.ts +83 -0
  210. package/src/server/routes/nat.ts +75 -63
  211. package/src/server/routes/nodes.ts +96 -188
  212. package/src/server/routes/settings.ts +115 -123
  213. package/src/server/routes/status.ts +102 -134
  214. package/src/server/server.ts +8 -8
  215. package/src/server/settings-store.ts +2 -2
  216. package/src/server/static.ts +1 -2
  217. package/src/update/index.ts +2 -2
  218. package/src/update/store.ts +1 -1
  219. package/src/update/types.ts +1 -1
  220. package/dist/auth/duration.d.ts +0 -1
  221. package/dist/auth/duration.js +0 -32
  222. package/dist/auth/duration.js.map +0 -1
  223. package/dist/auth/index.d.ts +0 -11
  224. package/dist/auth/index.js +0 -9
  225. package/dist/auth/index.js.map +0 -1
  226. package/dist/auth/self-label.d.ts +0 -36
  227. package/dist/auth/self-label.js +0 -57
  228. package/dist/auth/self-label.js.map +0 -1
  229. package/dist/auth/trust-circle-store.d.ts +0 -26
  230. package/dist/auth/trust-circle-store.js +0 -129
  231. package/dist/auth/trust-circle-store.js.map +0 -1
  232. package/dist/auth/trust-circle.d.ts +0 -156
  233. package/dist/auth/trust-circle.js +0 -307
  234. package/dist/auth/trust-circle.js.map +0 -1
  235. package/dist/auth/types.d.ts +0 -84
  236. package/dist/auth/types.js +0 -24
  237. package/dist/auth/types.js.map +0 -1
  238. package/dist/donation/donation-service.d.ts +0 -389
  239. package/dist/donation/donation-service.js +0 -828
  240. package/dist/donation/donation-service.js.map +0 -1
  241. package/dist/donation/donation-store.d.ts +0 -51
  242. package/dist/donation/donation-store.js +0 -152
  243. package/dist/donation/donation-store.js.map +0 -1
  244. package/dist/donation/donation-supervisor.js +0 -364
  245. package/dist/donation/donation-supervisor.js.map +0 -1
  246. package/dist/donation/grant-service.d.ts +0 -80
  247. package/dist/donation/grant-service.js +0 -210
  248. package/dist/donation/grant-service.js.map +0 -1
  249. package/dist/donation/grant-store.d.ts +0 -38
  250. package/dist/donation/grant-store.js +0 -135
  251. package/dist/donation/grant-store.js.map +0 -1
  252. package/dist/donation/index.d.ts +0 -20
  253. package/dist/donation/index.js +0 -17
  254. package/dist/donation/index.js.map +0 -1
  255. package/dist/donation/types.d.ts +0 -224
  256. package/dist/donation/types.js +0 -40
  257. package/dist/donation/types.js.map +0 -1
  258. package/dist/installer/identity.js.map +0 -1
  259. package/dist/owner/index.d.ts +0 -11
  260. package/dist/owner/index.js +0 -11
  261. package/dist/owner/index.js.map +0 -1
  262. package/dist/owner/owner-node-client.d.ts +0 -110
  263. package/dist/owner/owner-node-client.js +0 -184
  264. package/dist/owner/owner-node-client.js.map +0 -1
  265. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  266. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  267. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  268. package/dist/server/routes/grants-admin.d.ts +0 -29
  269. package/dist/server/routes/grants-admin.js +0 -58
  270. package/dist/server/routes/grants-admin.js.map +0 -1
  271. package/dist/server/routes/grants.d.ts +0 -28
  272. package/dist/server/routes/grants.js +0 -165
  273. package/dist/server/routes/grants.js.map +0 -1
  274. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  275. package/dist/server/routes/provision-request-validation.js +0 -97
  276. package/dist/server/routes/provision-request-validation.js.map +0 -1
  277. package/dist/server/routes/strands.d.ts +0 -20
  278. package/dist/server/routes/strands.js +0 -38
  279. package/dist/server/routes/strands.js.map +0 -1
  280. package/dist/server/routes/trust-circle.d.ts +0 -17
  281. package/dist/server/routes/trust-circle.js +0 -35
  282. package/dist/server/routes/trust-circle.js.map +0 -1
  283. package/dist/strands/index.d.ts +0 -7
  284. package/dist/strands/index.js +0 -6
  285. package/dist/strands/index.js.map +0 -1
  286. package/dist/strands/strand-service.d.ts +0 -50
  287. package/dist/strands/strand-service.js +0 -103
  288. package/dist/strands/strand-service.js.map +0 -1
  289. package/dist/strands/types.d.ts +0 -93
  290. package/dist/strands/types.js +0 -23
  291. package/dist/strands/types.js.map +0 -1
  292. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  293. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  294. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  295. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  296. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  297. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  298. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  299. package/src/auth/__tests__/duration.test.ts +0 -28
  300. package/src/auth/__tests__/self-label.test.ts +0 -85
  301. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  302. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  303. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  304. package/src/auth/duration.ts +0 -33
  305. package/src/auth/index.ts +0 -21
  306. package/src/auth/self-label.ts +0 -74
  307. package/src/auth/trust-circle-store.ts +0 -155
  308. package/src/auth/trust-circle.ts +0 -395
  309. package/src/auth/types.ts +0 -99
  310. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  311. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  312. package/src/donation/__tests__/grant-service.test.ts +0 -213
  313. package/src/donation/__tests__/grant-store.test.ts +0 -113
  314. package/src/donation/donation-service.ts +0 -1033
  315. package/src/donation/donation-store.ts +0 -176
  316. package/src/donation/donation-supervisor.ts +0 -411
  317. package/src/donation/grant-service.ts +0 -252
  318. package/src/donation/grant-store.ts +0 -152
  319. package/src/donation/index.ts +0 -65
  320. package/src/donation/types.ts +0 -260
  321. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  322. package/src/owner/index.ts +0 -12
  323. package/src/owner/owner-node-client.ts +0 -259
  324. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  325. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  326. package/src/server/__tests__/grants-route.test.ts +0 -394
  327. package/src/server/__tests__/strands-route.test.ts +0 -234
  328. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  329. package/src/server/routes/grants-admin.ts +0 -74
  330. package/src/server/routes/grants.ts +0 -193
  331. package/src/server/routes/provision-request-validation.ts +0 -105
  332. package/src/server/routes/strands.ts +0 -54
  333. package/src/server/routes/trust-circle.ts +0 -49
  334. package/src/strands/__tests__/strand-service.test.ts +0 -165
  335. package/src/strands/index.ts +0 -14
  336. package/src/strands/strand-service.ts +0 -135
  337. package/src/strands/types.ts +0 -102
@@ -1,175 +1,135 @@
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 { 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
+ * Non-secret push (FCM/APNs) settings. The actual private keys (and the FCM
26
+ * service-account / APNs key identifiers) live in the secret store — see
27
+ * `src/push/`. Only these app-config bits, which are safe to keep in plaintext
28
+ * `host.config.json`, are stored here. Absent ⇒ push is not configured.
29
+ */
30
+ export interface PushSettings {
31
+ /** APNs app-config (the `.p8` private key + key/team ids stay in the secret store). */
32
+ apns?: {
33
+ /** App bundle id → APNs `apns-topic` header. */
34
+ bundleId: string;
35
+ /** `true` → production APNs host; false/undefined → sandbox (dev/TestFlight). */
36
+ production?: boolean;
37
+ };
38
+ /** Per-(peer,strand) anti-spam cooldown (ms) forwarded into the node's push block. */
39
+ cooldownMs?: number;
40
+ /** Per-strand burst-coalescing window (ms) forwarded into the node's push block. */
41
+ debounceMs?: number;
42
+ }
43
+
44
+ export interface HostConfigFile {
45
+ version: 3;
46
+ /** A stable per-install id, for log correlation. */
47
+ installId: string;
48
+ /** Localhost-only management UI + API listener port. */
49
+ uiPort: number;
50
+ /** Self-reference (convenient for service-host ExecStart). */
51
+ dataDir: string;
52
+ /** Whether UPnP/NAT-PMP should be attempted at start time. */
53
+ upnpEnabled: boolean;
54
+ /** ISO timestamp the wizard wrote this file. */
55
+ installedAt: string;
56
+ /** Installer (= package) version that wrote this file. */
57
+ installerVersion: string;
58
+ /** Update-flow settings; defaults: { autoApply: false }. */
59
+ updates: UpdatesConfig;
60
+ /**
61
+ * Non-secret push settings (bundle id, sandbox/prod toggle, cooldown/debounce).
62
+ * Optional — absent when push is not configured. Private keys never live here;
63
+ * they are read from the secret store at node-spawn time (`src/push/`).
64
+ */
65
+ push?: PushSettings;
66
+ }
67
+
68
+ const CURRENT_VERSION = 3;
69
+
70
+ export function writeHostConfig(filePath: string, cfg: HostConfigFile): void {
71
+ if (cfg.version !== CURRENT_VERSION) {
72
+ throw new Error(`Refusing to write host.config.json with version=${cfg.version} (current: ${CURRENT_VERSION})`);
73
+ }
74
+ mkdirSync(dirname(filePath), { recursive: true });
75
+ writeFileSync(filePath, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
76
+ }
77
+
78
+ /** Apply a partial patch to the persisted config (read-modify-write). */
79
+ export function updateHostConfig(
80
+ filePath: string,
81
+ patch: Partial<Omit<HostConfigFile, 'version'>>,
82
+ ): HostConfigFile {
83
+ const current = readHostConfig(filePath);
84
+ const next: HostConfigFile = {
85
+ ...current,
86
+ ...patch,
87
+ updates: { ...current.updates, ...(patch.updates ?? {}) },
88
+ version: CURRENT_VERSION,
89
+ };
90
+ writeHostConfig(filePath, next);
91
+ return next;
92
+ }
93
+
94
+ export function readHostConfig(filePath: string): HostConfigFile {
95
+ const raw = readFileSync(filePath, 'utf8');
96
+ let parsed: unknown;
97
+ try {
98
+ parsed = JSON.parse(raw);
99
+ } catch (err) {
100
+ throw new Error(`Failed to parse host.config.json at ${filePath}: ${(err as Error).message}`, { cause: err });
101
+ }
102
+ if (!parsed || typeof parsed !== 'object') {
103
+ throw new Error(`host.config.json at ${filePath} is not a JSON object`);
104
+ }
105
+ const obj = parsed as Record<string, unknown>;
106
+ if (obj.version !== CURRENT_VERSION) {
107
+ throw new Error(
108
+ `host.config.json at ${filePath} has unsupported version=${obj.version} (this build expects ${CURRENT_VERSION})`,
109
+ );
110
+ }
111
+ if (!isHostConfigShape(obj)) {
112
+ throw new Error(`host.config.json at ${filePath} is missing required fields`);
113
+ }
114
+ return obj;
115
+ }
116
+
117
+ function isHostConfigShape(v: unknown): v is HostConfigFile {
118
+ if (!v || typeof v !== 'object') return false;
119
+ const o = v as Record<string, unknown>;
120
+ if (
121
+ typeof o.installId !== 'string' ||
122
+ typeof o.uiPort !== 'number' ||
123
+ typeof o.dataDir !== 'string' ||
124
+ typeof o.upnpEnabled !== 'boolean' ||
125
+ typeof o.installedAt !== 'string' ||
126
+ typeof o.installerVersion !== 'string'
127
+ ) {
128
+ return false;
129
+ }
130
+ const updates = o.updates as Record<string, unknown> | undefined;
131
+ if (!updates || typeof updates !== 'object') return false;
132
+ if (typeof updates.autoApply !== 'boolean') return false;
133
+ if (updates.manifestUrl !== undefined && typeof updates.manifestUrl !== 'string') return false;
134
+ return true;
135
+ }
@@ -5,8 +5,8 @@
5
5
  * `tickets/implement/6.4.1-cadre-host-installer.md`.
6
6
  *
7
7
  * The class is purposefully thin — heavy lifting lives in the focused
8
- * subsystem modules (wizard.ts, identity.ts, config.ts,
9
- * service-host/{systemd,launchd,nssm}.ts, browser.ts).
8
+ * subsystem modules (wizard.ts, config.ts, service-host/{systemd,launchd,nssm}.ts,
9
+ * browser.ts).
10
10
  */
11
11
 
12
12
  import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
@@ -17,12 +17,10 @@ import { NatStore } from '../nat/index.js';
17
17
 
18
18
  import { openBrowser } from './browser.js';
19
19
  import { writeHostConfig, type HostConfigFile } from './config.js';
20
- import { generateIdentity } from './identity.js';
21
20
  import {
22
21
  configPath,
23
22
  defaultHostJs,
24
23
  defaultServiceDir,
25
- identityPath,
26
24
  logsDir,
27
25
  } from './paths.js';
28
26
  import {
@@ -35,7 +33,6 @@ import {
35
33
  runWizard,
36
34
  runWizardWith,
37
35
  DEFAULT_UI_PORT,
38
- DEFAULT_LIBP2P_PORT,
39
36
  type WizardAnswers,
40
37
  type WizardDefaults,
41
38
  } from './wizard.js';
@@ -54,25 +51,18 @@ export interface InstallOptions {
54
51
  dataDir?: string;
55
52
  /** Override the UI port. */
56
53
  uiPort?: number;
57
- /** Override the libp2p port. */
58
- libp2pPort?: number;
59
54
  /** Disable UPnP probing on first run. */
60
55
  noUpnp?: boolean;
61
- /** Also run the host's own personal cadre here (founder persona). Default false. */
62
- ownCadre?: boolean;
63
56
  /** Open browser at end (default true; suppressed by --no-browser or non-TTY). */
64
57
  openBrowser?: boolean;
65
- /** Skip the enrollment-invite step (no QR printed). */
66
- noInvite?: boolean;
67
58
  /** System-wide install — v1 emits a not-yet-supported error. */
68
59
  system?: boolean;
69
60
  /** Path to node.exe / node binary used by the service-host unit. */
70
61
  nodePath?: string;
71
62
  /**
72
- * Write the data dir (identity, config, NAT seed) but register no OS service.
63
+ * Write the data dir (config, NAT seed) but register no OS service.
73
64
  * The host is then run by hand with `cadre-host start`. Nothing is listening
74
- * after such an install, so the browser-open and enrollment-invite steps are
75
- * skipped too.
65
+ * after such an install, so the browser-open step is skipped too.
76
66
  */
77
67
  noService?: boolean;
78
68
  /** Test-only: stub the service-host registration. */
@@ -88,15 +78,10 @@ export interface InstallResult {
88
78
  serviceName?: string;
89
79
  /** Path to the rendered config file. */
90
80
  configPath: string;
91
- /** Generated enrollment invite, unless --no-invite. */
92
- enrollmentInvite?: {
93
- encodedInvite: string;
94
- expiresAt?: string;
95
- };
96
81
  }
97
82
 
98
83
  export interface UninstallOptions {
99
- /** Also remove the data dir (default false — preserve trust circle + identity). */
84
+ /** Also remove the data dir (default false — preserve hosted-node records, node identities and NAT state). */
100
85
  removeData: boolean;
101
86
  /** Skip the confirmation prompt (for scripts). */
102
87
  yes: boolean;
@@ -136,40 +121,30 @@ export class Installer {
136
121
  mkdirSync(answers.dataDir, { recursive: true });
137
122
  mkdirSync(logsDir(answers.dataDir), { recursive: true });
138
123
 
139
- // 3. Identity + config. Don't overwrite an existing key — re-running
140
- // install on a populated data dir must preserve the host's peer ID.
141
- const idPath = identityPath(answers.dataDir);
142
- if (existsSync(idPath)) {
143
- log('identity already exists at %s — leaving it', idPath);
144
- } else {
145
- await generateIdentity(idPath);
146
- }
147
-
124
+ // 3. Config.
148
125
  const cfg: HostConfigFile = {
149
- version: 2,
126
+ version: 3,
150
127
  installId: randomInstallId(),
151
128
  uiPort: answers.uiPort,
152
- libp2pPort: answers.libp2pPort,
153
129
  dataDir: answers.dataDir,
154
- identityPath: idPath,
155
130
  upnpEnabled: answers.upnpEnabled,
156
131
  installedAt: new Date().toISOString(),
157
132
  installerVersion: this.installerVersion,
158
133
  updates: { autoApply: false },
159
- ownCadre: { enabled: answers.ownCadre },
160
134
  };
161
135
  const cfgPath = configPath(answers.dataDir);
162
136
  writeHostConfig(cfgPath, cfg);
163
137
 
164
- // 4. Seed nat.json with the chosen libp2p port + UPnP flag so the
165
- // NatService picks them up on first start.
166
- seedNatSettings(answers.dataDir, answers.libp2pPort, answers.upnpEnabled);
138
+ // 4. Seed nat.json with the UPnP flag so the NatService picks it up on
139
+ // first start. Ports are not seeded: the service maps every hosted
140
+ // node's ports from the orchestrator's allocations.
141
+ seedNatSettings(answers.dataDir, answers.upnpEnabled);
167
142
 
168
143
  const uiUrl = `http://127.0.0.1:${answers.uiPort}/`;
169
144
 
170
145
  // 5. Service-host registration.
171
146
  if (opts.noService) {
172
- log('--no-service: skipping service registration, browser open and enrollment invite');
147
+ log('--no-service: skipping service registration and browser open');
173
148
  return { dataDir: answers.dataDir, uiUrl, configPath: cfgPath };
174
149
  }
175
150
  const serviceHost = opts.serviceHost ?? createServiceHost(this.platform);
@@ -187,21 +162,11 @@ export class Installer {
187
162
  openBrowser(uiUrl);
188
163
  }
189
164
 
190
- // 7. Enrollment invite (best-effort — the listener may not be up yet).
191
- let invite: InstallResult['enrollmentInvite'];
192
- if (!opts.noInvite && !opts.nonInteractive) {
193
- invite = await fetchEnrollmentInvite(answers.uiPort).catch((err) => {
194
- log('enrollment-invite fetch failed: %s', (err as Error).message);
195
- return undefined;
196
- });
197
- }
198
-
199
165
  return {
200
166
  dataDir: answers.dataDir,
201
167
  uiUrl,
202
168
  serviceName: serviceHost.name,
203
169
  configPath: cfgPath,
204
- ...(invite ? { enrollmentInvite: invite } : {}),
205
170
  };
206
171
  }
207
172
 
@@ -241,10 +206,8 @@ export class Installer {
241
206
  return {
242
207
  dataDir: opts.dataDir ?? defaults.dataDir,
243
208
  uiPort: opts.uiPort ?? defaults.uiPort,
244
- libp2pPort: opts.libp2pPort ?? defaults.libp2pPort,
245
209
  upnpEnabled: opts.noUpnp ? false : defaults.upnpEnabled,
246
210
  configureDdns: false,
247
- ownCadre: opts.ownCadre ?? defaults.ownCadre,
248
211
  };
249
212
  }
250
213
 
@@ -261,25 +224,19 @@ export class Installer {
261
224
  const defaults: WizardDefaults = {
262
225
  dataDir: opts.dataDir ?? platformDefaults.dataDir,
263
226
  uiPort: opts.uiPort ?? platformDefaults.uiPort,
264
- libp2pPort: opts.libp2pPort ?? platformDefaults.libp2pPort,
265
227
  upnpEnabled: opts.noUpnp ? false : platformDefaults.upnpEnabled,
266
- ownCadre: opts.ownCadre ?? platformDefaults.ownCadre,
267
228
  };
268
229
  const wizard = opts.wizard ?? runWizard;
269
230
  return await wizard(defaults);
270
231
  }
271
232
  }
272
233
 
273
- export { runWizardWith, defaultsForPlatform, DEFAULT_UI_PORT, DEFAULT_LIBP2P_PORT };
234
+ export { runWizardWith, defaultsForPlatform, DEFAULT_UI_PORT };
274
235
 
275
- function seedNatSettings(dataDir: string, libp2pPort: number, upnpEnabled: boolean): void {
236
+ function seedNatSettings(dataDir: string, upnpEnabled: boolean): void {
276
237
  const store = new NatStore(dataDir);
277
238
  // `update()` merges + persists.
278
- store.update({
279
- externalPort: libp2pPort,
280
- internalPort: libp2pPort,
281
- upnpEnabled,
282
- });
239
+ store.update({ upnpEnabled });
283
240
  }
284
241
 
285
242
  function randomInstallId(): string {
@@ -298,30 +255,5 @@ function readPackageVersion(): string {
298
255
  }
299
256
  }
300
257
 
301
- async function fetchEnrollmentInvite(uiPort: number): Promise<InstallResult['enrollmentInvite']> {
302
- const url = `http://127.0.0.1:${uiPort}/auth/invites`;
303
- // Node 18+'s fetch has no default timeout. Bound it so a slow / hung
304
- // local-UI listener can't wedge `cadre-host install` indefinitely.
305
- const controller = new AbortController();
306
- const timer = setTimeout(() => controller.abort(), 5000);
307
- try {
308
- const response = await fetch(url, {
309
- method: 'POST',
310
- headers: { 'content-type': 'application/json' },
311
- body: JSON.stringify({ label: 'Install — first device', ttlMs: 24 * 60 * 60 * 1000 }),
312
- signal: controller.signal,
313
- });
314
- if (!response.ok) return undefined;
315
- const body = await response.json() as { encodedInvite?: string; expiresAt?: string };
316
- if (!body.encodedInvite) return undefined;
317
- return {
318
- encodedInvite: body.encodedInvite,
319
- ...(body.expiresAt ? { expiresAt: body.expiresAt } : {}),
320
- };
321
- } finally {
322
- clearTimeout(timer);
323
- }
324
- }
325
-
326
258
  // Exposed for tests that drive the wizard with a custom prompt fn.
327
259
  export { runWizard };