@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/nat/reachability.ts
CHANGED
|
@@ -1,34 +1,143 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Reachability verdicts: one per hosted node, and the host-level roll-up the
|
|
3
|
+
* dashboard badge shows. Heuristics over the mapping table, not a dial-back —
|
|
4
|
+
* libp2p AutoNAT is a later ticket.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
DirectReachability,
|
|
9
|
+
NodeReachability,
|
|
10
|
+
NodeVerdict,
|
|
11
|
+
PortKind,
|
|
12
|
+
PortRoute,
|
|
13
|
+
} from './types.js';
|
|
2
14
|
|
|
3
|
-
export interface
|
|
4
|
-
|
|
15
|
+
export interface NodeVerdictInput {
|
|
16
|
+
tcp: PortRoute;
|
|
17
|
+
/** Null for a handle persisted by an older build without a WebSocket port. */
|
|
18
|
+
ws: PortRoute | null;
|
|
19
|
+
/** Whether a public host part exists: a DDNS hostname, or a public IPv4. */
|
|
20
|
+
hostKnown: boolean;
|
|
5
21
|
cgnatDetected: boolean;
|
|
22
|
+
upnpEnabled: boolean;
|
|
23
|
+
gatewayFound: boolean;
|
|
24
|
+
/** The address a user forwards to; null when no gateway was found. */
|
|
25
|
+
lanAddress: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NodeVerdictResult {
|
|
29
|
+
verdict: NodeVerdict;
|
|
30
|
+
reason: string | null;
|
|
6
31
|
}
|
|
7
32
|
|
|
8
33
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* external IP isn't actually reachable from the open internet).
|
|
14
|
-
* - 'unreachable' when port mapping failed.
|
|
15
|
-
* - 'reachable' when an auto mapping succeeded.
|
|
16
|
-
* - 'unknown' otherwise (manual config, disabled UPnP, etc).
|
|
17
|
-
*
|
|
18
|
-
* A future ticket will replace this with libp2p AutoNAT or a sereus-owned
|
|
19
|
-
* dial-back probe (see the follow-up section of `6.3-cadre-host-nat`).
|
|
34
|
+
* `unreachable` when either port has no usable route or the host part is
|
|
35
|
+
* unknown; otherwise `manual` when either port is forwarded by hand, else
|
|
36
|
+
* `mapped`. A UPnP route under CGNAT is not usable: the router's mapping is on
|
|
37
|
+
* a carrier-private address.
|
|
20
38
|
*/
|
|
21
|
-
export function
|
|
39
|
+
export function evaluateNodeReachability(input: NodeVerdictInput): NodeVerdictResult {
|
|
40
|
+
const ports = portRoutes(input);
|
|
41
|
+
const failing = ports.filter(({ route }) => !usable(route, input.cgnatDetected));
|
|
42
|
+
if (!input.hostKnown) {
|
|
43
|
+
return { verdict: 'unreachable', reason: HOST_UNKNOWN_REASON };
|
|
44
|
+
}
|
|
45
|
+
if (failing.length > 0) {
|
|
46
|
+
return { verdict: 'unreachable', reason: describeFailures(failing, input) };
|
|
47
|
+
}
|
|
48
|
+
const manual = ports.some(({ route }) => route.source === 'manual');
|
|
49
|
+
return { verdict: manual ? 'manual' : 'mapped', reason: null };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Host roll-up: `cgnat` when CGNAT is detected and no node is reachable
|
|
54
|
+
* through a manual route; `unknown` with no running nodes; `reachable` when
|
|
55
|
+
* every running node is `mapped`/`manual`; else `unreachable`.
|
|
56
|
+
*/
|
|
57
|
+
export function evaluateHostReachability(
|
|
58
|
+
nodes: ReadonlyArray<Pick<NodeReachability, 'running' | 'verdict'>>,
|
|
59
|
+
cgnatDetected: boolean,
|
|
60
|
+
): DirectReachability {
|
|
61
|
+
const running = nodes.filter((n) => n.running);
|
|
62
|
+
if (cgnatDetected && !running.some((n) => n.verdict === 'manual')) return 'cgnat';
|
|
63
|
+
if (running.length === 0) return 'unknown';
|
|
64
|
+
return running.every((n) => n.verdict !== 'unreachable') ? 'reachable' : 'unreachable';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const HOST_UNKNOWN_REASON =
|
|
68
|
+
"This host's public address is unknown: no dynamic DNS hostname is set and no public IPv4 address was detected. " +
|
|
69
|
+
'Set a DDNS hostname, or check that this machine can reach the internet.';
|
|
70
|
+
|
|
71
|
+
interface PortEntry {
|
|
72
|
+
kind: PortKind;
|
|
73
|
+
route: PortRoute;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function portRoutes(input: NodeVerdictInput): PortEntry[] {
|
|
77
|
+
const out: PortEntry[] = [{ kind: 'tcp', route: input.tcp }];
|
|
78
|
+
if (input.ws) out.push({ kind: 'ws', route: input.ws });
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function usable(route: PortRoute, cgnatDetected: boolean): boolean {
|
|
83
|
+
if (route.externalPort === null || route.source === null) return false;
|
|
84
|
+
return !(cgnatDetected && route.source === 'upnp');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type FailureCause = 'cgnat' | 'refused' | 'upnp_off' | 'no_gateway' | 'pending';
|
|
88
|
+
|
|
89
|
+
/** Behind CGNAT no router-side remedy helps, so CGNAT outranks whatever else failed the port. */
|
|
90
|
+
function causeOf(route: PortRoute, input: NodeVerdictInput): FailureCause {
|
|
22
91
|
if (input.cgnatDetected) return 'cgnat';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
92
|
+
if (route.error) return 'refused';
|
|
93
|
+
if (!input.upnpEnabled) return 'upnp_off';
|
|
94
|
+
if (!input.gatewayFound) return 'no_gateway';
|
|
95
|
+
return 'pending';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One sentence per distinct cause, each naming every port that failed for it. */
|
|
99
|
+
function describeFailures(failing: PortEntry[], input: NodeVerdictInput): string {
|
|
100
|
+
const byCause = new Map<FailureCause, PortEntry[]>();
|
|
101
|
+
for (const entry of failing) {
|
|
102
|
+
const cause = causeOf(entry.route, input);
|
|
103
|
+
byCause.set(cause, [...(byCause.get(cause) ?? []), entry]);
|
|
104
|
+
}
|
|
105
|
+
return [...byCause.entries()].map(([cause, entries]) => describeCause(cause, entries, input)).join(' ');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function describeCause(cause: FailureCause, entries: PortEntry[], input: NodeVerdictInput): string {
|
|
109
|
+
const names = entries.map(({ kind, route }) => `${kindLabel(kind)} port ${route.internalPort}`);
|
|
110
|
+
const lan = input.lanAddress ?? "this machine's LAN address";
|
|
111
|
+
const forwardHint = `Forward ${entries.length === 1 ? 'port' : 'ports'} ${listPorts(entries)} to ${lan} on your router, then tell cadre-host the external ${entries.length === 1 ? 'port' : 'ports'}.`;
|
|
112
|
+
switch (cause) {
|
|
113
|
+
case 'cgnat':
|
|
114
|
+
return `This host is behind carrier-grade NAT, so the ${joinNames(names)} cannot be reached from outside through your router, and neither UPnP nor a port forward on your router will help; a relay is needed.`;
|
|
115
|
+
case 'refused': {
|
|
116
|
+
const errors = entries.map(({ route }) => route.error).filter((e): e is string => !!e);
|
|
117
|
+
return `Router refused the ${joinNames(names)} mapping (${errors.join('; ')}). ${forwardHint}`;
|
|
118
|
+
}
|
|
119
|
+
case 'upnp_off':
|
|
120
|
+
return `UPnP is off, so the ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped. Turn UPnP on, or: ${lowerFirst(forwardHint)}`;
|
|
121
|
+
case 'no_gateway':
|
|
122
|
+
return `No UPnP router answered, so the ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped. Enable UPnP on your router, or: ${lowerFirst(forwardHint)}`;
|
|
123
|
+
case 'pending':
|
|
124
|
+
return `The ${joinNames(names)} ${entries.length === 1 ? 'is' : 'are'} not mapped yet; the router is being asked.`;
|
|
33
125
|
}
|
|
34
126
|
}
|
|
127
|
+
|
|
128
|
+
function kindLabel(kind: PortKind): string {
|
|
129
|
+
return kind === 'tcp' ? 'TCP' : 'WebSocket';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function listPorts(entries: PortEntry[]): string {
|
|
133
|
+
return joinNames(entries.map(({ route }) => String(route.internalPort)));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function joinNames(names: string[]): string {
|
|
137
|
+
if (names.length <= 1) return names[0] ?? '';
|
|
138
|
+
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function lowerFirst(s: string): string {
|
|
142
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
143
|
+
}
|
package/src/nat/types.ts
CHANGED
|
@@ -1,159 +1,209 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NAT/DDNS types for cadre-host.
|
|
3
|
-
*
|
|
4
|
-
* cadre-host runs on home/SMB machines that are typically behind NAT.
|
|
5
|
-
* dialable from the open internet the host must:
|
|
6
|
-
* 1.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
1
|
+
/**
|
|
2
|
+
* NAT/DDNS types for cadre-host.
|
|
3
|
+
*
|
|
4
|
+
* cadre-host runs on home/SMB machines that are typically behind NAT. For the
|
|
5
|
+
* nodes it runs to be dialable from the open internet the host must:
|
|
6
|
+
* 1. Map every hosted node's two libp2p ports (TCP, and the WebSocket port a
|
|
7
|
+
* phone dials) through the upstream router, or let the user forward them
|
|
8
|
+
* by hand.
|
|
9
|
+
* 2. Detect its external IP (and whether it's behind CGNAT).
|
|
10
|
+
* 3. Optionally publish a stable hostname via dynamic DNS.
|
|
11
|
+
*
|
|
12
|
+
* State for these concerns lives in `<rootDir>/nat.json` (user settings) and is
|
|
13
|
+
* exposed as a single `NatStatusSnapshot` to the management UI, with one
|
|
14
|
+
* `NodeReachability` entry per hosted node.
|
|
15
|
+
*
|
|
16
|
+
* Credentials for DDNS providers are NOT stored in `nat.json`. They go through
|
|
17
|
+
* the SecretsStore (keytar, with a 0600 file fallback).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Reachability roll-up across the host's running nodes — see `reachability.ts`. */
|
|
21
|
+
export type DirectReachability = 'reachable' | 'unreachable' | 'unknown' | 'cgnat';
|
|
22
|
+
|
|
23
|
+
/** The two ports of a hosted node that are mapped: libp2p TCP and libp2p WebSocket. */
|
|
24
|
+
export type PortKind = 'tcp' | 'ws';
|
|
25
|
+
|
|
26
|
+
/** Where a port's external route came from. */
|
|
27
|
+
export type PortRouteSource = 'upnp' | 'manual';
|
|
28
|
+
|
|
29
|
+
/** How one of a node's ports is reached from outside the home network. */
|
|
30
|
+
export interface PortRoute {
|
|
31
|
+
internalPort: number;
|
|
32
|
+
/** Port reachable from outside; null when there is no route. */
|
|
33
|
+
externalPort: number | null;
|
|
34
|
+
source: PortRouteSource | null;
|
|
35
|
+
/** ISO timestamp when the router lease expires; UPnP routes only. */
|
|
36
|
+
leaseExpiresAt: string | null;
|
|
37
|
+
/** Last mapping failure, in plain language; null when the last attempt succeeded. */
|
|
38
|
+
error: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Per-node verdict. */
|
|
42
|
+
export type NodeVerdict = 'mapped' | 'manual' | 'unreachable';
|
|
43
|
+
|
|
44
|
+
/** Reachability of one hosted node, as reported in the status snapshot. */
|
|
45
|
+
export interface NodeReachability {
|
|
46
|
+
nodeId: string;
|
|
47
|
+
/** Whether the node's process is running; a stopped node keeps its last routes until the unmap grace elapses. */
|
|
48
|
+
running: boolean;
|
|
49
|
+
verdict: NodeVerdict;
|
|
50
|
+
/**
|
|
51
|
+
* Plain-language reason and remedy when unreachable, e.g. "Router refused
|
|
52
|
+
* the WebSocket port mapping. Forward port 10004 to 192.168.1.20 on your
|
|
53
|
+
* router, then tell cadre-host the external port."
|
|
54
|
+
*/
|
|
55
|
+
reason: string | null;
|
|
56
|
+
tcp: PortRoute;
|
|
57
|
+
/** Null for a handle persisted by an older build without a WebSocket port. */
|
|
58
|
+
ws: PortRoute | null;
|
|
59
|
+
/** Multiaddrs (no `/p2p/` suffix) through which the node is reachable from outside. */
|
|
60
|
+
publicAddrs: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The UPnP gateway as the host last saw it. */
|
|
64
|
+
export interface NatGatewayStatus {
|
|
65
|
+
found: boolean;
|
|
66
|
+
/** This machine's address on the router's subnet — the address a user forwards to. */
|
|
67
|
+
lanAddress: string | null;
|
|
68
|
+
/** The WAN address the router reports for itself. */
|
|
69
|
+
routerExternalIp: string | null;
|
|
70
|
+
/** Why discovery or the last gateway operation failed; null when it did not. */
|
|
71
|
+
lastError: string | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Snapshot returned to the management UI / CLI. */
|
|
75
|
+
export interface NatStatusSnapshot {
|
|
76
|
+
upnpEnabled: boolean;
|
|
77
|
+
gateway: NatGatewayStatus;
|
|
78
|
+
|
|
79
|
+
// External IP (from the public probe, falling back to the router's report).
|
|
80
|
+
externalIp: string | null;
|
|
81
|
+
externalIpDetectedAt: string | null;
|
|
82
|
+
cgnatDetected: boolean;
|
|
83
|
+
|
|
84
|
+
/** Roll-up across running nodes; see `reachability.ts`. */
|
|
85
|
+
directReachability: DirectReachability;
|
|
86
|
+
lastTestedAt: string | null;
|
|
87
|
+
|
|
88
|
+
ddns: NatDdnsStatus;
|
|
89
|
+
nodes: NodeReachability[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** DDNS sub-section of the status snapshot. */
|
|
93
|
+
export interface NatDdnsStatus {
|
|
94
|
+
/** Provider ID (e.g. "duckdns"), null when no DDNS is configured. */
|
|
95
|
+
providerId: string | null;
|
|
96
|
+
/** Configured hostname (e.g. "foo.duckdns.org"), null when unconfigured. */
|
|
97
|
+
hostname: string | null;
|
|
98
|
+
/** True when the user opted out of cadre-host updating the record. */
|
|
99
|
+
externallyManaged: boolean;
|
|
100
|
+
/** Last attempt timestamp (ISO), null until first attempt. */
|
|
101
|
+
lastUpdateAt: string | null;
|
|
102
|
+
/** Whether the most recent attempt succeeded. */
|
|
103
|
+
lastUpdateOk: boolean | null;
|
|
104
|
+
/** Most recent error message, null when last attempt succeeded. */
|
|
105
|
+
lastError: string | null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The external ports a user forwarded on their router for one node, per port kind. */
|
|
109
|
+
export interface ManualForward {
|
|
110
|
+
tcp?: number;
|
|
111
|
+
ws?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Patch applied by `PUT /nat/nodes/:nodeId/forward`: `null` clears that port. */
|
|
115
|
+
export interface ManualForwardPatch {
|
|
116
|
+
tcp?: number | null;
|
|
117
|
+
ws?: number | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* On-disk shape of `nat.json`. NEVER contains DDNS credentials — those live
|
|
122
|
+
* in the SecretsStore.
|
|
123
|
+
*/
|
|
124
|
+
export interface NatSettingsFile {
|
|
125
|
+
version: 1;
|
|
126
|
+
/** Whether to ask the router for port mappings at all. */
|
|
127
|
+
upnpEnabled: boolean;
|
|
128
|
+
/** Per node id: the external port the user forwarded on their router, per port kind. */
|
|
129
|
+
forwards: Record<string, ManualForward>;
|
|
130
|
+
ddns: NatDdnsSettings;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** DDNS sub-section of `nat.json`. */
|
|
134
|
+
export interface NatDdnsSettings {
|
|
135
|
+
/** Provider ID (e.g. "duckdns"). Null = unconfigured. */
|
|
136
|
+
providerId: string | null;
|
|
137
|
+
/** Hostname to publish. Null = unconfigured. */
|
|
138
|
+
hostname: string | null;
|
|
139
|
+
/**
|
|
140
|
+
* True means the user maintains the DNS record themselves (e.g. via the
|
|
141
|
+
* provider's own auto-update agent on the router). cadre-host shows the
|
|
142
|
+
* hostname but never tries to update it.
|
|
143
|
+
*/
|
|
144
|
+
externallyManaged: boolean;
|
|
145
|
+
/** DDNS update interval in milliseconds. Default 5 min. */
|
|
146
|
+
intervalMs: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Typed error codes for NatError. */
|
|
150
|
+
export type NatErrorCode =
|
|
151
|
+
| 'mapping_failed'
|
|
152
|
+
| 'router_unreachable'
|
|
153
|
+
| 'ip_detection_failed'
|
|
154
|
+
| 'ddns_provider_unknown'
|
|
155
|
+
| 'ddns_credentials_missing'
|
|
156
|
+
| 'ddns_update_failed'
|
|
157
|
+
| 'secrets_unavailable'
|
|
158
|
+
| 'storage_error'
|
|
159
|
+
| 'invalid_config'
|
|
160
|
+
/** A manual forward named a node id the host does not run. */
|
|
161
|
+
| 'unknown_node';
|
|
162
|
+
|
|
163
|
+
/** Typed error carrying a stable `code` for HTTP mapping in the local-ui. */
|
|
164
|
+
export class NatError extends Error {
|
|
165
|
+
readonly code: NatErrorCode;
|
|
166
|
+
|
|
167
|
+
constructor(code: NatErrorCode, message: string) {
|
|
168
|
+
super(message);
|
|
169
|
+
this.name = 'NatError';
|
|
170
|
+
this.code = code;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Description of a configurable field on a DDNS provider. */
|
|
175
|
+
export interface DdnsConfigField {
|
|
176
|
+
/** Field key (e.g. "token"). */
|
|
177
|
+
key: string;
|
|
178
|
+
/** Human-readable label (e.g. "DuckDNS token"). */
|
|
179
|
+
label: string;
|
|
180
|
+
/** True if the value is a credential that must be stored in the SecretsStore. */
|
|
181
|
+
secret: boolean;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Summary of a DDNS provider for UI / CLI listing. */
|
|
185
|
+
export interface DdnsProviderInfo {
|
|
186
|
+
id: string;
|
|
187
|
+
displayName: string;
|
|
188
|
+
configFields: ReadonlyArray<DdnsConfigField>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Typed HTTP handlers exposed to the local UI server for Fastify wiring.
|
|
193
|
+
*
|
|
194
|
+
* These take typed objects and either return typed results or throw a
|
|
195
|
+
* `NatError` whose `.code` the error handler maps to an HTTP status code.
|
|
196
|
+
*/
|
|
197
|
+
export interface NatHandlers {
|
|
198
|
+
getStatus(): Promise<NatStatusSnapshot>;
|
|
199
|
+
testReachability(): Promise<NatStatusSnapshot>;
|
|
200
|
+
listDdnsProviders(): Promise<DdnsProviderInfo[]>;
|
|
201
|
+
putDdns(body: {
|
|
202
|
+
providerId: string;
|
|
203
|
+
hostname: string;
|
|
204
|
+
config: Record<string, string>;
|
|
205
|
+
externallyManaged?: boolean;
|
|
206
|
+
}): Promise<NatStatusSnapshot>;
|
|
207
|
+
putSettings(body: Partial<Omit<NatSettingsFile, 'version' | 'forwards'>>): Promise<NatStatusSnapshot>;
|
|
208
|
+
putForward(nodeId: string, patch: ManualForwardPatch): Promise<NatStatusSnapshot>;
|
|
209
|
+
}
|
package/src/{installer/__tests__/identity.test.ts → orchestrator/__tests__/identity-file.test.ts}
RENAMED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { mkdtempSync, rmSync, statSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
|
|
6
|
-
import { generateIdentity, loadIdentity } from '../identity.js';
|
|
7
|
-
|
|
8
|
-
describe('
|
|
9
|
-
let tmp: string;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
tmp = mkdtempSync(join(tmpdir(), 'cadre-host-id-'));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('round-trips an Ed25519 keypair through the file', async () => {
|
|
20
|
-
const path = join(tmp, 'identity.key');
|
|
21
|
-
const generated = await generateIdentity(path);
|
|
22
|
-
expect(generated.peerId).toMatch(/^12D3Koo/);
|
|
23
|
-
|
|
24
|
-
const loaded = loadIdentity(path);
|
|
25
|
-
expect(loaded.peerId).toBe(generated.peerId);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it.runIf(process.platform !== 'win32')('persists with mode 0600 on POSIX', async () => {
|
|
29
|
-
const path = join(tmp, 'identity.key');
|
|
30
|
-
await generateIdentity(path);
|
|
31
|
-
const mode = statSync(path).mode & 0o777;
|
|
32
|
-
expect(mode).toBe(0o600);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, statSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { generateIdentity, loadIdentity } from '../identity-file.js';
|
|
7
|
+
|
|
8
|
+
describe('identity file', () => {
|
|
9
|
+
let tmp: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tmp = mkdtempSync(join(tmpdir(), 'cadre-host-id-'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('round-trips an Ed25519 keypair through the file', async () => {
|
|
20
|
+
const path = join(tmp, 'identity.key');
|
|
21
|
+
const generated = await generateIdentity(path);
|
|
22
|
+
expect(generated.peerId).toMatch(/^12D3Koo/);
|
|
23
|
+
|
|
24
|
+
const loaded = loadIdentity(path);
|
|
25
|
+
expect(loaded.peerId).toBe(generated.peerId);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it.runIf(process.platform !== 'win32')('persists with mode 0600 on POSIX', async () => {
|
|
29
|
+
const path = join(tmp, 'identity.key');
|
|
30
|
+
await generateIdentity(path);
|
|
31
|
+
const mode = statSync(path).mode & 0o777;
|
|
32
|
+
expect(mode).toBe(0o600);
|
|
33
|
+
});
|
|
34
|
+
});
|