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