@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,63 +1,75 @@
1
- /**
2
- * NAT/DDNS HTTP routes — thin adapters around `NatHandlers`
3
- * (`createNatHandlers(service)`). Side-effects (settings change, reachability
4
- * re-test, DDNS reconfiguration) emit a `connectivity-changed` bus event.
5
- *
6
- * Mount path: `/nat/*` — matches the existing CLI (cadre-host nat ...).
7
- */
8
-
9
- import type { FastifyInstance } from 'fastify';
10
-
11
- import type { NatHandlers, NatSettingsFile, NatStatusSnapshot } from '../../nat/types.js';
12
- import type { EventBus } from '../events/bus.js';
13
-
14
- export interface NatRoutesOptions {
15
- handlers: NatHandlers;
16
- events: EventBus;
17
- }
18
-
19
- export function registerNatRoutes(app: FastifyInstance, opts: NatRoutesOptions): void {
20
- const { handlers, events } = opts;
21
-
22
- app.get('/nat/status', async () => handlers.getStatus());
23
-
24
- app.post('/nat/test', async () => {
25
- const snap = await handlers.testReachability();
26
- publishConnectivity(events, snap);
27
- return snap;
28
- });
29
-
30
- app.get('/nat/providers', async () => handlers.listDdnsProviders());
31
-
32
- app.put('/nat/ddns', async (request) => {
33
- const body = (request.body ?? {}) as {
34
- providerId?: string;
35
- hostname?: string;
36
- config?: Record<string, string>;
37
- externallyManaged?: boolean;
38
- };
39
- const snap = await handlers.putDdns({
40
- providerId: body.providerId ?? '',
41
- hostname: body.hostname ?? '',
42
- config: body.config ?? {},
43
- externallyManaged: body.externallyManaged ?? false,
44
- });
45
- publishConnectivity(events, snap);
46
- return snap;
47
- });
48
-
49
- app.put('/nat/settings', async (request) => {
50
- const body = (request.body ?? {}) as Partial<Omit<NatSettingsFile, 'version'>>;
51
- const snap = await handlers.putSettings(body);
52
- publishConnectivity(events, snap);
53
- return snap;
54
- });
55
- }
56
-
57
- export function publishConnectivity(events: EventBus, snap: NatStatusSnapshot): void {
58
- events.publish({
59
- type: 'connectivity-changed',
60
- portMode: snap.portMode,
61
- directReachability: snap.directReachability,
62
- });
63
- }
1
+ /**
2
+ * NAT/DDNS HTTP routes — thin adapters around `NatHandlers`
3
+ * (`createNatHandlers(service)`). Side-effects (settings change, reachability
4
+ * re-test, DDNS reconfiguration, a manual forward) emit a `connectivity-changed`
5
+ * bus event. The service's own change listener covers the changes that happen
6
+ * on their own (a mapping completing after a spawn, an IP change) — see
7
+ * `server/index.ts`.
8
+ *
9
+ * Mount path: `/nat/*` — matches the existing CLI (cadre-host nat ...).
10
+ */
11
+
12
+ import type { FastifyInstance } from 'fastify';
13
+
14
+ import type { ManualForwardPatch, NatHandlers, NatSettingsFile, NatStatusSnapshot } from '../../nat/types.js';
15
+ import type { EventBus } from '../events/bus.js';
16
+
17
+ export interface NatRoutesOptions {
18
+ handlers: NatHandlers;
19
+ events: EventBus;
20
+ }
21
+
22
+ export function registerNatRoutes(app: FastifyInstance, opts: NatRoutesOptions): void {
23
+ const { handlers, events } = opts;
24
+
25
+ app.get('/nat/status', async () => handlers.getStatus());
26
+
27
+ app.post('/nat/test', async () => {
28
+ const snap = await handlers.testReachability();
29
+ publishConnectivity(events, snap);
30
+ return snap;
31
+ });
32
+
33
+ app.get('/nat/providers', async () => handlers.listDdnsProviders());
34
+
35
+ app.put('/nat/ddns', async (request) => {
36
+ const body = (request.body ?? {}) as {
37
+ providerId?: string;
38
+ hostname?: string;
39
+ config?: Record<string, string>;
40
+ externallyManaged?: boolean;
41
+ };
42
+ const snap = await handlers.putDdns({
43
+ providerId: body.providerId ?? '',
44
+ hostname: body.hostname ?? '',
45
+ config: body.config ?? {},
46
+ externallyManaged: body.externallyManaged ?? false,
47
+ });
48
+ publishConnectivity(events, snap);
49
+ return snap;
50
+ });
51
+
52
+ app.put('/nat/settings', async (request) => {
53
+ const body = (request.body ?? {}) as Partial<Omit<NatSettingsFile, 'version' | 'forwards'>>;
54
+ const snap = await handlers.putSettings(body);
55
+ publishConnectivity(events, snap);
56
+ return snap;
57
+ });
58
+
59
+ // The ports the user forwarded on their router for one node. `null` clears a
60
+ // port; both cleared removes the entry. Unknown node → 404 unknown_node; a
61
+ // body that is not an object, or a port outside 1–65535 → 400 invalid_config
62
+ // (all from the service, which reads only `tcp` and `ws`).
63
+ app.put<{ Params: { nodeId: string } }>('/nat/nodes/:nodeId/forward', async (request) => {
64
+ const snap = await handlers.putForward(request.params.nodeId, (request.body ?? {}) as ManualForwardPatch);
65
+ publishConnectivity(events, snap);
66
+ return snap;
67
+ });
68
+ }
69
+
70
+ export function publishConnectivity(events: EventBus, snap: NatStatusSnapshot): void {
71
+ events.publish({
72
+ type: 'connectivity-changed',
73
+ directReachability: snap.directReachability,
74
+ });
75
+ }
@@ -1,188 +1,96 @@
1
- /**
2
- * /api/nodes — list managed cadre nodes, look up details, and control
3
- * lifecycle.
4
- *
5
- * Bus events for lifecycle transitions are emitted by the orchestrator's
6
- * `onStateChange` (forwarded by `createLocalUiServer.start`) — the route
7
- * handlers don't re-publish.
8
- *
9
- * start/stop/restart are real for the admin's owner node only (start/restart
10
- * re-spawn it from the persisted `OwnerSpawnConfig`). Every other node this
11
- * route can see is a donated node, whose lifecycle belongs to the donation
12
- * surface — this route no longer spawns generic nodes, and a stop here would
13
- * be undone at once by the donation supervisor's respawn. So all three verbs
14
- * answer a known non-owner node with 501, pointing at the donation teardown.
15
- *
16
- * Owner start/restart are founder-only: a donor-only host that once ran as a
17
- * founder still holds the owner handle and saved config (so re-enabling
18
- * `ownCadre` can resume it), but spawning it here would bring the owner node
19
- * back without its trust circle, NAT or strand services — so those answer 409.
20
- */
21
-
22
- import { existsSync, openSync, readSync, closeSync, statSync } from 'node:fs';
23
-
24
- import type { FastifyInstance, FastifyReply } from 'fastify';
25
-
26
- import type { HostProcessOrchestrator } from '../../orchestrator/index.js';
27
- import { defaultLogPath } from '../../orchestrator/log-rotator.js';
28
-
29
- import type { HostRole } from './status.js';
30
-
31
- const DEFAULT_LOG_LINES = 200;
32
- const MAX_LOG_LINES = 2000;
33
-
34
- export interface NodesRoutesOptions {
35
- orchestrator: HostProcessOrchestrator;
36
- role: HostRole;
37
- }
38
-
39
- export function registerNodesRoutes(app: FastifyInstance, opts: NodesRoutesOptions): void {
40
- const { orchestrator, role } = opts;
41
-
42
- app.get('/api/nodes', async () => {
43
- return { ok: true, data: { nodes: orchestrator.listNodes() } };
44
- });
45
-
46
- app.get<{ Params: { id: string } }>('/api/nodes/:id', async (request, reply) => {
47
- const node = orchestrator.getNode(request.params.id);
48
- if (!node) return notFound(reply, request.params.id);
49
- let stats = null;
50
- try {
51
- stats = await orchestrator.getStats(node.dockerId);
52
- } catch {
53
- // Stats unavailable (dead process etc.) — surface null.
54
- }
55
- return { ok: true, data: { node, stats } };
56
- });
57
-
58
- app.get<{ Params: { id: string }; Querystring: { lines?: string } }>(
59
- '/api/nodes/:id/logs',
60
- async (request, reply) => {
61
- const node = orchestrator.getNode(request.params.id);
62
- if (!node) return notFound(reply, request.params.id);
63
- const requested = Number(request.query.lines ?? DEFAULT_LOG_LINES);
64
- const tail = Number.isFinite(requested)
65
- ? Math.max(1, Math.min(MAX_LOG_LINES, Math.floor(requested)))
66
- : DEFAULT_LOG_LINES;
67
- const lines = tailLogFile(defaultLogPath(node.workdir), tail);
68
- return { ok: true, data: { lines } };
69
- },
70
- );
71
-
72
- // start / stop / restart: real for the admin's owner node (started from the
73
- // persisted OwnerSpawnConfig). Donated-node lifecycle belongs to the donation
74
- // surface — those ids return a clear not_implemented that points there;
75
- // unknown ids 404.
76
- app.post<{ Params: { id: string } }>('/api/nodes/:id/stop', async (request, reply) => {
77
- const { id } = request.params;
78
- if (!orchestrator.isOwnerNode(id)) {
79
- return ownerOnlyFallback(reply, orchestrator, id, 'stop');
80
- }
81
- const dockerId = orchestrator.resolveDockerId(id);
82
- if (!dockerId) return notFound(reply, id);
83
- await orchestrator.stopContainer(dockerId);
84
- // The orchestrator emits its own onStateChange — createLocalUiServer
85
- // forwards that to the bus. Don't double-publish here.
86
- return { ok: true };
87
- });
88
-
89
- app.post<{ Params: { id: string } }>('/api/nodes/:id/start', async (request, reply) => {
90
- const { id } = request.params;
91
- if (orchestrator.isOwnerNode(id)) {
92
- if (role === 'donor') return ownCadreDisabled(reply, 'start');
93
- if (!orchestrator.hasOwnerConfig()) {
94
- return notImplemented(reply, `start ${id}: owner node has no saved spawn config.`);
95
- }
96
- const node = await orchestrator.ensureOwnerNode();
97
- return { ok: true, data: { node } };
98
- }
99
- return ownerOnlyFallback(reply, orchestrator, id, 'start');
100
- });
101
-
102
- app.post<{ Params: { id: string } }>('/api/nodes/:id/restart', async (request, reply) => {
103
- const { id } = request.params;
104
- if (orchestrator.isOwnerNode(id)) {
105
- if (role === 'donor') return ownCadreDisabled(reply, 'restart');
106
- if (!orchestrator.hasOwnerConfig()) {
107
- return notImplemented(reply, `restart ${id}: owner node has no saved spawn config.`);
108
- }
109
- const node = await orchestrator.restartOwnerNode();
110
- return { ok: true, data: { node } };
111
- }
112
- return ownerOnlyFallback(reply, orchestrator, id, 'restart');
113
- });
114
- }
115
-
116
- /**
117
- * 404 for unknown ids; 501 not_implemented for known non-owner nodes — all of
118
- * them donated. A donated node's containerId is its donation id, so the
119
- * message names `node.id` rather than the route param, which may be the
120
- * opaque dockerId — only the donation id is what the terminate command takes.
121
- */
122
- function ownerOnlyFallback(
123
- reply: FastifyReply,
124
- orchestrator: HostProcessOrchestrator,
125
- id: string,
126
- verb: 'start' | 'stop' | 'restart',
127
- ) {
128
- const node = orchestrator.getNode(id);
129
- if (!node) return notFound(reply, id);
130
- return notImplemented(
131
- reply,
132
- `${verb} ${id}: only the owner node can be started, stopped or restarted here; a donated node's lifecycle is owned by the donation surface — end it with 'cadre-host grant terminate ${node.id}' (DELETE /grants-admin/donations/${node.id}).`,
133
- );
134
- }
135
-
136
- function notFound(reply: FastifyReply, id: string) {
137
- return reply.code(404).send({
138
- ok: false,
139
- error: { code: 'not_found', message: `Unknown node: ${id}` },
140
- });
141
- }
142
-
143
- function notImplemented(reply: FastifyReply, message: string) {
144
- return reply.code(501).send({ ok: false, error: { code: 'not_implemented', message } });
145
- }
146
-
147
- function ownCadreDisabled(reply: FastifyReply, verb: 'start' | 'restart') {
148
- return reply.code(409).send({
149
- ok: false,
150
- error: {
151
- code: 'own_cadre_disabled',
152
- message: `${verb} owner: this host's own cadre is turned off (donor-only mode). Set ownCadre.enabled in host.config.json and restart cadre-host to run it again.`,
153
- },
154
- });
155
- }
156
-
157
- /**
158
- * Read the last `n` lines from a log file by walking the tail in chunks.
159
- * Returns [] for a missing file.
160
- */
161
- export function tailLogFile(path: string, n: number): string[] {
162
- if (!existsSync(path)) return [];
163
- const fd = openSync(path, 'r');
164
- try {
165
- const size = statSync(path).size;
166
- const chunkSize = 8192;
167
- let pos = size;
168
- const buffers: Buffer[] = [];
169
- let newlineCount = 0;
170
- while (pos > 0 && newlineCount <= n) {
171
- const readLen = Math.min(chunkSize, pos);
172
- pos -= readLen;
173
- const buf = Buffer.alloc(readLen);
174
- readSync(fd, buf, 0, readLen, pos);
175
- buffers.unshift(buf);
176
- for (let i = 0; i < buf.length; i++) {
177
- if (buf[i] === 0x0a) newlineCount++;
178
- }
179
- }
180
- const joined = Buffer.concat(buffers).toString('utf8');
181
- const lines = joined.split('\n');
182
- const trimmed = lines[lines.length - 1] === '' ? lines.slice(0, -1) : lines;
183
- return trimmed.slice(-n);
184
- } finally {
185
- closeSync(fd);
186
- }
187
- }
188
-
1
+ /**
2
+ * /api/nodes — list managed cadre nodes (orchestrator handles), look up one
3
+ * node's details, and tail its log. Read-only: every node's lifecycle belongs to
4
+ * `/api/hosted-nodes`, whose supervisor would undo at once a stop issued
5
+ * anywhere else. A handle's `partyId` is the placeholder an unclaimed node was
6
+ * started with until its first respawn after the claim; the hosted-node record
7
+ * carries the real party.
8
+ */
9
+
10
+ import { existsSync, openSync, readSync, closeSync, statSync } from 'node:fs';
11
+
12
+ import type { FastifyInstance, FastifyReply } from 'fastify';
13
+
14
+ import type { HostProcessOrchestrator } from '../../orchestrator/index.js';
15
+ import { defaultLogPath } from '../../orchestrator/log-rotator.js';
16
+
17
+ const DEFAULT_LOG_LINES = 200;
18
+ const MAX_LOG_LINES = 2000;
19
+
20
+ export interface NodesRoutesOptions {
21
+ orchestrator: HostProcessOrchestrator;
22
+ }
23
+
24
+ export function registerNodesRoutes(app: FastifyInstance, opts: NodesRoutesOptions): void {
25
+ const { orchestrator } = opts;
26
+
27
+ app.get('/api/nodes', async () => {
28
+ return { ok: true, data: { nodes: orchestrator.listNodes() } };
29
+ });
30
+
31
+ app.get<{ Params: { id: string } }>('/api/nodes/:id', async (request, reply) => {
32
+ const node = orchestrator.getNode(request.params.id);
33
+ if (!node) return notFound(reply, request.params.id);
34
+ let stats = null;
35
+ try {
36
+ stats = await orchestrator.getStats(node.dockerId);
37
+ } catch {
38
+ // Stats unavailable (dead process etc.) — surface null.
39
+ }
40
+ return { ok: true, data: { node, stats } };
41
+ });
42
+
43
+ app.get<{ Params: { id: string }; Querystring: { lines?: string } }>(
44
+ '/api/nodes/:id/logs',
45
+ async (request, reply) => {
46
+ const node = orchestrator.getNode(request.params.id);
47
+ if (!node) return notFound(reply, request.params.id);
48
+ const requested = Number(request.query.lines ?? DEFAULT_LOG_LINES);
49
+ const tail = Number.isFinite(requested)
50
+ ? Math.max(1, Math.min(MAX_LOG_LINES, Math.floor(requested)))
51
+ : DEFAULT_LOG_LINES;
52
+ const lines = tailLogFile(defaultLogPath(node.workdir), tail);
53
+ return { ok: true, data: { lines } };
54
+ },
55
+ );
56
+ }
57
+
58
+ function notFound(reply: FastifyReply, id: string) {
59
+ return reply.code(404).send({
60
+ ok: false,
61
+ error: { code: 'not_found', message: `Unknown node: ${id}` },
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Read the last `n` lines from a log file by walking the tail in chunks.
67
+ * Returns [] for a missing file.
68
+ */
69
+ export function tailLogFile(path: string, n: number): string[] {
70
+ if (!existsSync(path)) return [];
71
+ const fd = openSync(path, 'r');
72
+ try {
73
+ const size = statSync(path).size;
74
+ const chunkSize = 8192;
75
+ let pos = size;
76
+ const buffers: Buffer[] = [];
77
+ let newlineCount = 0;
78
+ while (pos > 0 && newlineCount <= n) {
79
+ const readLen = Math.min(chunkSize, pos);
80
+ pos -= readLen;
81
+ const buf = Buffer.alloc(readLen);
82
+ readSync(fd, buf, 0, readLen, pos);
83
+ buffers.unshift(buf);
84
+ for (let i = 0; i < buf.length; i++) {
85
+ if (buf[i] === 0x0a) newlineCount++;
86
+ }
87
+ }
88
+ const joined = Buffer.concat(buffers).toString('utf8');
89
+ const lines = joined.split('\n');
90
+ const trimmed = lines[lines.length - 1] === '' ? lines.slice(0, -1) : lines;
91
+ return trimmed.slice(-n);
92
+ } finally {
93
+ closeSync(fd);
94
+ }
95
+ }
96
+