@serfab/cadre-host 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/README.md +347 -423
  2. package/dist/bin/host.d.ts +6 -3
  3. package/dist/bin/host.js +332 -524
  4. package/dist/bin/host.js.map +1 -1
  5. package/dist/bin/join-output.d.ts +56 -0
  6. package/dist/bin/join-output.js +92 -0
  7. package/dist/bin/join-output.js.map +1 -0
  8. package/dist/bin/nat-output.d.ts +54 -0
  9. package/dist/bin/nat-output.js +138 -0
  10. package/dist/bin/nat-output.js.map +1 -0
  11. package/dist/bin/ui-access.d.ts +22 -0
  12. package/dist/bin/ui-access.js +44 -0
  13. package/dist/bin/ui-access.js.map +1 -0
  14. package/dist/hosted/claim-details.d.ts +66 -0
  15. package/dist/hosted/claim-details.js +60 -0
  16. package/dist/hosted/claim-details.js.map +1 -0
  17. package/dist/hosted/hosted-node-service.d.ts +325 -0
  18. package/dist/hosted/hosted-node-service.js +678 -0
  19. package/dist/hosted/hosted-node-service.js.map +1 -0
  20. package/dist/hosted/hosted-node-store.d.ts +40 -0
  21. package/dist/hosted/hosted-node-store.js +126 -0
  22. package/dist/hosted/hosted-node-store.js.map +1 -0
  23. package/dist/{donation/donation-supervisor.d.ts → hosted/hosted-node-supervisor.d.ts} +70 -53
  24. package/dist/hosted/hosted-node-supervisor.js +396 -0
  25. package/dist/hosted/hosted-node-supervisor.js.map +1 -0
  26. package/dist/hosted/hosted-node-watcher.d.ts +95 -0
  27. package/dist/hosted/hosted-node-watcher.js +260 -0
  28. package/dist/hosted/hosted-node-watcher.js.map +1 -0
  29. package/dist/hosted/index.d.ts +16 -0
  30. package/dist/hosted/index.js +13 -0
  31. package/dist/hosted/index.js.map +1 -0
  32. package/dist/hosted/node-status.d.ts +51 -0
  33. package/dist/hosted/node-status.js +77 -0
  34. package/dist/hosted/node-status.js.map +1 -0
  35. package/dist/hosted/types.d.ts +141 -0
  36. package/dist/hosted/types.js +22 -0
  37. package/dist/hosted/types.js.map +1 -0
  38. package/dist/index.d.ts +12 -24
  39. package/dist/index.js +9 -19
  40. package/dist/index.js.map +1 -1
  41. package/dist/installer/config.d.ts +27 -34
  42. package/dist/installer/config.js +46 -15
  43. package/dist/installer/config.js.map +1 -1
  44. package/dist/installer/index.d.ts +9 -19
  45. package/dist/installer/index.js +21 -85
  46. package/dist/installer/index.js.map +1 -1
  47. package/dist/installer/paths.d.ts +0 -2
  48. package/dist/installer/paths.js +0 -4
  49. package/dist/installer/paths.js.map +1 -1
  50. package/dist/installer/wizard.d.ts +12 -9
  51. package/dist/installer/wizard.js +15 -14
  52. package/dist/installer/wizard.js.map +1 -1
  53. package/dist/nat/address-resolver.d.ts +18 -17
  54. package/dist/nat/address-resolver.js +79 -25
  55. package/dist/nat/address-resolver.js.map +1 -1
  56. package/dist/nat/address-watch.d.ts +37 -0
  57. package/dist/nat/address-watch.js +84 -0
  58. package/dist/nat/address-watch.js.map +1 -0
  59. package/dist/nat/ddns/duckdns.d.ts +2 -1
  60. package/dist/nat/ddns/duckdns.js +5 -1
  61. package/dist/nat/ddns/duckdns.js.map +1 -1
  62. package/dist/nat/ddns/index.d.ts +6 -0
  63. package/dist/nat/ddns/index.js.map +1 -1
  64. package/dist/nat/index.d.ts +13 -9
  65. package/dist/nat/index.js +7 -5
  66. package/dist/nat/index.js.map +1 -1
  67. package/dist/nat/nat-service.d.ts +220 -77
  68. package/dist/nat/nat-service.js +653 -239
  69. package/dist/nat/nat-service.js.map +1 -1
  70. package/dist/nat/nat-store.d.ts +12 -5
  71. package/dist/nat/nat-store.js +124 -29
  72. package/dist/nat/nat-store.js.map +1 -1
  73. package/dist/nat/network-mode.d.ts +19 -0
  74. package/dist/nat/network-mode.js +32 -0
  75. package/dist/nat/network-mode.js.map +1 -0
  76. package/dist/nat/port-mapper.d.ts +83 -79
  77. package/dist/nat/port-mapper.js +148 -189
  78. package/dist/nat/port-mapper.js.map +1 -1
  79. package/dist/nat/reachability.d.ts +32 -15
  80. package/dist/nat/reachability.js +110 -22
  81. package/dist/nat/reachability.js.map +1 -1
  82. package/dist/nat/types.d.ts +98 -27
  83. package/dist/nat/types.js +8 -5
  84. package/dist/nat/types.js.map +1 -1
  85. package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
  86. package/dist/orchestrator/host-process-orchestrator.js +84 -233
  87. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  88. package/dist/{installer/identity.d.ts → orchestrator/identity-file.d.ts} +5 -5
  89. package/dist/{installer/identity.js → orchestrator/identity-file.js} +6 -6
  90. package/dist/orchestrator/identity-file.js.map +1 -0
  91. package/dist/orchestrator/index.d.ts +2 -3
  92. package/dist/orchestrator/index.js +1 -1
  93. package/dist/orchestrator/index.js.map +1 -1
  94. package/dist/orchestrator/node-identity.d.ts +6 -6
  95. package/dist/orchestrator/node-identity.js +7 -7
  96. package/dist/orchestrator/node-identity.js.map +1 -1
  97. package/dist/orchestrator/port-allocator.d.ts +5 -6
  98. package/dist/orchestrator/port-allocator.js +9 -11
  99. package/dist/orchestrator/port-allocator.js.map +1 -1
  100. package/dist/orchestrator/state-store.d.ts +3 -5
  101. package/dist/orchestrator/state-store.js +2 -10
  102. package/dist/orchestrator/state-store.js.map +1 -1
  103. package/dist/orchestrator/types.d.ts +33 -22
  104. package/dist/orchestrator/types.js.map +1 -1
  105. package/dist/package-version.d.ts +1 -0
  106. package/dist/package-version.js +4 -0
  107. package/dist/package-version.js.map +1 -0
  108. package/dist/server/error-handler.d.ts +5 -25
  109. package/dist/server/error-handler.js +11 -74
  110. package/dist/server/error-handler.js.map +1 -1
  111. package/dist/server/events/types.d.ts +7 -11
  112. package/dist/server/events/types.js +2 -1
  113. package/dist/server/events/types.js.map +1 -1
  114. package/dist/server/index.d.ts +11 -37
  115. package/dist/server/index.js +30 -48
  116. package/dist/server/index.js.map +1 -1
  117. package/dist/server/routes/hosted-nodes.d.ts +28 -0
  118. package/dist/server/routes/hosted-nodes.js +68 -0
  119. package/dist/server/routes/hosted-nodes.js.map +1 -0
  120. package/dist/server/routes/nat.d.ts +4 -1
  121. package/dist/server/routes/nat.js +13 -2
  122. package/dist/server/routes/nat.js.map +1 -1
  123. package/dist/server/routes/nodes.d.ts +6 -20
  124. package/dist/server/routes/nodes.js +7 -86
  125. package/dist/server/routes/nodes.js.map +1 -1
  126. package/dist/server/routes/settings.d.ts +3 -7
  127. package/dist/server/routes/settings.js +18 -8
  128. package/dist/server/routes/settings.js.map +1 -1
  129. package/dist/server/routes/status.d.ts +2 -25
  130. package/dist/server/routes/status.js +3 -43
  131. package/dist/server/routes/status.js.map +1 -1
  132. package/dist/server/server.js +8 -8
  133. package/dist/server/settings-store.d.ts +2 -2
  134. package/dist/server/settings-store.js +2 -2
  135. package/dist/server/static.js +1 -2
  136. package/dist/server/static.js.map +1 -1
  137. package/dist/ui/assets/index-BHd8CWZA.css +1 -0
  138. package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
  139. package/dist/ui/assets/index-Ccr2GjPT.js.map +1 -0
  140. package/dist/ui/index.html +2 -2
  141. package/dist/update/index.d.ts +2 -2
  142. package/dist/update/index.js +2 -2
  143. package/dist/update/store.d.ts +1 -1
  144. package/dist/update/store.js +1 -1
  145. package/dist/update/types.d.ts +1 -1
  146. package/dist/update/types.js +1 -1
  147. package/package.json +5 -7
  148. package/src/__tests__/cli-nat.smoke.test.ts +219 -212
  149. package/src/__tests__/cli.smoke.test.ts +2 -2
  150. package/src/__tests__/global-setup.ts +2 -3
  151. package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
  152. package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
  153. package/src/__tests__/orchestrator-handle-contract.ts +134 -134
  154. package/src/__tests__/orchestrator-node-identity.test.ts +177 -203
  155. package/src/__tests__/orchestrator-ports.test.ts +124 -143
  156. package/src/__tests__/orchestrator-push.test.ts +138 -146
  157. package/src/__tests__/orchestrator.test.ts +1017 -1063
  158. package/src/__tests__/ui-access.test.ts +30 -0
  159. package/src/bin/host.ts +397 -572
  160. package/src/bin/join-output.ts +120 -0
  161. package/src/bin/nat-output.ts +188 -0
  162. package/src/bin/ui-access.ts +50 -0
  163. package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
  164. package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
  165. package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
  166. package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
  167. package/src/hosted/__tests__/test-invitation.ts +33 -0
  168. package/src/hosted/claim-details.ts +103 -0
  169. package/src/hosted/hosted-node-service.ts +814 -0
  170. package/src/hosted/hosted-node-store.ts +144 -0
  171. package/src/hosted/hosted-node-supervisor.ts +439 -0
  172. package/src/hosted/hosted-node-watcher.ts +288 -0
  173. package/src/hosted/index.ts +53 -0
  174. package/src/hosted/node-status.ts +97 -0
  175. package/src/hosted/types.ts +160 -0
  176. package/src/index.ts +72 -91
  177. package/src/installer/__tests__/config.test.ts +15 -40
  178. package/src/installer/__tests__/installer.smoke.test.ts +184 -263
  179. package/src/installer/__tests__/wizard.test.ts +25 -13
  180. package/src/installer/config.ts +201 -175
  181. package/src/installer/index.ts +252 -327
  182. package/src/installer/paths.ts +104 -109
  183. package/src/installer/wizard.ts +24 -26
  184. package/src/nat/__tests__/address-resolver.test.ts +107 -82
  185. package/src/nat/__tests__/nat-service.test.ts +608 -515
  186. package/src/nat/__tests__/nat-store.test.ts +49 -12
  187. package/src/nat/__tests__/network-mode.test.ts +38 -0
  188. package/src/nat/__tests__/port-mapper.test.ts +30 -191
  189. package/src/nat/__tests__/reachability.test.ts +96 -13
  190. package/src/nat/address-resolver.ts +94 -52
  191. package/src/nat/address-watch.ts +92 -0
  192. package/src/nat/ddns/duckdns.ts +5 -1
  193. package/src/nat/ddns/index.ts +6 -0
  194. package/src/nat/index.ts +33 -13
  195. package/src/nat/nat-service.ts +942 -501
  196. package/src/nat/nat-store.ts +126 -31
  197. package/src/nat/network-mode.ts +36 -0
  198. package/src/nat/port-mapper.ts +241 -329
  199. package/src/nat/reachability.ts +155 -34
  200. package/src/nat/types.ts +110 -37
  201. package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts} +34 -34
  202. package/src/orchestrator/__tests__/node-identity.test.ts +62 -62
  203. package/src/orchestrator/host-process-orchestrator.ts +1089 -1260
  204. package/src/{installer/identity.ts → orchestrator/identity-file.ts} +56 -56
  205. package/src/orchestrator/index.ts +9 -10
  206. package/src/orchestrator/node-identity.ts +41 -41
  207. package/src/orchestrator/port-allocator.ts +71 -73
  208. package/src/orchestrator/state-store.ts +76 -90
  209. package/src/orchestrator/types.ts +34 -23
  210. package/src/package-version.ts +4 -0
  211. package/src/server/__tests__/bus.test.ts +38 -38
  212. package/src/server/__tests__/error-handler.test.ts +81 -118
  213. package/src/server/__tests__/fakes.ts +15 -18
  214. package/src/server/__tests__/hosted-nodes-route.test.ts +161 -0
  215. package/src/server/__tests__/nodes-route.test.ts +114 -283
  216. package/src/server/__tests__/publishers.test.ts +193 -180
  217. package/src/server/__tests__/server.smoke.test.ts +134 -223
  218. package/src/server/__tests__/settings-route.test.ts +210 -195
  219. package/src/server/__tests__/sse-route.test.ts +119 -119
  220. package/src/server/__tests__/status-route.test.ts +82 -121
  221. package/src/server/error-handler.ts +112 -178
  222. package/src/server/events/types.ts +20 -24
  223. package/src/server/index.ts +223 -267
  224. package/src/server/routes/hosted-nodes.ts +83 -0
  225. package/src/server/routes/nat.ts +75 -63
  226. package/src/server/routes/nodes.ts +96 -188
  227. package/src/server/routes/settings.ts +20 -15
  228. package/src/server/routes/status.ts +5 -67
  229. package/src/server/server.ts +8 -8
  230. package/src/server/settings-store.ts +2 -2
  231. package/src/server/static.ts +1 -2
  232. package/src/update/index.ts +2 -2
  233. package/src/update/store.ts +1 -1
  234. package/src/update/types.ts +1 -1
  235. package/dist/auth/duration.d.ts +0 -1
  236. package/dist/auth/duration.js +0 -32
  237. package/dist/auth/duration.js.map +0 -1
  238. package/dist/auth/index.d.ts +0 -11
  239. package/dist/auth/index.js +0 -9
  240. package/dist/auth/index.js.map +0 -1
  241. package/dist/auth/self-label.d.ts +0 -36
  242. package/dist/auth/self-label.js +0 -57
  243. package/dist/auth/self-label.js.map +0 -1
  244. package/dist/auth/trust-circle-store.d.ts +0 -26
  245. package/dist/auth/trust-circle-store.js +0 -129
  246. package/dist/auth/trust-circle-store.js.map +0 -1
  247. package/dist/auth/trust-circle.d.ts +0 -156
  248. package/dist/auth/trust-circle.js +0 -307
  249. package/dist/auth/trust-circle.js.map +0 -1
  250. package/dist/auth/types.d.ts +0 -84
  251. package/dist/auth/types.js +0 -24
  252. package/dist/auth/types.js.map +0 -1
  253. package/dist/donation/donation-service.d.ts +0 -389
  254. package/dist/donation/donation-service.js +0 -828
  255. package/dist/donation/donation-service.js.map +0 -1
  256. package/dist/donation/donation-store.d.ts +0 -51
  257. package/dist/donation/donation-store.js +0 -152
  258. package/dist/donation/donation-store.js.map +0 -1
  259. package/dist/donation/donation-supervisor.js +0 -364
  260. package/dist/donation/donation-supervisor.js.map +0 -1
  261. package/dist/donation/grant-service.d.ts +0 -80
  262. package/dist/donation/grant-service.js +0 -210
  263. package/dist/donation/grant-service.js.map +0 -1
  264. package/dist/donation/grant-store.d.ts +0 -38
  265. package/dist/donation/grant-store.js +0 -135
  266. package/dist/donation/grant-store.js.map +0 -1
  267. package/dist/donation/index.d.ts +0 -20
  268. package/dist/donation/index.js +0 -17
  269. package/dist/donation/index.js.map +0 -1
  270. package/dist/donation/types.d.ts +0 -224
  271. package/dist/donation/types.js +0 -40
  272. package/dist/donation/types.js.map +0 -1
  273. package/dist/installer/identity.js.map +0 -1
  274. package/dist/owner/index.d.ts +0 -11
  275. package/dist/owner/index.js +0 -11
  276. package/dist/owner/index.js.map +0 -1
  277. package/dist/owner/owner-node-client.d.ts +0 -110
  278. package/dist/owner/owner-node-client.js +0 -184
  279. package/dist/owner/owner-node-client.js.map +0 -1
  280. package/dist/server/routes/bootstrap-node-validation.d.ts +0 -115
  281. package/dist/server/routes/bootstrap-node-validation.js +0 -197
  282. package/dist/server/routes/bootstrap-node-validation.js.map +0 -1
  283. package/dist/server/routes/grants-admin.d.ts +0 -29
  284. package/dist/server/routes/grants-admin.js +0 -58
  285. package/dist/server/routes/grants-admin.js.map +0 -1
  286. package/dist/server/routes/grants.d.ts +0 -28
  287. package/dist/server/routes/grants.js +0 -165
  288. package/dist/server/routes/grants.js.map +0 -1
  289. package/dist/server/routes/provision-request-validation.d.ts +0 -45
  290. package/dist/server/routes/provision-request-validation.js +0 -97
  291. package/dist/server/routes/provision-request-validation.js.map +0 -1
  292. package/dist/server/routes/strands.d.ts +0 -20
  293. package/dist/server/routes/strands.js +0 -38
  294. package/dist/server/routes/strands.js.map +0 -1
  295. package/dist/server/routes/trust-circle.d.ts +0 -17
  296. package/dist/server/routes/trust-circle.js +0 -35
  297. package/dist/server/routes/trust-circle.js.map +0 -1
  298. package/dist/strands/index.d.ts +0 -7
  299. package/dist/strands/index.js +0 -6
  300. package/dist/strands/index.js.map +0 -1
  301. package/dist/strands/strand-service.d.ts +0 -50
  302. package/dist/strands/strand-service.js +0 -103
  303. package/dist/strands/strand-service.js.map +0 -1
  304. package/dist/strands/types.d.ts +0 -93
  305. package/dist/strands/types.js +0 -23
  306. package/dist/strands/types.js.map +0 -1
  307. package/dist/ui/assets/index-C6nELNfC.js +0 -23
  308. package/dist/ui/assets/index-C6nELNfC.js.map +0 -1
  309. package/dist/ui/assets/index-TtIYSeZO.css +0 -1
  310. package/src/__tests__/cli-invite.smoke.test.ts +0 -192
  311. package/src/__tests__/host-owner.smoke.test.ts +0 -123
  312. package/src/__tests__/orchestrator-owner.test.ts +0 -234
  313. package/src/__tests__/orchestrator-pin-keys.test.ts +0 -152
  314. package/src/auth/__tests__/duration.test.ts +0 -28
  315. package/src/auth/__tests__/self-label.test.ts +0 -85
  316. package/src/auth/__tests__/trust-circle-integration.test.ts +0 -152
  317. package/src/auth/__tests__/trust-circle-store.test.ts +0 -115
  318. package/src/auth/__tests__/trust-circle.test.ts +0 -445
  319. package/src/auth/duration.ts +0 -33
  320. package/src/auth/index.ts +0 -21
  321. package/src/auth/self-label.ts +0 -74
  322. package/src/auth/trust-circle-store.ts +0 -155
  323. package/src/auth/trust-circle.ts +0 -395
  324. package/src/auth/types.ts +0 -99
  325. package/src/donation/__tests__/donation-service.test.ts +0 -1193
  326. package/src/donation/__tests__/donation-supervisor.test.ts +0 -548
  327. package/src/donation/__tests__/grant-service.test.ts +0 -213
  328. package/src/donation/__tests__/grant-store.test.ts +0 -113
  329. package/src/donation/donation-service.ts +0 -1033
  330. package/src/donation/donation-store.ts +0 -176
  331. package/src/donation/donation-supervisor.ts +0 -411
  332. package/src/donation/grant-service.ts +0 -252
  333. package/src/donation/grant-store.ts +0 -152
  334. package/src/donation/index.ts +0 -65
  335. package/src/donation/types.ts +0 -260
  336. package/src/owner/__tests__/owner-node-client.test.ts +0 -194
  337. package/src/owner/index.ts +0 -12
  338. package/src/owner/owner-node-client.ts +0 -259
  339. package/src/server/__tests__/bootstrap-node-validation.test.ts +0 -138
  340. package/src/server/__tests__/grants-admin-route.test.ts +0 -171
  341. package/src/server/__tests__/grants-route.test.ts +0 -394
  342. package/src/server/__tests__/strands-route.test.ts +0 -234
  343. package/src/server/routes/bootstrap-node-validation.ts +0 -206
  344. package/src/server/routes/grants-admin.ts +0 -74
  345. package/src/server/routes/grants.ts +0 -193
  346. package/src/server/routes/provision-request-validation.ts +0 -105
  347. package/src/server/routes/strands.ts +0 -54
  348. package/src/server/routes/trust-circle.ts +0 -49
  349. package/src/strands/__tests__/strand-service.test.ts +0 -165
  350. package/src/strands/index.ts +0 -14
  351. package/src/strands/strand-service.ts +0 -135
  352. package/src/strands/types.ts +0 -102
@@ -1,263 +1,184 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { existsSync, mkdtempSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
-
6
- import { Installer } from '../index.js';
7
- import type { ServiceHost, ServiceHostContext, ServiceHostStatus } from '../service-host/types.js';
8
-
9
- class FakeServiceHost implements ServiceHost {
10
- readonly name = 'cadre-host';
11
- installCalls: ServiceHostContext[] = [];
12
- uninstallCalls: ServiceHostContext[] = [];
13
- installed = false;
14
- running = false;
15
-
16
- async install(ctx: ServiceHostContext): Promise<void> {
17
- this.installCalls.push(ctx);
18
- this.installed = true;
19
- this.running = true;
20
- }
21
- async uninstall(ctx: ServiceHostContext): Promise<void> {
22
- this.uninstallCalls.push(ctx);
23
- this.installed = false;
24
- this.running = false;
25
- }
26
- async restart(_ctx: ServiceHostContext): Promise<void> {
27
- this.running = true;
28
- }
29
- async status(_ctx: ServiceHostContext): Promise<ServiceHostStatus> {
30
- return { installed: this.installed, running: this.running };
31
- }
32
- renderUnit(_ctx: ServiceHostContext): string | null {
33
- return 'rendered-unit';
34
- }
35
- }
36
-
37
- describe('Installer smoke', () => {
38
- let tmp: string;
39
-
40
- beforeEach(() => {
41
- tmp = mkdtempSync(join(tmpdir(), 'cadre-host-installer-smoke-'));
42
- });
43
-
44
- afterEach(() => {
45
- rmSync(tmp, { recursive: true, force: true });
46
- });
47
-
48
- it('install creates config + identity + invokes service-host', async () => {
49
- const fake = new FakeServiceHost();
50
- const installer = new Installer({ platform: 'linux', installerVersion: 'test-1.0.0' });
51
- const result = await installer.install({
52
- nonInteractive: true,
53
- dataDir: tmp,
54
- uiPort: 19999,
55
- libp2pPort: 14001,
56
- openBrowser: false,
57
- noInvite: true,
58
- serviceHost: fake,
59
- });
60
-
61
- expect(result.dataDir).toBe(tmp);
62
- expect(result.uiUrl).toBe('http://127.0.0.1:19999/');
63
- expect(result.serviceName).toBe('cadre-host');
64
- expect(result.configPath).toBe(join(tmp, 'host.config.json'));
65
-
66
- expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
67
- expect(existsSync(join(tmp, 'identity.key'))).toBe(true);
68
- expect(existsSync(join(tmp, 'nat.json'))).toBe(true);
69
- expect(existsSync(join(tmp, 'logs'))).toBe(true);
70
-
71
- expect(fake.installCalls).toHaveLength(1);
72
- expect(fake.installCalls[0]!.dataDir).toBe(tmp);
73
- });
74
-
75
- it('noService writes the data dir without registering a service', async () => {
76
- const fake = new FakeServiceHost();
77
- const installer = new Installer({ platform: 'linux', installerVersion: 'test-1.0.0' });
78
- const result = await installer.install({
79
- nonInteractive: true,
80
- dataDir: tmp,
81
- uiPort: 19995,
82
- libp2pPort: 14005,
83
- noService: true,
84
- serviceHost: fake,
85
- });
86
-
87
- expect(fake.installCalls).toHaveLength(0);
88
- expect(result.serviceName).toBeUndefined();
89
- expect(result.uiUrl).toBe('http://127.0.0.1:19995/');
90
- expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
91
- expect(existsSync(join(tmp, 'identity.key'))).toBe(true);
92
- expect(existsSync(join(tmp, 'nat.json'))).toBe(true);
93
- });
94
-
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
- it('uninstall removes data only when --remove-data is set', async () => {
121
- const fake = new FakeServiceHost();
122
- const installer = new Installer({ platform: 'linux' });
123
- await installer.install({
124
- nonInteractive: true,
125
- dataDir: tmp,
126
- uiPort: 19998,
127
- libp2pPort: 14002,
128
- openBrowser: false,
129
- noInvite: true,
130
- serviceHost: fake,
131
- });
132
- expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
133
-
134
- await installer.uninstall({ yes: true, removeData: false, dataDir: tmp, serviceHost: fake });
135
- expect(fake.uninstallCalls).toHaveLength(1);
136
- expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
137
-
138
- await installer.uninstall({ yes: true, removeData: true, dataDir: tmp, serviceHost: fake });
139
- expect(existsSync(tmp)).toBe(false);
140
- });
141
-
142
- it('rejects --system in v1', async () => {
143
- const fake = new FakeServiceHost();
144
- const installer = new Installer({ platform: 'linux' });
145
- await expect(installer.install({
146
- nonInteractive: true,
147
- dataDir: tmp,
148
- system: true,
149
- serviceHost: fake,
150
- openBrowser: false,
151
- noInvite: true,
152
- })).rejects.toThrow(/system.*not yet supported/);
153
- expect(fake.installCalls).toHaveLength(0);
154
- });
155
-
156
- it('passes CLI overrides to the wizard as suggested defaults', async () => {
157
- // Regression: the interactive wizard previously showed the platform
158
- // default (`~/.local/share/cadre-host`) in the prompt even when the
159
- // caller passed `--data-dir <path>`. Whatever the user typed was then
160
- // silently discarded because the CLI flag won post-hoc. The fix: CLI
161
- // overrides become the wizard's suggested defaults, and the wizard's
162
- // returned answers stand on their own.
163
- const fake = new FakeServiceHost();
164
- const installer = new Installer({ platform: 'linux' });
165
- let seenDefaults: { dataDir: string; uiPort: number; libp2pPort: number; upnpEnabled: boolean; ownCadre: boolean } | undefined;
166
- await installer.install({
167
- nonInteractive: false,
168
- dataDir: tmp,
169
- uiPort: 19997,
170
- libp2pPort: 14003,
171
- noUpnp: true,
172
- openBrowser: false,
173
- noInvite: true,
174
- serviceHost: fake,
175
- wizard: async (defaults) => {
176
- seenDefaults = { ...defaults };
177
- // Simulate the user pressing Enter at every prompt.
178
- return {
179
- dataDir: defaults.dataDir,
180
- uiPort: defaults.uiPort,
181
- libp2pPort: defaults.libp2pPort,
182
- upnpEnabled: defaults.upnpEnabled,
183
- configureDdns: false,
184
- ownCadre: defaults.ownCadre,
185
- };
186
- },
187
- });
188
-
189
- expect(seenDefaults).toEqual({
190
- dataDir: tmp,
191
- uiPort: 19997,
192
- libp2pPort: 14003,
193
- upnpEnabled: false,
194
- ownCadre: false,
195
- });
196
- expect(fake.installCalls).toHaveLength(1);
197
- expect(fake.installCalls[0]!.dataDir).toBe(tmp);
198
- });
199
-
200
- it('honors wizard-typed input when no CLI override is passed', async () => {
201
- // Mirror of the previous test: with no `--data-dir`, the wizard's
202
- // returned value is what the installer uses.
203
- const fake = new FakeServiceHost();
204
- const installer = new Installer({ platform: 'linux' });
205
- await installer.install({
206
- nonInteractive: false,
207
- openBrowser: false,
208
- noInvite: true,
209
- serviceHost: fake,
210
- wizard: async () => ({
211
- dataDir: tmp,
212
- uiPort: 19996,
213
- libp2pPort: 14004,
214
- upnpEnabled: true,
215
- configureDdns: false,
216
- ownCadre: false,
217
- }),
218
- });
219
- expect(fake.installCalls).toHaveLength(1);
220
- expect(fake.installCalls[0]!.dataDir).toBe(tmp);
221
- });
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
- });
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { existsSync, mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ import { Installer } from '../index.js';
7
+ import type { ServiceHost, ServiceHostContext, ServiceHostStatus } from '../service-host/types.js';
8
+
9
+ class FakeServiceHost implements ServiceHost {
10
+ readonly name = 'cadre-host';
11
+ installCalls: ServiceHostContext[] = [];
12
+ uninstallCalls: ServiceHostContext[] = [];
13
+ installed = false;
14
+ running = false;
15
+
16
+ async install(ctx: ServiceHostContext): Promise<void> {
17
+ this.installCalls.push(ctx);
18
+ this.installed = true;
19
+ this.running = true;
20
+ }
21
+ async uninstall(ctx: ServiceHostContext): Promise<void> {
22
+ this.uninstallCalls.push(ctx);
23
+ this.installed = false;
24
+ this.running = false;
25
+ }
26
+ async restart(_ctx: ServiceHostContext): Promise<void> {
27
+ this.running = true;
28
+ }
29
+ async status(_ctx: ServiceHostContext): Promise<ServiceHostStatus> {
30
+ return { installed: this.installed, running: this.running };
31
+ }
32
+ renderUnit(_ctx: ServiceHostContext): string | null {
33
+ return 'rendered-unit';
34
+ }
35
+ }
36
+
37
+ describe('Installer smoke', () => {
38
+ let tmp: string;
39
+
40
+ beforeEach(() => {
41
+ tmp = mkdtempSync(join(tmpdir(), 'cadre-host-installer-smoke-'));
42
+ });
43
+
44
+ afterEach(() => {
45
+ rmSync(tmp, { recursive: true, force: true });
46
+ });
47
+
48
+ it('install creates config + invokes service-host', async () => {
49
+ const fake = new FakeServiceHost();
50
+ const installer = new Installer({ platform: 'linux', installerVersion: 'test-1.0.0' });
51
+ const result = await installer.install({
52
+ nonInteractive: true,
53
+ dataDir: tmp,
54
+ uiPort: 19999,
55
+ openBrowser: false,
56
+ serviceHost: fake,
57
+ });
58
+
59
+ expect(result.dataDir).toBe(tmp);
60
+ expect(result.uiUrl).toBe('http://127.0.0.1:19999/');
61
+ expect(result.serviceName).toBe('cadre-host');
62
+ expect(result.configPath).toBe(join(tmp, 'host.config.json'));
63
+
64
+ expect(existsSync(join(tmp, 'host.config.json'))).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);
67
+ expect(existsSync(join(tmp, 'nat.json'))).toBe(true);
68
+ expect(existsSync(join(tmp, 'logs'))).toBe(true);
69
+
70
+ expect(fake.installCalls).toHaveLength(1);
71
+ expect(fake.installCalls[0]!.dataDir).toBe(tmp);
72
+ });
73
+
74
+ it('noService writes the data dir without registering a service', async () => {
75
+ const fake = new FakeServiceHost();
76
+ const installer = new Installer({ platform: 'linux', installerVersion: 'test-1.0.0' });
77
+ const result = await installer.install({
78
+ nonInteractive: true,
79
+ dataDir: tmp,
80
+ uiPort: 19995,
81
+ noService: true,
82
+ serviceHost: fake,
83
+ });
84
+
85
+ expect(fake.installCalls).toHaveLength(0);
86
+ expect(result.serviceName).toBeUndefined();
87
+ expect(result.uiUrl).toBe('http://127.0.0.1:19995/');
88
+ expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
89
+ expect(existsSync(join(tmp, 'nat.json'))).toBe(true);
90
+ });
91
+
92
+ it('uninstall removes data only when --remove-data is set', async () => {
93
+ const fake = new FakeServiceHost();
94
+ const installer = new Installer({ platform: 'linux' });
95
+ await installer.install({
96
+ nonInteractive: true,
97
+ dataDir: tmp,
98
+ uiPort: 19998,
99
+ openBrowser: false,
100
+ serviceHost: fake,
101
+ });
102
+ expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
103
+
104
+ await installer.uninstall({ yes: true, removeData: false, dataDir: tmp, serviceHost: fake });
105
+ expect(fake.uninstallCalls).toHaveLength(1);
106
+ expect(existsSync(join(tmp, 'host.config.json'))).toBe(true);
107
+
108
+ await installer.uninstall({ yes: true, removeData: true, dataDir: tmp, serviceHost: fake });
109
+ expect(existsSync(tmp)).toBe(false);
110
+ });
111
+
112
+ it('rejects --system in v1', async () => {
113
+ const fake = new FakeServiceHost();
114
+ const installer = new Installer({ platform: 'linux' });
115
+ await expect(installer.install({
116
+ nonInteractive: true,
117
+ dataDir: tmp,
118
+ system: true,
119
+ serviceHost: fake,
120
+ openBrowser: false,
121
+ })).rejects.toThrow(/system.*not yet supported/);
122
+ expect(fake.installCalls).toHaveLength(0);
123
+ });
124
+
125
+ it('passes CLI overrides to the wizard as suggested defaults', async () => {
126
+ // Regression: the interactive wizard previously showed the platform
127
+ // default (`~/.local/share/cadre-host`) in the prompt even when the
128
+ // caller passed `--data-dir <path>`. Whatever the user typed was then
129
+ // silently discarded because the CLI flag won post-hoc. The fix: CLI
130
+ // overrides become the wizard's suggested defaults, and the wizard's
131
+ // returned answers stand on their own.
132
+ const fake = new FakeServiceHost();
133
+ const installer = new Installer({ platform: 'linux' });
134
+ let seenDefaults: Record<string, unknown> | undefined;
135
+ await installer.install({
136
+ nonInteractive: false,
137
+ dataDir: tmp,
138
+ uiPort: 19997,
139
+ noUpnp: true,
140
+ openBrowser: false,
141
+ serviceHost: fake,
142
+ publicInterfaceAddress: () => null,
143
+ wizard: async (defaults) => {
144
+ seenDefaults = { ...defaults };
145
+ // Simulate the user pressing Enter at every prompt.
146
+ return {
147
+ dataDir: defaults.dataDir,
148
+ uiPort: defaults.uiPort,
149
+ upnpEnabled: defaults.upnpEnabled,
150
+ };
151
+ },
152
+ });
153
+
154
+ // Every value came from a flag, so the wizard is told not to ask for any of them.
155
+ expect(seenDefaults).toEqual({
156
+ dataDir: tmp,
157
+ uiPort: 19997,
158
+ upnpEnabled: false,
159
+ given: { dataDir: true, uiPort: true, upnpEnabled: true },
160
+ publicInterfaceIp: null,
161
+ });
162
+ expect(fake.installCalls).toHaveLength(1);
163
+ expect(fake.installCalls[0]!.dataDir).toBe(tmp);
164
+ });
165
+
166
+ it('honors wizard-typed input when no CLI override is passed', async () => {
167
+ // Mirror of the previous test: with no `--data-dir`, the wizard's
168
+ // returned value is what the installer uses.
169
+ const fake = new FakeServiceHost();
170
+ const installer = new Installer({ platform: 'linux' });
171
+ await installer.install({
172
+ nonInteractive: false,
173
+ openBrowser: false,
174
+ serviceHost: fake,
175
+ wizard: async () => ({
176
+ dataDir: tmp,
177
+ uiPort: 19996,
178
+ upnpEnabled: true,
179
+ }),
180
+ });
181
+ expect(fake.installCalls).toHaveLength(1);
182
+ expect(fake.installCalls[0]!.dataDir).toBe(tmp);
183
+ });
184
+ });
@@ -5,9 +5,7 @@ import { runWizardWith, type WizardDefaults } from '../wizard.js';
5
5
  const defaults: WizardDefaults = {
6
6
  dataDir: '/data/cadre-host',
7
7
  uiPort: 8765,
8
- libp2pPort: 4001,
9
8
  upnpEnabled: true,
10
- ownCadre: false,
11
9
  };
12
10
 
13
11
  function scripted(answers: ReadonlyArray<string>): (label: string, fallback: string) => Promise<string> {
@@ -20,14 +18,11 @@ function scripted(answers: ReadonlyArray<string>): (label: string, fallback: str
20
18
 
21
19
  describe('runWizardWith', () => {
22
20
  it('returns the defaults when every answer is blank', async () => {
23
- const out = await runWizardWith(defaults, scripted(['', '', '', '', '', '']));
21
+ const out = await runWizardWith(defaults, scripted(['', '', '', '']));
24
22
  expect(out).toEqual({
25
23
  dataDir: '/data/cadre-host',
26
24
  uiPort: 8765,
27
- libp2pPort: 4001,
28
25
  upnpEnabled: true,
29
- configureDdns: false,
30
- ownCadre: false,
31
26
  });
32
27
  });
33
28
 
@@ -35,30 +30,47 @@ describe('runWizardWith', () => {
35
30
  const out = await runWizardWith(defaults, scripted([
36
31
  '/srv/host',
37
32
  '9000',
38
- '5000',
39
33
  'n',
40
34
  'y',
41
- 'y',
42
35
  ]));
43
36
  expect(out).toEqual({
44
37
  dataDir: '/srv/host',
45
38
  uiPort: 9000,
46
- libp2pPort: 5000,
47
39
  upnpEnabled: false,
48
- configureDdns: true,
49
- ownCadre: true,
50
40
  });
51
41
  });
52
42
 
53
43
  it('rejects invalid port input', async () => {
54
44
  await expect(
55
- runWizardWith(defaults, scripted(['', 'banana', '', '', ''])),
45
+ runWizardWith(defaults, scripted(['', 'banana', '', ''])),
56
46
  ).rejects.toThrow(/Invalid port/);
57
47
  });
58
48
 
49
+ it('asks nothing a flag already answered', async () => {
50
+ const asked: string[] = [];
51
+ const out = await runWizardWith(
52
+ { ...defaults, dataDir: '/srv/host', uiPort: 9000, upnpEnabled: false, given: { dataDir: true, uiPort: true, upnpEnabled: true } },
53
+ async (label) => { asked.push(label); return ''; },
54
+ );
55
+ expect(asked).toEqual([]);
56
+ expect(out).toEqual({ dataDir: '/srv/host', uiPort: 9000, upnpEnabled: false });
57
+ });
58
+
59
+ it('does not ask about UPnP on a machine with a public IP, and says why', async () => {
60
+ const asked: string[] = [];
61
+ const said: string[] = [];
62
+ await runWizardWith(
63
+ { ...defaults, given: { dataDir: true }, publicInterfaceIp: '203.0.113.7' },
64
+ async (label) => { asked.push(label); return ''; },
65
+ (line) => said.push(line),
66
+ );
67
+ expect(asked).toEqual(['UI port (localhost only)']);
68
+ expect(said.join(' ')).toMatch(/public IP address \(203\.0\.113\.7\).*no UPnP/);
69
+ });
70
+
59
71
  it('rejects unrecognized yes/no answers', async () => {
60
72
  await expect(
61
- runWizardWith(defaults, scripted(['', '', '', 'maybe', ''])),
73
+ runWizardWith(defaults, scripted(['', '', 'maybe', ''])),
62
74
  ).rejects.toThrow(/Invalid yes\/no/);
63
75
  });
64
76
  });