@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.
- package/README.md +347 -423
- package/dist/bin/host.d.ts +6 -3
- package/dist/bin/host.js +332 -524
- 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 +54 -0
- package/dist/bin/nat-output.js +138 -0
- package/dist/bin/nat-output.js.map +1 -0
- package/dist/bin/ui-access.d.ts +22 -0
- package/dist/bin/ui-access.js +44 -0
- package/dist/bin/ui-access.js.map +1 -0
- package/dist/hosted/claim-details.d.ts +66 -0
- package/dist/hosted/claim-details.js +60 -0
- package/dist/hosted/claim-details.js.map +1 -0
- package/dist/hosted/hosted-node-service.d.ts +325 -0
- package/dist/hosted/hosted-node-service.js +678 -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 +95 -0
- package/dist/hosted/hosted-node-watcher.js +260 -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 +51 -0
- package/dist/hosted/node-status.js +77 -0
- package/dist/hosted/node-status.js.map +1 -0
- package/dist/hosted/types.d.ts +141 -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 +27 -34
- package/dist/installer/config.js +46 -15
- package/dist/installer/config.js.map +1 -1
- package/dist/installer/index.d.ts +9 -19
- package/dist/installer/index.js +21 -85
- 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 +12 -9
- package/dist/installer/wizard.js +15 -14
- 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 +13 -9
- package/dist/nat/index.js +7 -5
- package/dist/nat/index.js.map +1 -1
- package/dist/nat/nat-service.d.ts +220 -77
- package/dist/nat/nat-service.js +653 -239
- package/dist/nat/nat-service.js.map +1 -1
- package/dist/nat/nat-store.d.ts +12 -5
- package/dist/nat/nat-store.js +124 -29
- package/dist/nat/nat-store.js.map +1 -1
- package/dist/nat/network-mode.d.ts +19 -0
- package/dist/nat/network-mode.js +32 -0
- package/dist/nat/network-mode.js.map +1 -0
- 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 +32 -15
- package/dist/nat/reachability.js +110 -22
- package/dist/nat/reachability.js.map +1 -1
- package/dist/nat/types.d.ts +98 -27
- package/dist/nat/types.js +8 -5
- package/dist/nat/types.js.map +1 -1
- package/dist/orchestrator/host-process-orchestrator.d.ts +32 -74
- package/dist/orchestrator/host-process-orchestrator.js +84 -233
- 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/package-version.d.ts +1 -0
- package/dist/package-version.js +4 -0
- package/dist/package-version.js.map +1 -0
- 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 +3 -7
- package/dist/server/routes/settings.js +18 -8
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/status.d.ts +2 -25
- package/dist/server/routes/status.js +3 -43
- 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-BHd8CWZA.css +1 -0
- package/dist/ui/assets/index-Ccr2GjPT.js +23 -0
- package/dist/ui/assets/index-Ccr2GjPT.js.map +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 -7
- package/src/__tests__/cli-nat.smoke.test.ts +219 -212
- package/src/__tests__/cli.smoke.test.ts +2 -2
- package/src/__tests__/global-setup.ts +2 -3
- package/src/__tests__/orchestrator-claim-secret.test.ts +111 -0
- package/src/__tests__/orchestrator-env-scrub.test.ts +160 -163
- package/src/__tests__/orchestrator-handle-contract.ts +134 -134
- package/src/__tests__/orchestrator-node-identity.test.ts +177 -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 +1017 -1063
- package/src/__tests__/ui-access.test.ts +30 -0
- package/src/bin/host.ts +397 -572
- package/src/bin/join-output.ts +120 -0
- package/src/bin/nat-output.ts +188 -0
- package/src/bin/ui-access.ts +50 -0
- package/src/{donation → hosted}/__tests__/fake-orchestrator.test.ts +172 -173
- package/src/{donation → hosted}/__tests__/fake-orchestrator.ts +43 -23
- package/src/hosted/__tests__/hosted-node-service.test.ts +708 -0
- package/src/hosted/__tests__/hosted-node-supervisor.test.ts +475 -0
- package/src/hosted/__tests__/test-invitation.ts +33 -0
- package/src/hosted/claim-details.ts +103 -0
- package/src/hosted/hosted-node-service.ts +814 -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 +288 -0
- package/src/hosted/index.ts +53 -0
- package/src/hosted/node-status.ts +97 -0
- package/src/hosted/types.ts +160 -0
- package/src/index.ts +72 -91
- package/src/installer/__tests__/config.test.ts +15 -40
- package/src/installer/__tests__/installer.smoke.test.ts +184 -263
- package/src/installer/__tests__/wizard.test.ts +25 -13
- package/src/installer/config.ts +201 -175
- package/src/installer/index.ts +252 -327
- package/src/installer/paths.ts +104 -109
- package/src/installer/wizard.ts +24 -26
- package/src/nat/__tests__/address-resolver.test.ts +107 -82
- package/src/nat/__tests__/nat-service.test.ts +608 -515
- package/src/nat/__tests__/nat-store.test.ts +49 -12
- package/src/nat/__tests__/network-mode.test.ts +38 -0
- 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 +33 -13
- package/src/nat/nat-service.ts +942 -501
- package/src/nat/nat-store.ts +126 -31
- package/src/nat/network-mode.ts +36 -0
- package/src/nat/port-mapper.ts +241 -329
- package/src/nat/reachability.ts +155 -34
- package/src/nat/types.ts +110 -37
- 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 +1089 -1260
- 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/package-version.ts +4 -0
- 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 +15 -18
- package/src/server/__tests__/hosted-nodes-route.test.ts +161 -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 +210 -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 +20 -15
- package/src/server/routes/status.ts +5 -67
- 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
|
@@ -47,6 +47,18 @@ function scrubbedParentEnv() {
|
|
|
47
47
|
}
|
|
48
48
|
return env;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The env carrying how the child gets into its cadre, and nowhere else (see
|
|
52
|
+
* `HostedSpawnRequest`): `CADRE_CLAIM_SECRET` makes `cadre-cli start` wait to be
|
|
53
|
+
* claimed, `CADRE_INVITATION` makes it redeem the invitation once it is up.
|
|
54
|
+
*/
|
|
55
|
+
function joinEnv(request) {
|
|
56
|
+
if (request.claimSecret)
|
|
57
|
+
return { CADRE_CLAIM_SECRET: request.claimSecret };
|
|
58
|
+
if (request.invitation)
|
|
59
|
+
return { CADRE_INVITATION: request.invitation };
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
50
62
|
/**
|
|
51
63
|
* The libp2p listen addresses a managed child binds — its `CADRE_LISTEN_ADDRS`, one
|
|
52
64
|
* entry per element: TCP on the `p2p` port and WebSocket on the `ws` port, both on
|
|
@@ -65,22 +77,17 @@ export function childListenAddrs(ports) {
|
|
|
65
77
|
/**
|
|
66
78
|
* Every TCP port a managed child binds, on the address it binds it to: health and
|
|
67
79
|
* metrics (`cadre-cli`'s `HealthServer`) and the libp2p TCP and WebSocket listeners
|
|
68
|
-
* ({@link childListenAddrs}) on every interface
|
|
69
|
-
*
|
|
70
|
-
* set lacks (a handle persisted by an older build) is skipped.
|
|
80
|
+
* ({@link childListenAddrs}), all on every interface. A key the set lacks (a handle
|
|
81
|
+
* persisted by an older build) is skipped.
|
|
71
82
|
*/
|
|
72
|
-
function childPortBindings(ports
|
|
83
|
+
function childPortBindings(ports) {
|
|
73
84
|
const bindings = [];
|
|
74
85
|
for (const port of [ports.health, ports.metrics, ports.p2p, ports.ws]) {
|
|
75
86
|
if (port !== undefined)
|
|
76
87
|
bindings.push({ port, host: '0.0.0.0' });
|
|
77
88
|
}
|
|
78
|
-
if (owner && ports.admin !== undefined)
|
|
79
|
-
bindings.push({ port: ports.admin, host: '127.0.0.1' });
|
|
80
89
|
return bindings;
|
|
81
90
|
}
|
|
82
|
-
/** Fixed friendly id for the admin's owner node. */
|
|
83
|
-
export const OWNER_CONTAINER_ID = 'owner';
|
|
84
91
|
/**
|
|
85
92
|
* Runs cadre nodes as plain Node child processes on the host.
|
|
86
93
|
*
|
|
@@ -127,6 +134,7 @@ export class HostProcessOrchestrator {
|
|
|
127
134
|
this.defaultMemoryLimit = cfg.defaultResources?.memoryLimit;
|
|
128
135
|
this.cliEntrypoint = cfg.spawn?.entrypoint ?? resolveCadreCliBin();
|
|
129
136
|
this.pushResolver = cfg.pushResolver;
|
|
137
|
+
this.announceAddrs = cfg.announceAddrs;
|
|
130
138
|
log('HostProcessOrchestrator rootDir=%s cli=%s', this.rootDir, this.cliEntrypoint);
|
|
131
139
|
}
|
|
132
140
|
/**
|
|
@@ -136,7 +144,6 @@ export class HostProcessOrchestrator {
|
|
|
136
144
|
*/
|
|
137
145
|
async init() {
|
|
138
146
|
const state = this.stateStore.load();
|
|
139
|
-
this.ownerConfig = state.ownerConfig;
|
|
140
147
|
for (const persisted of state.handles) {
|
|
141
148
|
const handle = {
|
|
142
149
|
containerId: persisted.containerId,
|
|
@@ -148,7 +155,7 @@ export class HostProcessOrchestrator {
|
|
|
148
155
|
spawnedAt: persisted.spawnedAt,
|
|
149
156
|
partyId: persisted.partyId,
|
|
150
157
|
profile: persisted.profile,
|
|
151
|
-
|
|
158
|
+
announcedAddrs: persisted.announcedAddrs ?? [],
|
|
152
159
|
alive: false,
|
|
153
160
|
};
|
|
154
161
|
handle.alive = isHandleLive(handle);
|
|
@@ -194,25 +201,32 @@ export class HostProcessOrchestrator {
|
|
|
194
201
|
}
|
|
195
202
|
/**
|
|
196
203
|
* Register a state-change listener. Invoked when a handle's `alive`
|
|
197
|
-
* transitions (child exit, manual stop)
|
|
204
|
+
* transitions (create, child exit, manual stop) and once more, as `stopped`,
|
|
205
|
+
* when a handle is removed. Returns an unsubscribe fn.
|
|
198
206
|
*/
|
|
199
207
|
onStateChange(listener) {
|
|
200
208
|
this.stateListeners.add(listener);
|
|
201
209
|
return () => { this.stateListeners.delete(listener); };
|
|
202
210
|
}
|
|
203
211
|
/**
|
|
204
|
-
* Spawn a
|
|
205
|
-
*
|
|
206
|
-
*
|
|
212
|
+
* Spawn a managed node — a hosted node for some cadre, started waiting to be
|
|
213
|
+
* claimed when the request carries a `claimSecret`, or redeeming the cadre
|
|
214
|
+
* invitation it carries as `invitation`. The node is given its own
|
|
215
|
+
* protobuf identity key inside its workdir (`ensureNodeIdentity`, reused across
|
|
207
216
|
* re-spawns of the same containerId), which is what makes its peer id stable
|
|
208
217
|
* across restarts AND what makes its node-local stores durable: `cadre-cli
|
|
209
218
|
* start` opens the file-backed bootstrap-peer, trusted-owner and strand
|
|
210
219
|
* network-state stores in the node's state directory, which defaults to the
|
|
211
220
|
* directory holding the `cadre.json` written here — the same workdir.
|
|
212
|
-
*
|
|
221
|
+
* Removing the node (`removeContainer`) deletes the workdir, so the key and
|
|
213
222
|
* every store go with it.
|
|
214
223
|
*/
|
|
215
224
|
async createContainer(request) {
|
|
225
|
+
// Before anything is allocated: `cadre-cli start` refuses the pair anyway, and a child
|
|
226
|
+
// that exits at once would only surface as a crash loop.
|
|
227
|
+
if (request.claimSecret && request.invitation) {
|
|
228
|
+
throw new Error(`container ${request.containerId}: a spawn carries a claim secret or an invitation, not both`);
|
|
229
|
+
}
|
|
216
230
|
const workdir = this.workdirFor(request.containerId);
|
|
217
231
|
// Sampled BEFORE `ensureNodeIdentity`, which brings the directory into
|
|
218
232
|
// existence as a side effect of writing the key. Only a spawn that found
|
|
@@ -233,25 +247,14 @@ export class HostProcessOrchestrator {
|
|
|
233
247
|
// node's worth of ports from a bounded range on every failed provision attempt.
|
|
234
248
|
const identity = await ensureNodeIdentity(workdir);
|
|
235
249
|
log('container %s identity peerId=%s', request.containerId, identity.peerId);
|
|
236
|
-
// A node started with pinned owner keys belongs to a FOREIGN cadre (the
|
|
237
|
-
// node-donation flow: it trusts the requester's owner key, not the host's).
|
|
238
|
-
// The host's FCM/APNs credentials are minted for the host owner's own app
|
|
239
|
-
// and are meaningless to a foreign cadre — so donated nodes get NO push
|
|
240
|
-
// block. (Per-grantee push creds would be a future ticket; none in v1.)
|
|
241
|
-
const foreignParty = (request.pinnedOwnerKeys?.length ?? 0) > 0;
|
|
242
250
|
// Only storage-profile nodes participate in strands and thus fan out push
|
|
243
251
|
// wakes — a transaction-only node need not carry credentials. Resolved
|
|
244
252
|
// BEFORE the drop below, which keeps the drop → launch window synchronous
|
|
245
253
|
// (see restoreDroppedHandles).
|
|
246
|
-
const push = request.profile === 'storage'
|
|
247
|
-
|
|
248
|
-
// `cadre-cli start` unions it into its cold-start pinnedKeyTrustPolicy so the
|
|
249
|
-
// node will accept the foreign-authority-signed seed presented via POST /seed.
|
|
250
|
-
const extraEnv = request.pinnedOwnerKeys?.length
|
|
251
|
-
? { CADRE_OWNER_KEYS: request.pinnedOwnerKeys.join(',') }
|
|
252
|
-
: undefined;
|
|
254
|
+
const push = request.profile === 'storage' ? await this.resolvePush() : undefined;
|
|
255
|
+
const extraEnv = joinEnv(request);
|
|
253
256
|
// The last `await` on this path: checked as close to the drop as it can be.
|
|
254
|
-
await this.refuseRespawnOverLiveChild(request.containerId
|
|
257
|
+
await this.refuseRespawnOverLiveChild(request.containerId);
|
|
255
258
|
// Nothing from here to the launch is `await`ed — which is what keeps
|
|
256
259
|
// `restoreDroppedHandles`'s documented precondition true even though the
|
|
257
260
|
// `try` now opens further up. Do not add an `await` past this line.
|
|
@@ -266,7 +269,6 @@ export class HostProcessOrchestrator {
|
|
|
266
269
|
partyId: request.partyId,
|
|
267
270
|
profile: request.profile,
|
|
268
271
|
ports,
|
|
269
|
-
owner: false,
|
|
270
272
|
buildConfig: () => this.buildChildConfig(request, workdir, push),
|
|
271
273
|
extraArgs: ['--identity-file', identity.path],
|
|
272
274
|
...(extraEnv ? { extraEnv } : {}),
|
|
@@ -294,7 +296,7 @@ export class HostProcessOrchestrator {
|
|
|
294
296
|
}
|
|
295
297
|
/**
|
|
296
298
|
* Remove the working directory a spawn created for `containerId` when no
|
|
297
|
-
* handle owns it — the cleanup of last resort for a
|
|
299
|
+
* handle owns it — the cleanup of last resort for a hosted-node record that
|
|
298
300
|
* never got a `dockerId` (the host died between `ensureNodeIdentity` and the
|
|
299
301
|
* handle write, so `createContainer`'s own unwind never ran). Returns whether
|
|
300
302
|
* anything was removed.
|
|
@@ -303,17 +305,12 @@ export class HostProcessOrchestrator {
|
|
|
303
305
|
* to await.
|
|
304
306
|
*/
|
|
305
307
|
reclaimWorkdir(containerId) {
|
|
306
|
-
// The admin's own node is never reclaimed by this route: its workdir is a
|
|
307
|
-
// single fixed path reused by every restart and it holds the host's own
|
|
308
|
-
// control-DB storage.
|
|
309
|
-
if (containerId === OWNER_CONTAINER_ID)
|
|
310
|
-
return false;
|
|
311
308
|
// A live handle owns that directory, and `removeContainer` — which stops
|
|
312
309
|
// the child first — is the only thing allowed to delete it.
|
|
313
310
|
if (this.resolveDockerId(containerId))
|
|
314
311
|
return false;
|
|
315
312
|
const workdir = this.workdirFor(containerId);
|
|
316
|
-
// Defence, not a live case:
|
|
313
|
+
// Defence, not a live case: hosted-node ids are `hn_<base64url>` and so can
|
|
317
314
|
// hold neither a path separator nor a dot. A caller that ever passes
|
|
318
315
|
// something else must not be able to walk out of `rootDir`.
|
|
319
316
|
if (!workdir.startsWith(this.rootDir + sep)) {
|
|
@@ -376,120 +373,27 @@ export class HostProcessOrchestrator {
|
|
|
376
373
|
}
|
|
377
374
|
}
|
|
378
375
|
/**
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
* orchestrator restart) can re-spawn it without re-supplying them.
|
|
384
|
-
*
|
|
385
|
-
* The owner node is spawned as `cadre-cli start --owner
|
|
386
|
-
* --admin-port <p> --identity-file <identityPath>` so it carries the
|
|
387
|
-
* host's libp2p identity, founds/joins the control network, and exposes the
|
|
388
|
-
* loopback admin channel the manager delegates to.
|
|
376
|
+
* The public addresses the node about to spawn announces. A hook that throws costs
|
|
377
|
+
* the node its public addresses, never its start: it still binds and is reachable
|
|
378
|
+
* on its LAN addresses, and `NatService` restarts it once the addresses it should
|
|
379
|
+
* announce differ from these.
|
|
389
380
|
*/
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
throw new Error('ensureOwnerNode: no owner config supplied and none persisted');
|
|
394
|
-
}
|
|
395
|
-
this.ownerConfig = config;
|
|
396
|
-
const existing = this.findOwnerHandle();
|
|
397
|
-
if (existing && isHandleLive(existing)) {
|
|
398
|
-
this.persist();
|
|
399
|
-
return toNodeInfo(existing);
|
|
400
|
-
}
|
|
401
|
-
const profile = config.profile ?? 'storage';
|
|
402
|
-
// Re-resolve push credentials from the secret store on every (re-)spawn so a
|
|
403
|
-
// restart picks up rotated keys and nothing raw is replayed from state.json.
|
|
404
|
-
// Resolved BEFORE the drop below so the drop → launch window stays
|
|
405
|
-
// synchronous (see restoreDroppedHandles).
|
|
406
|
-
const push = await this.resolvePush();
|
|
407
|
-
// The last `await` before the drop — same guard as in createContainer.
|
|
408
|
-
await this.refuseRespawnOverLiveChild(OWNER_CONTAINER_ID, { p2p: config.libp2pPort }, true);
|
|
409
|
-
// NOTE: the short-circuit above matches via `findOwnerHandle` (owner flag OR
|
|
410
|
-
// containerId), this drop only by containerId. Equivalent today — every
|
|
411
|
-
// owner handle is launched with OWNER_CONTAINER_ID. If an owner spawn ever
|
|
412
|
-
// takes a different containerId, the two would disagree and the handle the
|
|
413
|
-
// short-circuit found would survive here, leaking it and its ports.
|
|
414
|
-
const dropped = this.dropStaleHandle(OWNER_CONTAINER_ID);
|
|
415
|
-
let ports;
|
|
381
|
+
announceAddrsFor(containerId, ports) {
|
|
382
|
+
if (!this.announceAddrs)
|
|
383
|
+
return [];
|
|
416
384
|
try {
|
|
417
|
-
|
|
418
|
-
// mapping (external → internal) lands on it — hence the p2p override, which
|
|
419
|
-
// wins over the dropped handle's. Every other port comes back as it was, for
|
|
420
|
-
// the same reason as in createContainer.
|
|
421
|
-
ports = allocateNodePorts(this.portAllocator, { ...reusedNodePorts(dropped), p2p: config.libp2pPort });
|
|
422
|
-
const result = this.launchChild({
|
|
423
|
-
containerId: OWNER_CONTAINER_ID,
|
|
424
|
-
partyId: config.partyId,
|
|
425
|
-
profile,
|
|
426
|
-
ports,
|
|
427
|
-
owner: true,
|
|
428
|
-
buildConfig: (workdir) => this.buildOwnerChildConfig(config, profile, workdir, push),
|
|
429
|
-
extraArgs: ['--owner', '--admin-port', String(ports.admin), '--identity-file', config.identityPath],
|
|
430
|
-
});
|
|
431
|
-
const handle = this.handles.get(result.dockerId);
|
|
432
|
-
return toNodeInfo(handle);
|
|
385
|
+
return this.announceAddrs(containerId, ports);
|
|
433
386
|
}
|
|
434
387
|
catch (err) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
this.releasePorts(ports);
|
|
438
|
-
this.restoreDroppedHandles(dropped);
|
|
439
|
-
// Deliberately NOT `discardWorkdir` — the asymmetry with createContainer
|
|
440
|
-
// is the point. `<rootDir>/owner` is a single fixed path reused by every
|
|
441
|
-
// restart and holds the host's own control-DB storage, so it neither
|
|
442
|
-
// grows without bound nor is safe to delete on a failed spawn.
|
|
443
|
-
throw err;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
/** Endpoint + bearer for the owner node's admin channel, if spawned. */
|
|
447
|
-
getOwnerAdminEndpoint() {
|
|
448
|
-
const handle = this.findOwnerHandle();
|
|
449
|
-
if (!handle)
|
|
450
|
-
return undefined;
|
|
451
|
-
return {
|
|
452
|
-
baseUrl: `http://127.0.0.1:${handle.ports.admin}`,
|
|
453
|
-
token: handle.startupToken,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
/** Whether spawn parameters for the owner node are known (persisted). */
|
|
457
|
-
hasOwnerConfig() {
|
|
458
|
-
return this.ownerConfig !== undefined;
|
|
459
|
-
}
|
|
460
|
-
/** Whether the given friendly/docker id refers to the owner node. */
|
|
461
|
-
isOwnerNode(idOrDockerId) {
|
|
462
|
-
if (idOrDockerId === OWNER_CONTAINER_ID)
|
|
463
|
-
return true;
|
|
464
|
-
const direct = this.handles.get(idOrDockerId);
|
|
465
|
-
if (direct?.owner)
|
|
466
|
-
return true;
|
|
467
|
-
for (const h of this.handles.values()) {
|
|
468
|
-
if (h.owner && h.containerId === idOrDockerId)
|
|
469
|
-
return true;
|
|
388
|
+
log('announce hook failed for %s; it starts with no public addresses: %s', containerId, err.message);
|
|
389
|
+
return [];
|
|
470
390
|
}
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
/** Stop the owner node (if running). Used on graceful shutdown. */
|
|
474
|
-
async stopOwnerNode() {
|
|
475
|
-
const handle = this.findOwnerHandle();
|
|
476
|
-
if (handle && isPidAlive(handle.pid)) {
|
|
477
|
-
await this.stopContainer(handle.dockerId);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
/** Stop the owner node (if running), then re-spawn it from saved config. */
|
|
481
|
-
async restartOwnerNode() {
|
|
482
|
-
const handle = this.findOwnerHandle();
|
|
483
|
-
if (handle && isPidAlive(handle.pid)) {
|
|
484
|
-
await this.stopContainer(handle.dockerId);
|
|
485
|
-
}
|
|
486
|
-
return this.ensureOwnerNode();
|
|
487
391
|
}
|
|
488
392
|
/**
|
|
489
|
-
* Core spawn mechanics
|
|
393
|
+
* Core spawn mechanics behind `createContainer`.
|
|
490
394
|
*
|
|
491
395
|
* The caller has already allocated `ports` and **owns their lifetime**: this
|
|
492
|
-
* method never releases them, so
|
|
396
|
+
* method never releases them, so the caller must release on any throw out
|
|
493
397
|
* of here (it has to unwind its handle drop on the same path anyway — see
|
|
494
398
|
* `restoreDroppedHandles`).
|
|
495
399
|
*
|
|
@@ -506,12 +410,6 @@ export class HostProcessOrchestrator {
|
|
|
506
410
|
mkdirSync(workdir, { recursive: true });
|
|
507
411
|
mkdirSync(join(workdir, 'storage'), { recursive: true });
|
|
508
412
|
const token = randomBytes(16).toString('hex');
|
|
509
|
-
// Per-container secret gating the node's `POST /seed` route. The node
|
|
510
|
-
// refuses seed delivery unless the caller presents this as a bearer token
|
|
511
|
-
// (and unregisters the route entirely when the env var is empty), so we
|
|
512
|
-
// mint a fresh high-entropy value per child and hand it back on the result
|
|
513
|
-
// for `ContainerService.applySeed` to send.
|
|
514
|
-
const seedToken = randomBytes(32).toString('base64url');
|
|
515
413
|
const tokenPath = join(workdir, STARTUP_TOKEN_FILE);
|
|
516
414
|
// Ensure stale token from a previous container with the same id is cleared.
|
|
517
415
|
if (existsSync(tokenPath)) {
|
|
@@ -525,32 +423,29 @@ export class HostProcessOrchestrator {
|
|
|
525
423
|
const memoryLimit = opts.memoryLimit ?? this.defaultMemoryLimit;
|
|
526
424
|
const heapBytes = parseMemoryLimit(memoryLimit);
|
|
527
425
|
const heapMB = heapBytes !== undefined ? Math.max(64, Math.floor(heapBytes / (1024 * 1024))) : undefined;
|
|
426
|
+
const announcedAddrs = this.announceAddrsFor(containerId, ports);
|
|
528
427
|
const env = {
|
|
529
428
|
// Scrubbed, not raw process.env: every CADRE_* config-override key is
|
|
530
429
|
// stripped so a var set on the manager can't silently reconfigure every
|
|
531
430
|
// child (see scrubbedParentEnv). Caller-supplied extras come next — the
|
|
532
431
|
// fixed per-child vars below always win so extraEnv can never clobber
|
|
533
|
-
// the
|
|
432
|
+
// the startup token or port wiring.
|
|
534
433
|
...scrubbedParentEnv(),
|
|
535
434
|
...opts.extraEnv,
|
|
536
435
|
CADRE_STARTUP_TOKEN: token,
|
|
537
436
|
CADRE_HEALTH_PORT: String(ports.health),
|
|
538
437
|
CADRE_METRICS_PORT: String(ports.metrics),
|
|
539
438
|
CADRE_LISTEN_ADDRS: childListenAddrs(ports).join(','),
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
// WebSocket port, and every strand node are reached through observed addresses only,
|
|
551
|
-
// since no `CADRE_RELAY_ADDRS` is set here and a child therefore holds no relay
|
|
552
|
-
// reservation.
|
|
553
|
-
CADRE_SEED_TOKEN: seedToken,
|
|
439
|
+
// The append form, not `CADRE_ANNOUNCE_ADDRS`: that one replaces the advertised
|
|
440
|
+
// set and would drop the LAN addresses a phone at home dials. Every entry must be
|
|
441
|
+
// a valid multiaddr: cadre-core refuses to start on one that is not.
|
|
442
|
+
// The TCP and WebSocket ports are the CHILD'S CONTROL NODE's alone: each strand
|
|
443
|
+
// node the child runs binds the same two entries with OS-assigned ports instead,
|
|
444
|
+
// since one port cannot be held twice, and inherits neither announce field
|
|
445
|
+
// (`cadre-core/src/strand-network-config.ts`). Those ports are not mapped, and
|
|
446
|
+
// strand nodes are reached through observed addresses only, since no
|
|
447
|
+
// `CADRE_RELAY_ADDRS` is set here and a child therefore holds no relay reservation.
|
|
448
|
+
...(announcedAddrs.length > 0 ? { CADRE_APPEND_ANNOUNCE_ADDRS: announcedAddrs.join(',') } : {}),
|
|
554
449
|
// Pin each child's node-local state (trusted-owner anchor, retained
|
|
555
450
|
// cold-start dial targets) to its OWN workdir. This is the same value the
|
|
556
451
|
// cli would derive by default (the config file lives here too), but stated
|
|
@@ -612,7 +507,7 @@ export class HostProcessOrchestrator {
|
|
|
612
507
|
spawnedAt: new Date().toISOString(),
|
|
613
508
|
partyId: opts.partyId,
|
|
614
509
|
profile: opts.profile,
|
|
615
|
-
|
|
510
|
+
announcedAddrs,
|
|
616
511
|
child,
|
|
617
512
|
alive: true,
|
|
618
513
|
};
|
|
@@ -644,23 +539,20 @@ export class HostProcessOrchestrator {
|
|
|
644
539
|
dockerId,
|
|
645
540
|
healthEndpoint: `http://localhost:${ports.health}/health`,
|
|
646
541
|
metricsEndpoint: `http://localhost:${ports.metrics}/metrics`,
|
|
647
|
-
// The node's seed API (`POST /seed`) is bound to the same server/port as `/health`.
|
|
648
|
-
seedEndpoint: `http://localhost:${ports.health}/seed`,
|
|
649
|
-
seedToken,
|
|
650
542
|
p2pPort: ports.p2p,
|
|
651
543
|
};
|
|
652
544
|
}
|
|
653
545
|
/**
|
|
654
546
|
* Refuse to re-spawn `containerId` while a previous child of it may still be
|
|
655
|
-
* running.
|
|
547
|
+
* running. `createContainer` calls this as its last `await` before the handle
|
|
656
548
|
* drop, so a refusal happens while nothing has been released, no token rotated
|
|
657
549
|
* and no child launched — the caller sees an ordinary failed spawn.
|
|
658
550
|
*
|
|
659
551
|
* Why it exists: the drop hands the old handle's ports back and the launch takes
|
|
660
552
|
* those same ports (`reusedNodePorts`), so a child started over a live one dies
|
|
661
|
-
* on a port clash — but only after `launchChild` has returned success
|
|
662
|
-
*
|
|
663
|
-
*
|
|
553
|
+
* on a port clash — but only after `launchChild` has returned success. The
|
|
554
|
+
* caller then records a dead child, and the live one keeps running with no
|
|
555
|
+
* handle naming it.
|
|
664
556
|
*
|
|
665
557
|
* - A handle this process spawned is checked exactly ({@link isHandleLive}); a
|
|
666
558
|
* live one refuses outright.
|
|
@@ -670,9 +562,6 @@ export class HostProcessOrchestrator {
|
|
|
670
562
|
* is already bound, refuse. The pid is never killed on that evidence — it may
|
|
671
563
|
* not be ours.
|
|
672
564
|
*
|
|
673
|
-
* `overrides` are the ports the caller will override on top of the reused ones
|
|
674
|
-
* (the owner node's configured p2p port); `owner` adds the admin channel.
|
|
675
|
-
*
|
|
676
565
|
* NOTE: the probe cannot see a re-attached child still loading its modules —
|
|
677
566
|
* `cadre-cli start` writes its token and only then binds, so a child in that
|
|
678
567
|
* window shows neither, and a re-spawn launched then races it for the ports.
|
|
@@ -687,14 +576,14 @@ export class HostProcessOrchestrator {
|
|
|
687
576
|
* child is ever seen dying on its own port right after a host restart, probe by
|
|
688
577
|
* connecting instead of binding.
|
|
689
578
|
*/
|
|
690
|
-
async refuseRespawnOverLiveChild(containerId
|
|
579
|
+
async refuseRespawnOverLiveChild(containerId) {
|
|
691
580
|
const previous = this.handlesFor(containerId);
|
|
692
581
|
if (previous.some(isHandleLive)) {
|
|
693
582
|
throw new Error(`container ${containerId} is still running`);
|
|
694
583
|
}
|
|
695
584
|
if (!previous.some((h) => isPidAlive(h.pid)))
|
|
696
585
|
return;
|
|
697
|
-
const bindings = childPortBindings(
|
|
586
|
+
const bindings = childPortBindings(reusedNodePorts(previous));
|
|
698
587
|
try {
|
|
699
588
|
await Promise.all(bindings.map(assertPortFree));
|
|
700
589
|
}
|
|
@@ -709,9 +598,9 @@ export class HostProcessOrchestrator {
|
|
|
709
598
|
/**
|
|
710
599
|
* Drop any handle left over from a previous spawn of the same `containerId`
|
|
711
600
|
* and release its ports. Handles are keyed by the per-spawn `dockerId`, so
|
|
712
|
-
* without this a re-spawn (
|
|
601
|
+
* without this a re-spawn (a hosted node's respawn) would strand the prior
|
|
713
602
|
* handle in the map forever, leaking its ports from a bounded range each
|
|
714
|
-
* time.
|
|
603
|
+
* time.
|
|
715
604
|
*
|
|
716
605
|
* DO NOT delete the workdir: the identity key and node-local stores
|
|
717
606
|
* (trusted-owner anchor, retained cold-start dial targets) live there and are
|
|
@@ -721,16 +610,16 @@ export class HostProcessOrchestrator {
|
|
|
721
610
|
* Releasing the ports hands them straight back to the allocator, and the
|
|
722
611
|
* re-spawn then takes those same ports as overrides (`reusedNodePorts`), so a
|
|
723
612
|
* re-spawn while the *previous* child is still listening would bind-clash.
|
|
724
|
-
*
|
|
613
|
+
* The caller must therefore never drop a live child, and does so only after
|
|
725
614
|
* {@link refuseRespawnOverLiveChild} has passed. A caller that needs to replace
|
|
726
615
|
* a live child must stop it first.
|
|
727
616
|
*
|
|
728
617
|
* **The drop is reversible.** It happens before the launch can fail, so the
|
|
729
|
-
* dropped handles are returned and
|
|
618
|
+
* dropped handles are returned and the caller restores them via
|
|
730
619
|
* {@link restoreDroppedHandles} when the launch throws — otherwise the caller
|
|
731
620
|
* would be left holding a dockerId this orchestrator no longer knows, and the
|
|
732
621
|
* node's workdir could never be reclaimed. On a *successful* spawn the drop
|
|
733
|
-
* stands: `
|
|
622
|
+
* stands: `HostedNodeService.abandonRespawn` depends on the old handle being
|
|
734
623
|
* gone by then.
|
|
735
624
|
*/
|
|
736
625
|
dropStaleHandle(containerId) {
|
|
@@ -749,10 +638,10 @@ export class HostProcessOrchestrator {
|
|
|
749
638
|
*
|
|
750
639
|
* **Precondition: the drop → launch window is synchronous.** Restoring is
|
|
751
640
|
* sound only because nothing can have taken those ports or run cleanup
|
|
752
|
-
* against the handle in between — `launchChild` contains no `await` and
|
|
753
|
-
*
|
|
641
|
+
* against the handle in between — `launchChild` contains no `await` and the
|
|
642
|
+
* caller resolves push credentials *before* its drop. `launchChild`'s half
|
|
754
643
|
* is enforced: its declared return type is not a Promise, so adding an
|
|
755
|
-
* `await` there is a compile error. The
|
|
644
|
+
* `await` there is a compile error. The caller's half is not — an `await`
|
|
756
645
|
* slipped between a drop and its launch silently breaks this.
|
|
757
646
|
*
|
|
758
647
|
* No `persist()` here: `state.json` is rewritten only by `launchChild` and
|
|
@@ -778,14 +667,6 @@ export class HostProcessOrchestrator {
|
|
|
778
667
|
workdirFor(containerId) {
|
|
779
668
|
return join(this.rootDir, containerId);
|
|
780
669
|
}
|
|
781
|
-
/** Locate the owner node's handle, if one has been spawned. */
|
|
782
|
-
findOwnerHandle() {
|
|
783
|
-
for (const h of this.handles.values()) {
|
|
784
|
-
if (h.owner || h.containerId === OWNER_CONTAINER_ID)
|
|
785
|
-
return h;
|
|
786
|
-
}
|
|
787
|
-
return undefined;
|
|
788
|
-
}
|
|
789
670
|
/**
|
|
790
671
|
* Inverse of {@link releasePorts} — hold a handle's whole port set against
|
|
791
672
|
* the allocator. Used both to rehydrate on `init` and to undo a drop whose
|
|
@@ -861,6 +742,11 @@ export class HostProcessOrchestrator {
|
|
|
861
742
|
this.discardWorkdir(handle.workdir, { maxRetries: 20, retryDelay: 200 });
|
|
862
743
|
this.handles.delete(dockerId);
|
|
863
744
|
this.persist();
|
|
745
|
+
// Removal is a state change too: `NatService` releases the node's port
|
|
746
|
+
// mappings on it at once rather than at its next timer pass. A stopped
|
|
747
|
+
// event for a node no longer in `listNodes()` is how listeners tell a
|
|
748
|
+
// removal from a stop.
|
|
749
|
+
this.emitStateChange(handle);
|
|
864
750
|
}
|
|
865
751
|
/**
|
|
866
752
|
* Whether the child `dockerId` names is running — see {@link isHandleLive}. An
|
|
@@ -912,7 +798,8 @@ export class HostProcessOrchestrator {
|
|
|
912
798
|
const cfg = {
|
|
913
799
|
controlNetwork: {
|
|
914
800
|
partyId: req.partyId,
|
|
915
|
-
|
|
801
|
+
// A hosted node is dialed (a claim) or dials the members its invitation names.
|
|
802
|
+
bootstrapNodes: [],
|
|
916
803
|
},
|
|
917
804
|
profile: req.profile,
|
|
918
805
|
// The request carries the filter as text (the provider hands the same text to
|
|
@@ -939,46 +826,10 @@ export class HostProcessOrchestrator {
|
|
|
939
826
|
cfg.push = push;
|
|
940
827
|
return cfg;
|
|
941
828
|
}
|
|
942
|
-
/**
|
|
943
|
-
* Child config for the **host's own personal cadre** owner node — the opt-in
|
|
944
|
-
* "founder" persona spawned only when `ownCadre.enabled` (see
|
|
945
|
-
* docs/cadre-host.md § Two roles: donor and founder). It founds/joins the
|
|
946
|
-
* control network for the host's own `partyId` (no bootstrap peers — it is the
|
|
947
|
-
* founding node of the host's *own* cadre) and carries the host identity via
|
|
948
|
-
* `--identity-file` (passed as a spawn arg).
|
|
949
|
-
*
|
|
950
|
-
* This is NOT the node cadre-host donates to a requester. Donated nodes are
|
|
951
|
-
* generic (`createContainer` / `buildChildConfig`): they join the
|
|
952
|
-
* **requester's** cadre via bootstrap peers and pin the *requester's* owner
|
|
953
|
-
* key — they never run a host genesis.
|
|
954
|
-
*/
|
|
955
|
-
buildOwnerChildConfig(cfg, profile, workdir, push) {
|
|
956
|
-
const config = {
|
|
957
|
-
controlNetwork: {
|
|
958
|
-
partyId: cfg.partyId,
|
|
959
|
-
bootstrapNodes: [],
|
|
960
|
-
},
|
|
961
|
-
profile,
|
|
962
|
-
strandFilter: 'all',
|
|
963
|
-
};
|
|
964
|
-
if (profile === 'storage') {
|
|
965
|
-
config.storage = { type: 'file', path: join(workdir, 'storage') };
|
|
966
|
-
}
|
|
967
|
-
// The host-own-cadre owner/storage node participates in strands, so it owns
|
|
968
|
-
// the push-wake fan-out when credentials are configured. (Donated nodes
|
|
969
|
-
// belong to foreign cadres and get NO host push block — see the donor path.)
|
|
970
|
-
// Written into cadre.json (same host trust boundary as the workdir's
|
|
971
|
-
// control-DB); keys are never logged — only the redacted presence line in
|
|
972
|
-
// resolvePush.
|
|
973
|
-
if (push)
|
|
974
|
-
config.push = push;
|
|
975
|
-
return config;
|
|
976
|
-
}
|
|
977
829
|
persist() {
|
|
978
830
|
const state = {
|
|
979
831
|
version: 1,
|
|
980
832
|
handles: [...this.handles.values()].map((h) => this.toPersisted(h)),
|
|
981
|
-
...(this.ownerConfig ? { ownerConfig: this.ownerConfig } : {}),
|
|
982
833
|
};
|
|
983
834
|
this.stateStore.save(state);
|
|
984
835
|
}
|
|
@@ -1006,7 +857,7 @@ export class HostProcessOrchestrator {
|
|
|
1006
857
|
spawnedAt: h.spawnedAt,
|
|
1007
858
|
partyId: h.partyId,
|
|
1008
859
|
profile: h.profile,
|
|
1009
|
-
|
|
860
|
+
announcedAddrs: h.announcedAddrs,
|
|
1010
861
|
};
|
|
1011
862
|
}
|
|
1012
863
|
}
|
|
@@ -1020,7 +871,7 @@ function toNodeInfo(h) {
|
|
|
1020
871
|
spawnedAt: h.spawnedAt,
|
|
1021
872
|
workdir: h.workdir,
|
|
1022
873
|
ports: { ...h.ports },
|
|
1023
|
-
...
|
|
874
|
+
announcedAddrs: [...h.announcedAddrs],
|
|
1024
875
|
};
|
|
1025
876
|
}
|
|
1026
877
|
/**
|
|
@@ -1040,8 +891,8 @@ function resolveCadreCliBin() {
|
|
|
1040
891
|
}
|
|
1041
892
|
/**
|
|
1042
893
|
* Whether the process a handle names is still the child it was launched as — the
|
|
1043
|
-
* one liveness rule behind `init`, `isRunning
|
|
1044
|
-
*
|
|
894
|
+
* one liveness rule behind `init`, `isRunning` and the pre-spawn guard, so they
|
|
895
|
+
* cannot drift apart.
|
|
1045
896
|
*
|
|
1046
897
|
* A handle this process spawned carries its `ChildProcess`, and that answer is
|
|
1047
898
|
* exact from the moment of spawn: no exit seen, and the pid still answering. (The
|