@serfab/cadre-host 1.12.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
package/src/index.ts CHANGED
@@ -4,18 +4,12 @@
4
4
  * This package is the sibling of @serfab/cadre-provider for self-hosted
5
5
  * basement-PC deployments. It depends on @serfab/cadre-provider for the
6
6
  * orchestration interface and container lifecycle types but ships its own
7
- * orchestrator (host processes, not Docker), auth (trust circle, not API
8
- * keys), installer, NAT layer, and local management UI.
7
+ * orchestrator (host processes, not Docker), the hosted-node service behind
8
+ * "Join a cadre", installer, NAT layer, and local management UI.
9
9
  *
10
- * Implementations of HostProcessOrchestrator, TrustCircleAuth, the NAT
11
- * layer, installer scripts, and the local UI live in sibling subdirectories
12
- * under src/ and are added by their respective tickets:
13
- *
14
- * - cadre-host-process-orchestrator [DONE]
15
- * - cadre-host-trust-circle [DONE]
16
- * - cadre-host-nat
17
- * - cadre-host-installer
18
- * - cadre-host-local-ui
10
+ * HostProcessOrchestrator, the hosted-node layer, the NAT layer, the
11
+ * installer, and the local UI server each live in a sibling subdirectory
12
+ * under src/.
19
13
  */
20
14
 
21
15
  export type {
@@ -27,84 +21,57 @@ export type {
27
21
  ContainerResources,
28
22
  } from '@serfab/cadre-provider';
29
23
 
30
- export { HostProcessOrchestrator, OWNER_CONTAINER_ID } from './orchestrator/index.js';
24
+ export { HostProcessOrchestrator } from './orchestrator/index.js';
31
25
  export type {
32
26
  HostProcessConfig,
27
+ HostedSpawnRequest,
33
28
  PersistedHandle,
34
29
  ManagedNodeInfo,
35
30
  NodeStateListener,
36
31
  NodePorts,
37
- OwnerAdminEndpoint,
38
- OwnerSpawnConfig,
39
32
  } from './orchestrator/index.js';
40
33
 
41
- export { OwnerNodeClient, OwnerNodeUnavailableError } from './owner/index.js';
42
- export type { OwnerNodeClientOptions } from './owner/index.js';
43
-
44
- export {
45
- TrustCircleService,
46
- TrustCircleStore,
47
- TrustCircleError,
48
- createTrustCircleHandlers,
49
- parseDuration,
50
- ensureSelfLabel,
51
- DEFAULT_INVITE_TTL_MS,
52
- } from './auth/index.js';
53
- export type {
54
- TrustCircleServiceOptions,
55
- CadreNodeLike,
56
- TrustCircleMember,
57
- PendingInvite,
58
- TrustCircleFile,
59
- TrustCircleSnapshot,
60
- TrustCircleHandlers,
61
- TrustCircleErrorCode,
62
- } from './auth/index.js';
63
-
64
- /* ──────────────── strand management ──────────────── */
65
-
66
- export { StrandService, StrandError, createStrandHandlers } from './strands/index.js';
67
- export type {
68
- StrandServiceOptions,
69
- CadreNodeLike as StrandCadreNodeLike,
70
- StrandSummary,
71
- StrandListSnapshot,
72
- StrandRemovalResult,
73
- StrandHandlers,
74
- StrandErrorCode,
75
- } from './strands/index.js';
76
-
77
- /* ──────────────── donation grant tokens ──────────────── */
34
+ /* ──────────────── hosted nodes ("Join a cadre") ──────────────── */
78
35
 
79
36
  export {
80
- GrantStore,
81
- GrantService,
82
- createGrantAdminHandlers,
83
- GrantError,
84
- DEFAULT_MAX_NODES,
85
- DonationStore,
86
- DonationService,
87
- DonationError,
88
- } from './donation/index.js';
37
+ HostedNodeStore,
38
+ HostedNodeService,
39
+ HostedNodeSupervisor,
40
+ HostedNodeError,
41
+ HOSTED_NODE_SPAWNING_TTL_MS,
42
+ HOSTED_NODE_REAP_SWEEP_MS,
43
+ HOSTED_NODE_CLAIM_POLL_MS,
44
+ HOSTED_NODE_CONNECTED_POLL_MS,
45
+ HOSTED_NODE_RESPAWN_BACKOFF_BASE_MS,
46
+ HOSTED_NODE_RESPAWN_BACKOFF_MAX_MS,
47
+ HOSTED_NODE_RESPAWN_MAX_ATTEMPTS,
48
+ HOSTED_NODE_RESPAWN_HEALTHY_MS,
49
+ HOSTED_NODE_RESPAWN_SWEEP_MS,
50
+ PLACEHOLDER_PARTY,
51
+ } from './hosted/index.js';
89
52
  export type {
90
- Grant,
91
- GrantDenyReason,
92
- GrantValidation,
93
- GrantValidator,
94
- GrantFile,
95
- GrantAdminHandlers,
96
- GrantErrorCode,
97
- GrantServiceOptions,
98
- Donation,
99
- DonationView,
100
- DonationStatus,
101
- DonationFile,
102
- DonationErrorCode,
103
- DonationProvisionRequest,
104
- DonationSeedResult,
105
- DonationPeerInfo,
106
- DonationServiceOptions,
107
- } from './donation/index.js';
53
+ ClaimDetails,
54
+ HostedNode,
55
+ HostedNodeAddressSource,
56
+ HostedNodeChange,
57
+ HostedNodeChangeKind,
58
+ HostedNodeChangeListener,
59
+ HostedNodeErrorCode,
60
+ HostedNodeClaimJoin,
61
+ HostedNodeFile,
62
+ HostedNodeInvitationJoin,
63
+ HostedNodeJoin,
64
+ HostedNodeJoinView,
65
+ HostedNodeOrchestrator,
66
+ HostedNodeServiceOptions,
67
+ HostedNodeStatus,
68
+ HostedNodeSupervisorOptions,
69
+ HostedNodeView,
70
+ JoinOptions,
71
+ RespawnOptions,
72
+ RespawnResult,
73
+ SupervisedOrchestrator,
74
+ } from './hosted/index.js';
108
75
 
109
76
  export { Installer, readHostConfig, updateHostConfig, writeHostConfig } from './installer/index.js';
110
77
  export type {
@@ -179,11 +146,16 @@ export {
179
146
  NatStore,
180
147
  NatError,
181
148
  createNatHandlers,
149
+ NAT_UNMAP_GRACE_MS,
150
+ NAT_RECONCILE_INTERVAL_MS,
151
+ NAT_IP_REDETECT_INTERVAL_MS,
182
152
  ExternalIpDetector,
183
- PortMapperService,
184
- createDefaultPortMapper,
185
- evaluateReachability,
186
- buildInviteAddresses,
153
+ UpnpPortMapper,
154
+ pickLanAddress,
155
+ evaluateNodeReachability,
156
+ evaluateHostReachability,
157
+ buildPublicAddresses,
158
+ isPublicIpv4,
187
159
  BUILTIN_PROVIDERS,
188
160
  getProvider,
189
161
  listProviders,
@@ -196,30 +168,41 @@ export {
196
168
  KEYTAR_SERVICE,
197
169
  DEFAULT_LEASE_TTL_MS,
198
170
  DEFAULT_REFRESH_MS,
171
+ GATEWAY_DISCOVERY_TIMEOUT_MS,
199
172
  isPlausibleIp,
200
173
  } from './nat/index.js';
201
174
  export type {
202
175
  NatServiceOptions,
203
- CadreNodeLike as NatCadreNodeLike,
176
+ NatNodeSource,
177
+ NatChangeListener,
178
+ NodeAddressesStaleListener,
204
179
  NatStatusSnapshot,
180
+ NatGatewayStatus,
205
181
  NatDdnsStatus,
206
182
  NatSettingsFile,
207
183
  NatDdnsSettings,
208
184
  NatHandlers,
209
185
  NatErrorCode,
210
- PortForwardMode,
211
186
  DirectReachability,
187
+ NodeReachability,
188
+ NodeVerdict,
189
+ PortKind,
190
+ PortRoute,
191
+ PortRouteSource,
192
+ ManualForward,
193
+ ManualForwardPatch,
212
194
  DdnsProviderInfo,
213
195
  DdnsConfigField,
214
196
  ExternalIpResult,
215
197
  RouterIpProbe,
216
198
  ExternalIpDetectorOptions,
217
199
  PortMapper,
218
- PortMappingResult,
219
- PortMapOptions,
220
- PortMapperState,
221
- PortMapperServiceOptions,
222
- BuildInviteAddressesInput,
200
+ GatewayInfo,
201
+ PortMapRequest,
202
+ PortMapResult,
203
+ NodeVerdictInput,
204
+ NodeVerdictResult,
205
+ PublicAddressInput,
223
206
  DdnsProvider,
224
207
  DdnsProviderDeps,
225
208
  DdnsUpdaterOptions,
@@ -236,8 +219,6 @@ export {
236
219
  HostSettingsStore,
237
220
  } from './server/index.js';
238
221
  export type {
239
- FounderServices,
240
- HostRole,
241
222
  LocalUiServer,
242
223
  LocalUiServerOptions,
243
224
  LocalUiEvent,
@@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
 
6
- import { readHostConfig, updateHostConfig, writeHostConfig, hostOwnsCadre, type HostConfigFile } from '../config.js';
6
+ import { readHostConfig, updateHostConfig, writeHostConfig, type HostConfigFile } from '../config.js';
7
7
 
8
8
  describe('host.config.json round-trip', () => {
9
9
  let tmp: string;
@@ -18,12 +18,10 @@ describe('host.config.json round-trip', () => {
18
18
 
19
19
  function makeCfg(overrides: Partial<HostConfigFile> = {}): HostConfigFile {
20
20
  return {
21
- version: 2,
21
+ version: 3,
22
22
  installId: 'abc123',
23
23
  uiPort: 8765,
24
- libp2pPort: 4001,
25
24
  dataDir: tmp,
26
- identityPath: join(tmp, 'identity.key'),
27
25
  upnpEnabled: true,
28
26
  installedAt: '2026-01-01T00:00:00.000Z',
29
27
  installerVersion: '0.6.0',
@@ -39,10 +37,11 @@ describe('host.config.json round-trip', () => {
39
37
  expect(readHostConfig(path)).toEqual(cfg);
40
38
  });
41
39
 
42
- it('rejects v1 files without rewriting them', () => {
40
+ // A version-2 file is a founder-era install: it is refused, never migrated.
41
+ it('rejects v2 files without rewriting them', () => {
43
42
  const path = join(tmp, 'host.config.json');
44
- const v1 = {
45
- version: 1,
43
+ const v2 = {
44
+ version: 2,
46
45
  installId: 'abc123',
47
46
  uiPort: 8765,
48
47
  libp2pPort: 4001,
@@ -51,10 +50,12 @@ describe('host.config.json round-trip', () => {
51
50
  upnpEnabled: true,
52
51
  installedAt: '2026-01-01T00:00:00.000Z',
53
52
  installerVersion: '0.6.0',
53
+ updates: { autoApply: false },
54
+ ownCadre: { enabled: true },
54
55
  };
55
- const raw = JSON.stringify(v1);
56
+ const raw = JSON.stringify(v2);
56
57
  writeFileSync(path, raw);
57
- expect(() => readHostConfig(path)).toThrow(/unsupported version=1/);
58
+ expect(() => readHostConfig(path)).toThrow(/unsupported version=2/);
58
59
  expect(readFileSync(path, 'utf8')).toBe(raw);
59
60
  });
60
61
 
@@ -79,20 +80,20 @@ describe('host.config.json round-trip', () => {
79
80
 
80
81
  it('rejects missing required fields', () => {
81
82
  const path = join(tmp, 'host.config.json');
82
- writeFileSync(path, JSON.stringify({ version: 2, uiPort: 1234 }));
83
+ writeFileSync(path, JSON.stringify({ version: 3, uiPort: 1234 }));
83
84
  expect(() => readHostConfig(path)).toThrow(/missing required fields/);
84
85
  });
85
86
 
86
87
  it('refuses to write the wrong version', () => {
87
88
  const path = join(tmp, 'host.config.json');
88
- expect(() => writeHostConfig(path, { ...makeCfg(), version: 3 as never })).toThrow(/version=3/);
89
+ expect(() => writeHostConfig(path, { ...makeCfg(), version: 2 as never })).toThrow(/version=2/);
89
90
  });
90
91
 
91
92
  it('update patches fields and re-stamps the current version', () => {
92
93
  const path = join(tmp, 'host.config.json');
93
94
  writeHostConfig(path, makeCfg());
94
95
  const next = updateHostConfig(path, { updates: { autoApply: true } });
95
- expect(next.version).toBe(2);
96
+ expect(next.version).toBe(3);
96
97
  expect(next.updates).toEqual({ autoApply: true });
97
98
  expect(next.installId).toBe('abc123');
98
99
  expect(readHostConfig(path)).toEqual(next);
@@ -102,33 +103,7 @@ describe('host.config.json round-trip', () => {
102
103
  const path = join(tmp, 'host.config.json');
103
104
  writeHostConfig(path, makeCfg());
104
105
  const patch = { version: 1 } as unknown as Partial<Omit<HostConfigFile, 'version'>>;
105
- expect(updateHostConfig(path, patch).version).toBe(2);
106
- expect(readHostConfig(path).version).toBe(2);
107
- });
108
-
109
- it('round-trips ownCadre and hostOwnsCadre reflects it', () => {
110
- const path = join(tmp, 'host.config.json');
111
- const cfg = makeCfg({ ownCadre: { enabled: true } });
112
- writeHostConfig(path, cfg);
113
- const read = readHostConfig(path);
114
- expect(read.ownCadre).toEqual({ enabled: true });
115
- expect(hostOwnsCadre(read)).toBe(true);
116
- });
117
-
118
- it('treats an absent ownCadre as donor-only (hostOwnsCadre false)', () => {
119
- // A pre-ownCadre v2 config (field absent) must read back cleanly and be
120
- // donor-only — no migration, field stays absent.
121
- const path = join(tmp, 'host.config.json');
122
- const cfg = makeCfg();
123
- writeHostConfig(path, cfg);
124
- const read = readHostConfig(path);
125
- expect(read.ownCadre).toBeUndefined();
126
- expect(hostOwnsCadre(read)).toBe(false);
127
- });
128
-
129
- it('rejects a malformed ownCadre shape', () => {
130
- const path = join(tmp, 'host.config.json');
131
- writeFileSync(path, JSON.stringify({ ...makeCfg(), ownCadre: { enabled: 'yes' } }));
132
- expect(() => readHostConfig(path)).toThrow(/missing required fields/);
106
+ expect(updateHostConfig(path, patch).version).toBe(3);
107
+ expect(readHostConfig(path).version).toBe(3);
133
108
  });
134
109
  });
@@ -1,4 +1,4 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
2
  import { existsSync, mkdtempSync, rmSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
@@ -45,16 +45,14 @@ describe('Installer smoke', () => {
45
45
  rmSync(tmp, { recursive: true, force: true });
46
46
  });
47
47
 
48
- it('install creates config + identity + invokes service-host', async () => {
48
+ it('install creates config + invokes service-host', async () => {
49
49
  const fake = new FakeServiceHost();
50
50
  const installer = new Installer({ platform: 'linux', installerVersion: 'test-1.0.0' });
51
51
  const result = await installer.install({
52
52
  nonInteractive: true,
53
53
  dataDir: tmp,
54
54
  uiPort: 19999,
55
- libp2pPort: 14001,
56
55
  openBrowser: false,
57
- noInvite: true,
58
56
  serviceHost: fake,
59
57
  });
60
58
 
@@ -64,7 +62,8 @@ describe('Installer smoke', () => {
64
62
  expect(result.configPath).toBe(join(tmp, 'host.config.json'));
65
63
 
66
64
  expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
67
- expect(existsSync(join(tmp, 'identity.key'))).toBe(true);
65
+ // The host holds no identity of its own; each node writes its own key in its workdir.
66
+ expect(existsSync(join(tmp, 'identity.key'))).toBe(false);
68
67
  expect(existsSync(join(tmp, 'nat.json'))).toBe(true);
69
68
  expect(existsSync(join(tmp, 'logs'))).toBe(true);
70
69
 
@@ -79,7 +78,6 @@ describe('Installer smoke', () => {
79
78
  nonInteractive: true,
80
79
  dataDir: tmp,
81
80
  uiPort: 19995,
82
- libp2pPort: 14005,
83
81
  noService: true,
84
82
  serviceHost: fake,
85
83
  });
@@ -88,35 +86,9 @@ describe('Installer smoke', () => {
88
86
  expect(result.serviceName).toBeUndefined();
89
87
  expect(result.uiUrl).toBe('http://127.0.0.1:19995/');
90
88
  expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
91
- expect(existsSync(join(tmp, 'identity.key'))).toBe(true);
92
89
  expect(existsSync(join(tmp, 'nat.json'))).toBe(true);
93
90
  });
94
91
 
95
- it('noService on an interactive install skips the enrollment-invite fetch', async () => {
96
- const fetchSpy = vi.spyOn(globalThis, 'fetch');
97
- try {
98
- const installer = new Installer({ platform: 'linux', installerVersion: 'test-1.0.0' });
99
- const result = await installer.install({
100
- nonInteractive: false,
101
- noService: true,
102
- wizard: async () => ({
103
- dataDir: tmp,
104
- uiPort: 19994,
105
- libp2pPort: 14006,
106
- upnpEnabled: false,
107
- configureDdns: false,
108
- ownCadre: false,
109
- }),
110
- });
111
-
112
- expect(fetchSpy).not.toHaveBeenCalled();
113
- expect(result.enrollmentInvite).toBeUndefined();
114
- expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
115
- } finally {
116
- fetchSpy.mockRestore();
117
- }
118
- });
119
-
120
92
  it('uninstall removes data only when --remove-data is set', async () => {
121
93
  const fake = new FakeServiceHost();
122
94
  const installer = new Installer({ platform: 'linux' });
@@ -124,9 +96,7 @@ describe('Installer smoke', () => {
124
96
  nonInteractive: true,
125
97
  dataDir: tmp,
126
98
  uiPort: 19998,
127
- libp2pPort: 14002,
128
99
  openBrowser: false,
129
- noInvite: true,
130
100
  serviceHost: fake,
131
101
  });
132
102
  expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
@@ -148,7 +118,6 @@ describe('Installer smoke', () => {
148
118
  system: true,
149
119
  serviceHost: fake,
150
120
  openBrowser: false,
151
- noInvite: true,
152
121
  })).rejects.toThrow(/system.*not yet supported/);
153
122
  expect(fake.installCalls).toHaveLength(0);
154
123
  });
@@ -162,15 +131,13 @@ describe('Installer smoke', () => {
162
131
  // returned answers stand on their own.
163
132
  const fake = new FakeServiceHost();
164
133
  const installer = new Installer({ platform: 'linux' });
165
- let seenDefaults: { dataDir: string; uiPort: number; libp2pPort: number; upnpEnabled: boolean; ownCadre: boolean } | undefined;
134
+ let seenDefaults: { dataDir: string; uiPort: number; upnpEnabled: boolean } | undefined;
166
135
  await installer.install({
167
136
  nonInteractive: false,
168
137
  dataDir: tmp,
169
138
  uiPort: 19997,
170
- libp2pPort: 14003,
171
139
  noUpnp: true,
172
140
  openBrowser: false,
173
- noInvite: true,
174
141
  serviceHost: fake,
175
142
  wizard: async (defaults) => {
176
143
  seenDefaults = { ...defaults };
@@ -178,10 +145,8 @@ describe('Installer smoke', () => {
178
145
  return {
179
146
  dataDir: defaults.dataDir,
180
147
  uiPort: defaults.uiPort,
181
- libp2pPort: defaults.libp2pPort,
182
148
  upnpEnabled: defaults.upnpEnabled,
183
149
  configureDdns: false,
184
- ownCadre: defaults.ownCadre,
185
150
  };
186
151
  },
187
152
  });
@@ -189,9 +154,7 @@ describe('Installer smoke', () => {
189
154
  expect(seenDefaults).toEqual({
190
155
  dataDir: tmp,
191
156
  uiPort: 19997,
192
- libp2pPort: 14003,
193
157
  upnpEnabled: false,
194
- ownCadre: false,
195
158
  });
196
159
  expect(fake.installCalls).toHaveLength(1);
197
160
  expect(fake.installCalls[0]!.dataDir).toBe(tmp);
@@ -205,59 +168,15 @@ describe('Installer smoke', () => {
205
168
  await installer.install({
206
169
  nonInteractive: false,
207
170
  openBrowser: false,
208
- noInvite: true,
209
171
  serviceHost: fake,
210
172
  wizard: async () => ({
211
173
  dataDir: tmp,
212
174
  uiPort: 19996,
213
- libp2pPort: 14004,
214
175
  upnpEnabled: true,
215
176
  configureDdns: false,
216
- ownCadre: false,
217
177
  }),
218
178
  });
219
179
  expect(fake.installCalls).toHaveLength(1);
220
180
  expect(fake.installCalls[0]!.dataDir).toBe(tmp);
221
181
  });
222
-
223
- it('writes ownCadre.enabled=false by default and true when opted in', async () => {
224
- const { readHostConfig } = await import('../config.js');
225
-
226
- // Default (donor-only): --own-cadre absent → ownCadre.enabled false.
227
- const fakeA = new FakeServiceHost();
228
- const installerA = new Installer({ platform: 'linux' });
229
- await installerA.install({
230
- nonInteractive: true, dataDir: tmp, openBrowser: false, noInvite: true, serviceHost: fakeA,
231
- });
232
- const donorCfg = readHostConfig(join(tmp, 'host.config.json'));
233
- expect(donorCfg.ownCadre).toEqual({ enabled: false });
234
-
235
- // Opt-in: ownCadre:true (the --own-cadre flag) → ownCadre.enabled true.
236
- const tmp2 = mkdtempSync(join(tmpdir(), 'cadre-host-installer-owncadre-'));
237
- try {
238
- const fakeB = new FakeServiceHost();
239
- const installerB = new Installer({ platform: 'linux' });
240
- await installerB.install({
241
- nonInteractive: true, dataDir: tmp2, ownCadre: true, openBrowser: false, noInvite: true, serviceHost: fakeB,
242
- });
243
- const founderCfg = readHostConfig(join(tmp2, 'host.config.json'));
244
- expect(founderCfg.ownCadre).toEqual({ enabled: true });
245
- } finally {
246
- rmSync(tmp2, { recursive: true, force: true });
247
- }
248
- });
249
-
250
- it('re-running install preserves the existing identity', async () => {
251
- const fake = new FakeServiceHost();
252
- const installer = new Installer({ platform: 'linux' });
253
- await installer.install({
254
- nonInteractive: true, dataDir: tmp, openBrowser: false, noInvite: true, serviceHost: fake,
255
- });
256
- const idBefore = (await import('node:fs')).readFileSync(join(tmp, 'identity.key'));
257
- await installer.install({
258
- nonInteractive: true, dataDir: tmp, openBrowser: false, noInvite: true, serviceHost: fake,
259
- });
260
- const idAfter = (await import('node:fs')).readFileSync(join(tmp, 'identity.key'));
261
- expect(Buffer.from(idBefore).equals(Buffer.from(idAfter))).toBe(true);
262
- });
263
182
  });
@@ -1,64 +1,56 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { runWizardWith, type WizardDefaults } from '../wizard.js';
4
-
5
- const defaults: WizardDefaults = {
6
- dataDir: '/data/cadre-host',
7
- uiPort: 8765,
8
- libp2pPort: 4001,
9
- upnpEnabled: true,
10
- ownCadre: false,
11
- };
12
-
13
- function scripted(answers: ReadonlyArray<string>): (label: string, fallback: string) => Promise<string> {
14
- let i = 0;
15
- return async (_label, _fallback) => {
16
- if (i >= answers.length) throw new Error(`wizard asked more questions than scripted (i=${i})`);
17
- return answers[i++]!;
18
- };
19
- }
20
-
21
- describe('runWizardWith', () => {
22
- it('returns the defaults when every answer is blank', async () => {
23
- const out = await runWizardWith(defaults, scripted(['', '', '', '', '', '']));
24
- expect(out).toEqual({
25
- dataDir: '/data/cadre-host',
26
- uiPort: 8765,
27
- libp2pPort: 4001,
28
- upnpEnabled: true,
29
- configureDdns: false,
30
- ownCadre: false,
31
- });
32
- });
33
-
34
- it('respects overrides', async () => {
35
- const out = await runWizardWith(defaults, scripted([
36
- '/srv/host',
37
- '9000',
38
- '5000',
39
- 'n',
40
- 'y',
41
- 'y',
42
- ]));
43
- expect(out).toEqual({
44
- dataDir: '/srv/host',
45
- uiPort: 9000,
46
- libp2pPort: 5000,
47
- upnpEnabled: false,
48
- configureDdns: true,
49
- ownCadre: true,
50
- });
51
- });
52
-
53
- it('rejects invalid port input', async () => {
54
- await expect(
55
- runWizardWith(defaults, scripted(['', 'banana', '', '', ''])),
56
- ).rejects.toThrow(/Invalid port/);
57
- });
58
-
59
- it('rejects unrecognized yes/no answers', async () => {
60
- await expect(
61
- runWizardWith(defaults, scripted(['', '', '', 'maybe', ''])),
62
- ).rejects.toThrow(/Invalid yes\/no/);
63
- });
64
- });
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { runWizardWith, type WizardDefaults } from '../wizard.js';
4
+
5
+ const defaults: WizardDefaults = {
6
+ dataDir: '/data/cadre-host',
7
+ uiPort: 8765,
8
+ upnpEnabled: true,
9
+ };
10
+
11
+ function scripted(answers: ReadonlyArray<string>): (label: string, fallback: string) => Promise<string> {
12
+ let i = 0;
13
+ return async (_label, _fallback) => {
14
+ if (i >= answers.length) throw new Error(`wizard asked more questions than scripted (i=${i})`);
15
+ return answers[i++]!;
16
+ };
17
+ }
18
+
19
+ describe('runWizardWith', () => {
20
+ it('returns the defaults when every answer is blank', async () => {
21
+ const out = await runWizardWith(defaults, scripted(['', '', '', '']));
22
+ expect(out).toEqual({
23
+ dataDir: '/data/cadre-host',
24
+ uiPort: 8765,
25
+ upnpEnabled: true,
26
+ configureDdns: false,
27
+ });
28
+ });
29
+
30
+ it('respects overrides', async () => {
31
+ const out = await runWizardWith(defaults, scripted([
32
+ '/srv/host',
33
+ '9000',
34
+ 'n',
35
+ 'y',
36
+ ]));
37
+ expect(out).toEqual({
38
+ dataDir: '/srv/host',
39
+ uiPort: 9000,
40
+ upnpEnabled: false,
41
+ configureDdns: true,
42
+ });
43
+ });
44
+
45
+ it('rejects invalid port input', async () => {
46
+ await expect(
47
+ runWizardWith(defaults, scripted(['', 'banana', '', ''])),
48
+ ).rejects.toThrow(/Invalid port/);
49
+ });
50
+
51
+ it('rejects unrecognized yes/no answers', async () => {
52
+ await expect(
53
+ runWizardWith(defaults, scripted(['', '', 'maybe', ''])),
54
+ ).rejects.toThrow(/Invalid yes\/no/);
55
+ });
56
+ });